Why is debugging important in software engineering?
One aspect of software engineering that is often underestimated is the task of debugging a problem.
Lots of material online explains how to build things from scratch, but very little talks about dealing with a bug report in an existing system. In this edit, I’m going to dive into my approach to debugging and some tips and tricks that have helped me fix many different bugs.
Before fixing a bug, the problem must be understood. This is often the first mistake that can be made during debugging: assuming too early that the root cause has been identified. It’s easy to put small fixes on the issues you have, but do a lot, and it will explode in your face, figuratively speaking.
Let’s take an example: if you are working on part of the UI and there is a weird text overlap between a title and body text below, how do you fix this bug? The initial solution may be to hard code the height of the title UI element so that it does not overlap, bug fixed! Not so fast, though – because what now happens when large fonts are enabled for accessibility, the text is now cut off in the title UI element. However, you can’t just hard-code the height to be taller because the font is variable, but removing the fixed height will undo the previous issue you fixed. So now you need to fix the original bug differently to fix this new problem.
In the example above, it was easy to think, “Oh, there’s not enough height here; let me add a height constraint to help you. Without thinking a little more about other details such as accessibility, the identified solution will not work in all use cases and you will have to resolve this issue several times. However, you can fix a bug just once by taking an extra step and thinking about potential borderline cases.
What about when the problem is not that easy to identify? The overlapping text is obvious to spot; What about an issue where notifications are not received by your users, where the notification pipeline involves more than three different distributed services belonging to other teams. Well, you start by gathering information to better understand the problem.
In a complex distributed environment, debugging is like solving a complex puzzle, where you know there has to be a solution – the system worked last week! – but you can’t quite see it yet. You have to dig around in the rooms, move them around a bit, to make sense of it all. The same goes for debugging.
Especially for systems without a user interface, console logs are your best friend. Having a log record and browsing it quickly for clues is essential for quick debugging. Relatedly, however, is the need to write connects to your code! In many cases, it’s hard to know what debugging information you’ll need, however, when assembling a new service for the first time, so one of the best tips to use when debugging is to start adding new ones. logs that will help you with the particular problem you are looking for. Then after you resolve this issue, delete the logs that you clearly see as specific to this issue, but leave whatever might be useful afterwards.
In some systems there are a lot of newspapers, however. So how do I find the specific lines I’m looking for through all this mess? Add journaling with narrow prefixes so that you can search by them efficiently. My favorite technique is to prefix the logs with the class name + function they are in, as this helps me navigate through the logs as if I was digging into the code itself looking for specific functions.
Sometimes, however, debugging can get frustrating. I have not often seen that a bug can occur; my judgment clouded more and more by my anger. If this happens to you too, what has worked for me is to take a break from these situations. I’m not just talking about a 5-10 minute break either; I’m talking about taking the rest of the day, not thinking about the problem explicitly and coming back to it the next day. As a morning person this helps me refocus on an issue when I’m at my best which is usually a different mindset than after debugging for a few hours without making much progress!
A difficult aspect of debugging that is difficult to quantify is to visualize the potential reasons for a problem. It requires a thorough understanding of both the software you built and the hardware the software is running on. Some bugs may only be reproducible on specific types of hardware! For example, the high-end machine you built the software on has enough memory to handle your code, but a cheaper device is constantly experiencing memory issues. Without understanding the hardware memory model, it would be hard to think of this possibility when testing on the high end machine you built the software on! In these situations, you must be wondering: how can this problem occur and what about my setup that makes it non-reproducible for me?
A final area to cover is debugging tools, of which there are many. These can be very helpful, but do not replace all the thought required to get to the root of complex problems. A debugger that lets you step through the code line by line and print the values of various variables along the way is an invaluable tool, as long as it’s stable – you don’t want to have to debug your debugger like you’re debugging of a production problem!
Overall, debugging just requires creative thinking and persistence, and any bug can be fixed – it can take a while.