Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it? — Brian Kernighan
Anytime you sit down to debug some code, you’ll already be armed with a piece of valuable information: this code is not doing what I expected it to do.
Debugging can take a lot of time. So can I give you a piece of advice please? It’s something that’s helped me a ton in the past years.
Ready? Here it is:
Every action you take should give you more information than you had before.
How many times have you rerun an application or a test, and have it bonk in the exact same way?
I mean sure that means it’s reproducible, so that’s good, and at least it’s not some weird race condition or a Heisenbug.
But what’s your mindset when you’re re-running for the 17th time? Are you really making progress on debugging or are you just stuck and killing time?
I bet it’s the stuck one.
And I can say this in confidence because I’ve been there. A lot. It sucks because it feels like we’re making zero progress.
Now the question: How do I perform actions that get new information than I had before?
And it’s actually four questions instead of one.
Treated like a flowchart, you’ll be successful at debugging any program. Here they are:
(1) Where can I look for information?
If you’ve just started debugging, the first thing is to locate where the problem showed up.
Now this doesn’t mean that just because the program blew up at point “W”, that’s where the problem is. It could mean that the problem manifested at point “A”, and the symptom showed up at “W”. We’re just looking for a starting point here.
If you’ve found where the symptom is happening (like from inspecting a stacktrace or a core file) then before you kick off that run again, ask yourself: is there anything right here where I’m looking that would give me more information? Or do I need to look at other places upstream and downstream of here?
Make whatever code changes, breakpoints, or setup needs to be done, and then run it again.
(2) Did I learn a new thing?
Did you learn something? What else did the program tell you due to the measures you took in the previous step?
And actually, even if the answer is “no”, that still counts as a “yes”. Because if you made a change and there was no positive or negative difference in the app’s behavior, that’s a very important data point. It might mean you’re looking in the wrong place.
What, by itself, does this new information mean?
(3) Does this, combined with the other things I’ve learned, mean something?
By itself, data has a certain meaning. Together, data has different meaning.
Combine this new information with the data you’ve accrued up to this point. For example: if you’ve made three changes to a SQL query, and the 2nd and 3rd ones manifested different errors than the 1st change did, what’s that mean?
Once you’ve looped through these steps you’ll have data, along with the stuff you already know about your application. You’ll probably find you’re getting closer to the solution too.
(4) Did that fix it?
Yes? Great! If the most recent change fixed the problem, then clean up all those debug statements, prints, breakpoints, etc. and rerun to make sure you didn’t remove something important.
No? Still great! It means you’re making progress. Knowing what doesn’t work is just as important as knowing what does. So don’t get discouraged–you took a step forward. Go back to step (1) and repeat.