In my last project, I was working on a big, complex expert system. For various reasons using a debugger to inspect bugs was difficult. One of the reasons was that we got live data from the real system we were trying to replace, so traffic was high, and following one request was problematic.
Especially when we had a bug on the client side debugging was hard. Our client was a custom Java frontend based on Eclipse RCP and we didn't actually have that much experience with the technology (we bought and revised an existing product, we didn't create a green field app. I don't remember what the actual UI framework was). For that reason our UI logic was really hidden in layers upon layers of Java code and XML files. Hard to inspect with a debugger because there was a lot of generic code, which made it hard to trace the execution flow.
In those situations I found a for me new strategy to debug applications. In the past I had almost exclusively used the debugger, but in this system I didn't find the debugger terribly helpful in most cases.
So what I did was the following: Really think hard about what I actually wanted to get out of the debugging. That was the first step. What variables did I actually want to look at, and what properties of their values was of interest? I think debugging with a debugger can often degenerate into a "let's have a look" kind of thing, where you don't think about this question very much before a debugging session. But asking that question beforehand is vital. I only go into debugging sessions empty-handed now if I really have no idea at all what the problem might be from looking at the code, which rarely ever happens.
The next step is to craft a set of log statements, logging the values of the variables you're interested in. The great thing about log-statements is that you can log any information, in any format you want. And one other crucial thing: Log statements can give you timestamp information. I found that really handy to debug timing problems, which I think are very hard to see using a debugger.
Then when you have your log-statements, you either run a local process with them added or you make what we called a temp (for temporary) commit. A temp commit was there only for the duration of the investigation of the bug, and would be taken from the codebase once we merged into main. The nice thing is that with a temp commit, you can deploy your system into test, then throw real data at it and then grab the logs afterwards.
Now you are in a great position to analyze bugs because you can programmatically analyze the logs. In my experience that's way more useful than a debugging session, which unless recorded as a video does not survive the session itself, and everything learned has to be passed by word of mouth. By programmatically analyzing logs you now can't only share your analysis results, but also the data which led to the results and even the analysis code. That way another developer can pick up where you left off with the analysis, even if you get sick or otherwise indisposed. You can even go so far as to build a little framework, making future analysis easier.
All this really helped me a lot in understanding the behavior of the system better. I came to a point in that project where I rarely ever used the debugger anymore. To me logs are a far more deliberate, structured way to analyze bugs than using a debugger.
Where I think debuggers are still useful is when you don't even know what the execution flow is actually like, because for example it is hidden in generic mechanisms in the code and you can't trace it just by reading the code. But even then I only analyze the control flow in the debugger, and once I know the classes involved I switch to writing logs.
If you've never used logs for debugging purposes I hope this piece gives you some new ideas, and hopefully makes debugging a little easier.
Thanks for reading, and feel free to let me know your thoughts in the comments.
Top comments (0)