By June 1949, people had begun to realize that it was not so easy to get a program right as had at one time appeared. It was on one of my journeys between the EDSAC room and the punching equipment that the realization came over me with full force that a good part of the remainder of my life was going to be spent in finding errors in my own programs. — Maurice Wilkes, creator of the first stored-program computer, EDSAC
Debugging is programming, and programming is often mostly debugging. One of the most useful skills you can pick up as a developer. Some lessons learned over the years from debugging. Strategies and observations.
- Reproduce with the smallest example. In the simplest environment.
- Read and re-read the error statement. Read the stack trace. Add more logging if you don’t know where the error is thrown.
- Change one thing at a time.
- Divide and conquer. Sometimes that means binary search on good/bad commits. Other times isolating the problem.
- Be open to debugging in different environments.
- State your assumptions.
- Get a second set of eyes on it.
- If you're debugging some stateful code, think about _how_ you ended up at that state. A recipe that (reproducibly) gets you to that state is often the path to fixing it.
- Look at the logs (all the logs).
- When in doubt, start with the most recent changes (especially dependencies changes).
- Make sure the code you’re looking at is actually getting executed (deployed code, external dependencies, etc.)
- Reduce the feedback loop — write a test or figure out the quickest way to test your debugging hypotheses.
- Look for patterns. Look for symmetry.
- Redundancy tends to hide bugs.
- Validate your inputs.
- Trust, but verify behavior.
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?" — Kernighan, The Elements of Programming Style (1974)