Monday, January 12, 2009

The Science of Debugging

One of the best pieces of advice I ever received about debugging code was something that I could have just as easily brushed aside.  I was attending a work-sponsored training course, and the instructor said something that struck me as obvious and profound at the same time: Always start with a hypothesis.

How many times have you found yourself chasing down a bug, clueless about its cause, blindly adding variables to your watch window (or for XCode Objective-C programmers, blindly opening up the console window and typing “print object.property” – no, wait, that common syntax isn’t supported by the debugger.  So you type “print [object property]”, then receive an error that it can’t determine what the type is without a cast, even though it’s just an integer, so you type “print (int)[object property]”) without really knowing what you’re looking for?  As you step through the code aimlessly, you finally realize that you’ve jumped past the part of the code which reproduced the bug, and you have to start over.  (This situation is magnified by the lateness of the hour and the length of time you’ve been coding prior.)

You repeat this process several times, gradually locking down more and more of your code until you’re picking it apart one line at a time.  But even this doesn’t help if you don’t know what you’re looking for.  But that’s just it – you don’t know what you’re looking for.  If you did, you wouldn’t need to be debugging in the first place. 

Rather than spinning your wheels, you need to come up with a hypothesis; an educated guess about what might be causing the behavior you’re seeing.  If a dialog isn’t appearing on the screen, a simple hypothesis might be:

The code which spawns the dialog is not being executed. 

Testing this hypothesis is simple; just set a breakpoint on the line of code responsible for displaying the dialog (assuming you find it – maybe that was the problem), run your program, and observe whether or not it’s hit.  If it’s hit and the dialog still doesn’t appear, that’s fine – great, even.  You’ve gained information about the situation.  Rather than going on a wild goose chase, you’ve narrowed down the issue.  Now you can test another hypothesis:

The dialog’s Visible property is set to false.

Failing that, try another:

The dialog’s bounds are outside of the screen’s bounds.

And other:

The dialog is rendering underneath another window of the application.

The key is that you’re learning more about the situation with every hypothesis, rather than repeating the same guesses, moving off in random directions based on hunches, and becoming more confused and frustrated.

Note that all of these hypotheses are testable and falsifiable.  That is, you can objectively demonstrate if the hypothesis is true or false through experimentation.  This is crucial not only in debugging but for science as a whole.  There’s a reason why Intelligent Design Theory is the butt of jokes; there’s nothing testable or falsifiable about the idea that the universe was created by (essentially) magic.  Science, on the other hand, gains from both confirmed and falsified hypotheses (a fact which is lost on evolution-deniers).  Just as science gains knowledge about the universe from a disproven hypothesis, you gain knowledge about your application the same way.

Obvious, right?  When you’re tracking down a frustratingly elusive bug while exhausted and over-caffeinated, it isn’t. 

No comments: