Every developer needs to find (and fix) bugs. But how can you squash a bug you don’t know how to find?
Writing software is, by most estimations, the easy part. A developer can be creative about user interface design, fancy algorithms, or the application’s infrastructure. However, as Thomas Edison was reputed to say, genius is 1% inspiration and 99% perspiration. For software to work as designed, it must be thoroughly debugged—but nobody teaches developers how to sweat.
Although every programmer spends a huge amount of time debugging, the defect-finding skill is learned on one’s own or in an informal apprenticeship. You can take academic courses on application design, esoterica of programming languages, and QA tools, but bug squashing? It’s up to you, buddy.
Perhaps the subject isn’t technically interesting enough. One programmer told me, “Colleges—at least the ones I was in for years—don’t teach debugging. There is no theory to expound and no beguiling formalisms to inculcate.” Few people even take the time to recommend books for QA professionals to help them improve testing techniques.
In any creative endeavor, there are techniques that can get (or keep) your head screwed on straight. Let’s fix that.
Specifically: These axioms can help you find and fix bugs. If you keep these six rules in mind, perhaps you might avoid another lost weekend, during which you search for the source of that dratted problem and pull out the last of your hair.
If you can’t find the bug, you’re looking in the wrong place. Sounds self-evident, doesn’t it? But this advice — first offered to me by Mike McKernan, a compiler developer at Wang Labs whom I knew in the 80s — pulled me out of more than one code swamp. Programmers usually look in the “obvious” place in the code for the problem, which is sensible. But they keep looking there even when it becomes apparent that the offensive bug isn’t anywhere nearby.
Keep in mind the words of Sherlock Holmes: “When you have eliminated the impossible, whatever remains, however improbable, must be the truth.”
Go back to where it last worked. If you’re writing brand-new code, the easiest way to find the bug is to go back to the last version of the code that worked, and add code segments until it doesn’t work anymore.
Or more directly: What changed?
Approach debugging with the scientific method. Form a hypothesis; create an experiment to disprove the hypothesis; repeat until the bug is found.
Contain the bug. To discover the source of the bug, you write code to box in the problem’s origination. Some people prefer debuggers; others eschew them. But it isn’t an either/or decision, and their functionality overlaps. Debuggers excel at state visualization and helping you get the big picture of an unfamiliar code base (e.g. “Where is this function called from?”). In contrast, printfs, logs, and traces shine for transient visualization (to discover how a system moves into an undesirable state) and to add logic to capture certain conditions (e.g. “here it looks like an accumulator overflowed”). Use the right tool for the right task.
One basic technique is to add bits of code to isolate where the problem occurs. For instance, if you have memory corruption problems, add bits of code to check that memory. Running the program lets you narrow down the problem to between two of those bits. This technique is sometimes called a “wolf trap” because of the premise that, to trap a wolf in Alaska, you build a fence to divide the land in half, and listen for a howl on one side of the fence. You continue building fences to divide the suspect area into smaller and smaller pieces. A wolf-phobic college professor might call this technique a “binary bug search modified with a pseudo-random start point.”
Another way to accomplish the same thing is to start at the bug and work backwards. Explore the program’s state at that point to locate discrepancies, and work out where the bug occurred by going up the call stack. Then set breakpoints preceding where the bug occurred, and work backwards, exploring data at each breakpoint.
Look for the bug in a new place, time, or format. Sometimes, you can’t find the bug no matter how hard you try. That’s when it’s time to walk away for a while. Take a nap. Hit the Print button. Some programmers discover that when they can’t find a bug on the screen, it leaps off the printed page. Others record their bug-seeking process by writing each attempt in a paper notebook.
Another way to gain perspective is to have someone else look at the code. Pair programming may not be helpful for debugging tasks (opinions differ), but sometimes the act of explaining what’s going on helps your discovery process.
Be responsible for the code. An astonishing number of developers fully expect that all their applications will work perfectly the first time, and they test to “prove correctness” rather than to find the inevitable problems. Never expect it to work the first time. Assume that the problems are your fault, not a bug in the compiler.
One developer told me that the hardest thing to learn was to accepting constructive criticism. Before that attitude adjustment, it was too easy to see bug reports and code reviews as a personal attack.
A healthy mindset includes a willingness to throw away code. Sometimes, it’s wisest to delete the code and start over. If you have fixed five or six bugs from the same section of code, throw it out and rewrite it from scratch. Some modules are simply bug factories, reflecting an underlying design problem.
Learn from the bug. Don’t repeat the same mistakes. Take the time to learn the lessons you can from this one.
Whenever you run into a nasty bug, ask yourself, “How could I have prevented that? Is there some programming technique that would have kept it from happening in the first place? What kind of test should I have run to detect it early?” Those meta-skills can drive your debugging techniques.
I intentionally kept these guidelines at a high level and concentrated on philosophical attitudes. Plenty of developers also offered detailed suggestions that might help you.
Obviously, to properly find defects you need to write test cases. But what’s the best way to go about that? Our white paper highlights the test-case-writing tips you ought to know.