A primer on debugging
I had to teach someone about debugging today, thought I’d share my thoughts here too.
What is debugging?
First and foremost, debugging is fixing your brain. The code does exactly what it was told to do. It’s YOU that doesn’t like what it does and doesn’t understand why it does that. As such, all debugging is about helping YOU figure out WHY the code behaves in a way that YOU don’t like.
Notably, it has nothing to do with fixing bugs. Just understanding their existance. This means “randomly changing code until something works” is not debugging, it’s poorly and randomly bug fixing.
How does one debug? Science!
Debugging is not “a science”, it is science. Science is how you figure out the rules behind a system and why it works the way it does. Doesn’t matter if that’s why apples fall from trees or why your app fails to display trees. The only way to figure it out is to create a model and run experiments until that model breaks or your model explains the bug.
Narrowing down the problem
When you start debugging, you think the issue could be any number of problems. Imagine it like a big space and the problem is…somewhere, but you don’t know where. To find a point in a space, you need to rule out as much space as you can as quickly as you can. Like how each step of binary search rules out 50% of the remaining space, you also want experiments that rule out large swaths of possible issues quickly. Then you grind and iterate until you’ve found the root cause.
There are two approaches to running experiments:
Explicit right brain. This is a formal lab coat, “you’d get an A+ on your 10th grade chemistry notebook” experiment.
Example:
I predict at line 20, X is 5.
on line 21: console.log({x});
Output: {x: 4}
Ah, ha! I’ve narrowed down the bug.
Implicit left brain. This boils down to intuitive data gathering. Instead of an explicit model, you rely on your intuitive model of the code to guide you.
Example:
Hm, foo is broken, let me look at bar, baz, and qux. Maybe something funny is going on there.
Output {bar: 0, baz: 0, qux: “flux”}. Flux!? That ain’t right, okay, let’s dig into qux.
Beginners should always use explicit right brain models since they haven’t trained their intuitions.
Experts use both.
Data gathering
Both ways of debugging require gathering data…so how?
Printf
Check out my post from a year ago below.
Debugger
Debuggers allow you to “step through programs” which is nice because:
A - You can follow the control flow as another way to gather data.
B - You can gather lots of data without rerunning the program.
However fancy, debuggers aren’t used as much because usually the data you want is easier to gather with printf than with a debugger.
Debugging Faster
There are two tricks:
1 - Run experiments faster. Measure and optimize your full experiment cycle time. If I need to run 5 experiments, how long does each take. Automate steps, use better tools, buy a faster computer, use caches (although they can cause their own bugs which will make your debugging slower), etc.
2 - Run fewer experiments. This is much, much harder.
Parting words
Debugging is a waste of time. All it does is leave you enlightened. Code that doesn’t have to be debugged is significantly faster to work on (fun fact: I almost called this blog DebugLess because of how core debugging is to fast programming). Avoid it, but if you can’t, get it over with as quickly as possible and move onto shipping code.