💡How to debug code: a model that always works for me and might work for you too…

Frustrating. Debugging truly feels like the most frustrating experience for a developer. At any point you encounter a bug, you feel frustration rising in your body.

Minimizing frustration is part of the “happy developer” experience if you’re the code maintainer or another user. The question becomes natural: can you make debugging code painless? This is the very same mission that we have here at Dashcam!

No one can write complicated software without bugs. To minimize the advent of bugs, a lot of developers rely on writing Unit Tests (and more complex versions of them, like Integration Tests), meanwhile some companies spend millions on hiring teams of QA Testers, but the result is always the same – eventually a bug in the code will happen, requiring a team of developers to debug it.

On the tools side, whether built-in in our IDEs or build-systems pipelines, there’s a whole movement on trying to minimize shipping code with bugs: tools to perform static analysis to eliminate most surface problems developers may miss when writing code is a good example. Companies came out of the woods showing the world tools developed for such use-cases: some famous ones like SonarQube or SnykCode – but also new programming languages supersets like TypeScript (built by Microsoft) do help with creating better software.

Fix bugs at every level.

Regardless of the tool, given enough complexity, there will be bugs. Whether at the level of your database, APIs, or anything else. It is essential to be able to fix bugs at any level you might be at. Any different tools have been created to discover issues and analyze different levels of the stack.

So, how do you find bugs?

You don’t find bugs unless you’re very good at reverse engineering software; most people will stumble upon them.

When a bug happens, when correctly reported, is going to have developers immediately try to track it down, eventually opening their code editor of choice and say something like: “I don’t understand how this code may be buggy” 🤯

Getting started debugging: take a look around

If you’re in a production environment, I would first look at error logs from your observability service of choice.

On the other hand, as a Dashcam user, if you’re running the code locally, you can read the error logs of the application (or applications) you are running. Of course – depending on your stack, you might have a lot of services running, and therefore tracking the source of the error might become a challenge.

If you’re running an application with a front end, your browser is your best friend. Firefox, Chrome, and other browsers have powerful in-built dev tools. 

In some scenarios, there are also specific tools (like React Dev Tools) that you can install to inspect components and the state of the web app and thus investigate the error like a good detective.

NB Remember to enable local dev/debug mode if you’re debugging your app locally; this is a flag that most frameworks have, which gives you a much more verbose error output!

Once you find the error, regardless of your framework, you must work back up to understand what triggered the error. That is because the error message is the outcome of the error, not the error itself!

Thinking about what caused the bug or error is a good mind model to have: “What’s causing this error? What do you expect to happen instead? When does the error happen? When would this error NOT occur?”

These are a good start – ask yourself and write down certain assumptions you know about the code, and then verify them.

Find the correct source code.

When you’re working in a complicated stack, identifying the right part of the code to debug can be challenging:

  • First and foremost, reproduce the bug. If you can reliably reproduce it, this is the first step to finding the cause of the bug in the source. Without stable reproduction, no point in attempting to pinpoint what causes the bug.
  •  Isolate more complex parts of the system. It’s easier to find a needle in a haystack if the haystack is as small as the palm of your hand
  •  Trace the error to the exact source code by breaking down your code. Similarly, you can use stack tracing or print debugging.

Find the starting point.

Without a starting point, finding the exact error message and where in the code it was outputted is a good starting point!

Sometimes it is possible to selectively comment out sections of code and test to see if the bug persists, then add assertions and validation checks that should never fail unless there is a bug to identify failure points precisely.

More often than not, error messages are caused by a problem very different from the text of the message. For example, “Upstream connect error” might mean simply that your server crashed.

It takes time to decode the real reason behind an error message – many people will struggle with this the most!

Look at the code with someone else

When debugging, it’s good to come prepared. There are a few reasons why looking at source code together with someone else can help debug software more effectively:

Having another person look at the code brings a fresh perspective. The other party may notice something you overlooked or have ideas you didn’t think of. 

Regardless of their experience level, this helps a team debug faster. The good ol’ saying of there’s no I in Team 😅

Another reason to pair-debug programming is that debugging code with someone else is more motivating: knowing they will be sharing the joy or the pain of debugging with you is a bonding experience!

Finally, when two or more people can divide debugging workload (without replicating efforts) truly speeds up debugging, especially if it doesn’t become a race between people.

Fix and reinforce

Regardless of how you debug your code, adding more error messages is always better than having the code fail silently! When fixing buggy code, think about how you can add more guard rails to it.

Specifically, an example using Python where you can use raise … from, which is used to create exception chaining – where one exception is raised “from” another exception that caused it.

try
  open("file.txt") 
except FileNotFoundError as e:
  raise CustomError("Can't open file") from e

Every language has its way of doing this!

Debugging and the future

Tools like Dashcam build solutions to help developers detect and destroy bugs before they hit production. No matter how much AI we will be using in our field of work, there will (in the short term) always be the need for developers to understand how to debug code manually.

Hopefully, this write-up brings some ideas to people looking for a mental model for debugging code! Ahhh, the things we do in the name of helping devs ship bug-free software!

Leave a Reply

%d bloggers like this: