» Thursday, 25 March A.D. 2010

love my debugger

I love my debugger.

I realize that this is not the usual stance among developers. The Practice of Programming by Kernighan and Pike states in the chapter on debugging:

As a personal choice, we tend not to use debuggers beyond getting a stack trace or the value of a variable or two. One reason is that it is easy to get lost in details of complicated data structures and control flow; we find stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Clicking over statements takes longer than scanning the output of judiciously-placed displays. It takes less time to decide where to put print statements than to single-step to the critical section of code, even assuming we know where that is. More important, debugging statements stay with the program; debugger sessions are transient.

Now, “thinking harder” is certainly a valuable skill. Debugging kernel oopses without the running kernel is certainly a valuable skill. (Read that link; it's the sort of debugging you ought to aspire to.) But I think it's fair to say that the above paragraph advocates two approaches to debugging: thinking hard and putting in print statements.

The critical observation that's made me love my debugger in the last several months is this: a debugger is a tool for inserting print statements at runtime. Stick a breakpoint where you think things are going wrong and poke around when you stop there. Stick a breakpoint a little further along if you need to. Disable the previous one because it's not helpful. If your debugger supports it, tell it to automatically print out some information when it hits a breakpoint and/or make that breakpoint depend on some condition you think is interesting. If you're feeling daring and you think you botched an assignment somewhere, tell the debugger to modify some variable values when you hit a breakpoint and continue execution. Yes, it's all boiling down to print statements, but you're doing it at runtime.

Plus, you can do all of these things without recompiling your program. And that is a huge huge win. With a huge program to debug, even with a beefy machine for compilation, you're stuck twiddling your thumbs waiting for a compile/link cycle. Ooops, wait, you have to do another one because you screwed up a semicolon or some matter of syntax. Oh, hm, that print statement isn't giving me enough information; I need to go insert another one somewhere. Bother, that's too much information, I should set up a debug level framework for enabling these messages separately. Wait, I need some information over here to obtain more clarity; should that be debug level one or debug level two or another debug level entirely? Whoops, fix that format string mismatch with my printf...you see how this can get tedious.

I was starting down the above path earlier this week when I decided to use the debugger instead. (And in a humorous twist, I was working on GDB, so reaching for the debugger should have been the first thing on my mind!) And the experience was significantly more pleasant and took me much less time than bothering with debug levels and printfs and staring at log output.

I'm not as good as Kernighan and Pike; perhaps I don't think hard enough about where to place my debug statements. Or I make too many simple typos and require too many recompile steps. Or the programs I work on don't follow the Unix Way strictly enough and are too big. And yes, I've worked on several codebases where using a debugger was painful and print statements are a better option. But the next time you're about to write a bevy of print statements to tell you everything you need to know about what your program is doing, try using a debugger instead. I bet you will be surprised at how much better the debugging experience is.

posted by Nate @ 9:11PM