In my continuous quest for self-improvement, I just finished reading Effective Debugging: 66 Specific Ways to Debug Software and Systems by Diomidis Spinellis. Debugging is a critical and under-appreciated skill. Building new features into software gets most of the attention and accolades, but being able to debug software keeps it running. Spinellis offers a wide variety of debugging techniques as the title suggests, and there is content worth learning (or re-learning!) for a developer of any skill level.
In this review I’d like to call out a few techniques that really stuck with me.
Item 38: Review and Manually Execute Suspect Code. Many developers stare at broken code on a screen. Spinellis argues for the developer to engage multiple senses through using print-outs, whiteboards, multiple color of pens, and manipulating physical objects. This pattern has definitely helped me out of several jams. Something about the brain gets “unstuck” when you change frames this way – your hands may see what your eye cannot!
Item 42: Use Unit Tests. Anyone who knows me knows I talk and write about unit testing ad nauseam (see here and here for starters). Please indulge me in a soapbox moment once more using Spinellis’ advice. The perfect bug report is essentially the smallest possible unit test that recreates the problem. Building that test gives essential clarity into what is causing the problem and what is relevant. The process of building this unit test is essentially writing down (in code) the entire debugging process. Thus, the final unit test serves as a permanent record of your debugging efforts AND can be used to prevent future regression. What could be better?
Item 43: Use Assertions. Similar to the unit tests advice, Spinellis advises developers to turn their assumptions into coded assertions. These assertions should be used to verify that any pre- and post-conditions on a block of code hold true. Rather than putting these assertions into comments, use formal language mechanisms to put them into code. An assertion can be used to force immediate failure in your software, leading you immediately to the place where the failure occurred (the “fail fast” principle in action). Most programming languages enable assertions to be disabled, a good option when you would rather fail gracefully than crash the application.
Item 43: Minimize the Differences between a Working Example and the Failing Code. Many times as developers we go to Stack Overflow to get the gist of a technique we want to perform, then adapt it to our current problem. If our new code doesn’t work, what can we do? The best option is to start from a “known good” place, which is probably the Stack Overflow example verbatim. Running quick change/test/verify cycles on small updates helps quickly identify where things are going off the rails. Relentlessly finding a “known good” state and minimizing the differences to the current/end state is a theme that repeats in Spinellis’ text and for very good reason. This is probably the most important advice I would give to new developers.
These are just my favorites from the collection of 66 techniques. As a developer, it’s likely that very little of your formal education focused on building specific debugging skills. Even after several years in the industry, I was surprised to see a claim of 66 actual, distinct debugging techniques. After finishing the book, I agree with the author that a full-featured debugging tool kit has 66 techniques or even more. I highly recommend this book for developers of any level.