The art of debugging

Bruno Barberi Gnecco
Corollarium
Published in
3 min readFeb 13, 2018

Pesky bugs and how I smack them.

Rubber duck debugging (source: Wikipedia)
  1. 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.
  2. Static code analysis. Our code runs through all sort of code analysis, linters, etc, automatically on every commit.
  3. 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.
  4. 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”.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. After the bug is fixed, make sure it becomes a test in your suite to avoid regressions.

--

--