The art of debugging
Published in
3 min readFeb 13, 2018
Pesky bugs and how I smack them.
- Code review. After writing anything I check it myself (don’t waste someone else’s eyes before you reviewed the code yourself). This catches most typos (things like = for ==, variables with typos in interpreted languages, etc). I also simulate code in my head, which makes sure simple logic errors that may have been made are caught early, before even running anything.
- Static code analysis. Our code runs through all sort of code analysis, linters, etc, automatically on every commit.
- Plenty of logs. I start any project by having a good Log class, with multiple log levels with filtering, information about the line and file that called the log, sometimes even automatic backtracing in cause of errors (in Python I automatically fall into the console debugging). I usually can get a pretty good idea of what went wrong just by checking these logs. Many times adding a few extra log() calls with detailed information about variables is quicker and simpler than using a debugger. And in some cases (websites, for example) setting up a debugger is not trivial, so this has to solve the problem. Also, it helps if you need to know what went wrong in production.
- Tests. This is 2a), in fact, because you can only test properly if you have a test suite. I’ll write someday about how I write test suites, but the basic needs are universal: tests should be as simple as possible; should be written before or during development; should cover as much as possible of the code; should be completely automatic when viable; and should test for both the “good cases” and the “bad cases”.
- Talk about it. Rubber duck debugging works. Try to explain what is going on to peers and you often will find the problem before you end your explanation.
- If things are looking complicated and logs did not help enough, call that faithful debugger. These days my use of debuggers is quite simple: I often set breakpoints in the code itself (debugger for javascript, raise(SIGINT) for C/C++ and gdb), since it’s usually faster than navigating through the code in the debugger if I know where I want to stop. When the breakpoint is hit, I print all variables to check if their values are correct, and if necessary I step through a few calls.
- Stack traces are your friends. In C/C++ I let the program run until it crashes, run a backtrace (bt) and start to move up in the call stack until I get to the culprit — usually it is pretty obvious. Python, PHP, JS, all of them have easy ways to get traces.
- In JavaScript and Python you can fall into an interactive terminal when the debugger stops that is just as good as the real program: so you can pretty much type any code there, which is run instantly. Then you see immediately if it fixes the code. I’ve come to love this: interactive programming can be extremely useful (great for testing complicated selectors in javascript, for example!). gdb can do some of that, but has several limitations due to C++ being compiled. But use STL support at least for decent printing of STL structures.
- After the bug is fixed, make sure it becomes a test in your suite to avoid regressions.