2023-03-19
Debugging is a skill that every developer uses daily, but it is not something that gets talked about much. Over the years I have picked up a few techniques that help me find and fix bugs more efficiently.
This sounds obvious, but I have seen many developers (including myself in the earlier days) panic when they see a red error message and immediately start changing code without reading what the error actually says. Most error messages tell us exactly what went wrong and where. The stack trace shows the sequence of function calls that led to the error. Reading from top to bottom usually points us to the exact line where the problem started.
The simplest debugging technique is adding console.log statements to check the values of variables at different points in the code. It is not the most sophisticated method, but it works and it is fast. The key is to log meaningful information. Instead of console.log(data), something like console.log("user data after transform:", data) makes it easier to find the relevant log in the output.
The browser developer tools have a built-in debugger that lets us set breakpoints in the code. When the execution reaches a breakpoint, it pauses and we can inspect the values of all variables in scope, step through the code line by line, and see the call stack. This is more powerful than console logging because we can explore everything at that moment without having to add log statements for each variable.
The Network tab is also very useful for debugging API-related issues. We can see every request and response, check the status codes, inspect the request headers and body, and see how long each request took.
Sometimes when I am stuck on a bug, I explain the problem out loud, step by step. The act of explaining forces me to think through the logic carefully, and often I realise where the assumption is wrong before I even finish explaining. Some developers keep a rubber duck on their desk for this purpose, but talking to a colleague works just as well.
When the bug could be in a large section of code, I use a binary search approach. I add a check or log at the midpoint. If the data is correct at the midpoint, the bug is in the second half. If it is already wrong, the bug is in the first half. Then I repeat the process in the narrowed-down section. This is much faster than checking line by line from the beginning.
Before trying to fix a bug, I always make sure I can reproduce it consistently. If I cannot reproduce it, I cannot verify that my fix actually works. I try to find the minimal set of steps that trigger the bug. Sometimes the process of creating a minimal reproduction reveals the cause of the bug on its own.