When ever programming teachers get together one of the debates that often starts up is between using a built-in debugger or using other debugging tools – most commonly extra print/display statements. It is an interesting debate and can get quite religious at times. I have been a long fan of debuggers ever since I used ODT (Octal Debugging Tool) on a PDP-8 many years ago in college. ODT let me examine the contents of memory and many of the computer’s registers while debugging assembly language programs. The values were displayed in binary coded octal by the way.
Debuggers have come a long way since then to say the least. Today it is easy to set break points, track the changes in values in variables and intimately watch expressions as they are executed. I do believe that one can learn a lot just by following the execution in code in the debugger.
One of the problems with debuggers though is that sometimes the extra data and process that code goes through in the debugger changes its nature. That means that some bugs that show up easily without the debugger go away inside the debugger. And it seem, though rarely in my experience, that things go the other way as well. For that reason it is always good to have other options. Multiple tools in the tool box is the way to go.
The other technique that has been used since the beginning of programming is logging data during execution using output statements either to the system console or to a log file. This can be a very valuable tool especially when there is no easy way to determine how many iterations of a loop (for example) have to take place before the bug occurs. Single stepping through a loop 1,000 times is tedious. There are times when the program will crash in the debugger and one can get some information but all too often one can’t get enough. being able to look through a log file can save a lot of time. There is an art to setting these statements and it is something people should be taught.
But there is more to debugging than running things live in the debugger or tracking the flow of code though a complex set of loops and methods. There is the matter of unexpected errors. Now in theory one should test every possibility but in practice that often turns out to be impossible. One simple operating system I worked on over 25 years ago was complex enough that it was estimated that testing every code path in every possibility would take over 300 years. I think that the complexity of operating software has more than kept up with the speed of CPUs so I suspect that sort of math to still be that way.
So to me adding debugging tools and techniques means making sure that when something does go wrong people have enough information to figure out what is going on. Error handling code should provide more information than just the routine that crashed and an operating system dump message. The values of important variables for example can be very useful. Things like loop counters are particularly valuable.
Additional debugging statements that can be set on or off without recompilation can be particularly valuable. Often one can set up their debugging statements to execute or not based on some external setting. More than a few times this has made the difference between a problem being solved quickly and taking a very long time. This is especially useful as a common way to deal with bad data. If the program keeps crashing turning on the debugging output and rerunning the data often pinpoints the bad data more quickly and accurately than going through it by hand ever could.
No matter how good a coder one is there are always going to be bugs to find. Perhaps not in your code but when you are assigned to debug code written by someone else having some debugging techniques in your tool kit can be a life saver.
This is the sixteenth of a series of posts based on the book Programming Proverbs by Henry Ledgard. The index for the series is an earlier post and discussion of the list as a whole is taking place in the comments there. Comments on this “proverb” are of course very welcome here.