It was a judgment day. I had been working on a feature for the last month. That afternoon, my team was to present the work to the CEO.
I felt fairly confident. The day before, a tester reported a few minor bugs — some obvious things to fix during my morning coffee.
One task was to show the companies' logos in a table. Fair enough. But it was weird. I felt like I had fixed it earlier. I even wondered how I could mess up simple logic.
Well, the only one who doesn’t make mistakes is the one who does nothing. I removed one exclamation mark in the code and believed the job to be done.
One-and-a-half hours later, I had touched 14 files and changed 250 code lines. I prayed for the fix to work.
Debugging Is Hard
When we started to create computer programs, we instantly noticed we were messing it up all the time. To make it worse, it wasn’t easy to fix our mistakes. As Brian Kernighan put it:
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
So why is debugging so hard? It all comes down to the complexity.
Even the simplest apps are built on a huge code foundation. Modern developers get many tools for free — tools they don’t really understand. We’re able to use them, but we don’t know how they work.
Complexity hits us hard, especially when we get a sloppy error message or a complicated stack trace to untangle. Often, the space in the code between the error and its symptoms is huge.
Our systems are complex, but our inputs can also cause trouble. You’ll often encounter a situation where the end-user has some bug, but you can’t see it on your machine. The bug may be configuration-related or depend on the browser version or sequence of events. Some bugs work in teams and blur the picture even more.
Also, one error can create various bugs. To make matters worse, fixing a mistake can often produce new ones. It usually indicates a lack of tests or sloppy fixes implemented without understanding the code.
How To Debug Better
We all acknowledge that debugging is hard, but how can we make it easier?
Finding the bug
To learn debugging, we need to understand one thing: It’s not about adding or deleting the code or logging everything. The essential part of debugging has nothing to do with typing. It has everything to do with thinking.
Before you begin any debugging, you need to think and analyze your code. What do you think went wrong? Whatever the bug may be, after just a few minutes of deep thinking, you should have a hypothesis. Maybe even a few. What’s the next step?
Test your hypotheses starting from the most probable one. Testing may consist of changing the inputs (e.g. different file size or format to the upload), using a different browser, or commenting out code parts you consider OK. The goal is to narrow down the bug as much as possible.
Now you should be fairly confident about the bug’s location. If you’re not, there’s an additional technique that can help you: Divide and conquer.
I wrote an article on that technique, but its essence is simple. You cut your code base in half and use logs to check if the bug is in the first or the second half. Now cut the buggy part in half again. Repeat the process until you narrow down the bug to just a few code lines.
Fixing the bug
You’ve found the bug. Now it’s time to fix it. Resist the urge to get coding immediately. As always with debugging, thinking is more important than doing.
Do you understand the inputs and outputs and the app’s flow? To really know why the error happened, you have to adjust your mental model of the code to reality.
Sometimes a quick fix is possible, but maybe you don’t fully understand what’s happening. Find out all the cases where the code fails. Try testing it with random inputs. Does it behave as expected?
After the fix, try all of the cases (both successful and unsuccessful) again. Do you get it now? If you fixed the problem and still are not sure what failed, your job is not done. Tinker around until you fully understand what is happening and when. Otherwise, you’ll stumble on that bug again — maybe in a different place — and you still won’t know what’s going on.
You may be wondering, “Did the fix work?” Yes! I managed to make it work and the presentation went fine.
A misunderstanding of the inputs caused my bug. I worked on massive objects returned from the database and had the wrong perception of some properties. The bug seemed like a simple error in boolean logic, but it was a huge misunderstanding of the system.
The incident reminded me to not take anything for granted. Programming is hard. Debugging is hard. Sometimes seemingly simple bugs can help you discover a major flaw in your mental model. Learn from it.
Do you have some debugging failures? Feel free to share in the comments!