The Art of Debugging
5 min read
As software engineers, debugging is an integral part of our jobs. You may have times as a Software Engineer when you are more concerned about fixing bugs than developing novel features.
A debugging process can be divided into two categories:
Active: In this step, you investigate all methods of fixing the bug by altering the code. The next steps are developing a hypothesis as to whether the solution will work and implementing it.
Passive: For passive debugging, we use logging when the bug cannot be reproduced, so we cannot run through the execution steps.
Throughout this blog post, I'll share some of the debugging practices I adopted and have now become part of my debugging process.
It is essential to remember that if a bug exists, even if you are not experiencing it, it implies that something is definitely off with the code. This could be logic, a dependency issue, or a missed edge case.
Go Back in the History
Gathering all available historical information about the issue is the first step in debugging. This typically allows you to identify whether the problem is persistent or whether it has never been fixed. There may be a log, error note, screenshot, and perhaps some old message exchanges.
SCM also allows you to view all of the file's previous modifications. Different Git extensions in the IDE can help with this process. Going back in time and discovering even the smallest indication might occasionally inspire an idea when you are absolutely baffled as to how the bug is happening.
Read Read Read
Sometimes, half of the issues may be solved by just reading the code and tracing back all the functions.
In my experience, after locating the API Endpoint where the problem occurred, I look for the method connected to it and then attempt to visualize the flow by creating a mental model of the code.
Once you comprehend the code, you may add breakpoints (eg: binding.pry in Rails) to the code to verify your comprehension.
Talk to Yourself
When working on a complicated problem, speaking aloud to yourself might occasionally disclose blind spots.
I try putting it down in the manner of what I would advise someone to do if they were in a similar circumstance if thinking aloud doesn't help.
Simply write down everything you know about the bug so that when you see it all written out in front of you, your mind will start to fill in the blanks.
Add Logs Everywhere
One of the most popular debugging techniques is this one. It entails inserting logs into the code at various locations—basically, before and after every function call.
Adding logs can help you understand the state of a variable whose value is changing across many functions.
Play Around to Reproduce the Bug
As a result of the discovery of the defect, we can conclude that the code is flawed. In huge code bases, it is essential to reproduce problems that only appear under a certain set of circumstances.
In order to understand the set of conditions, all the data points can be gathered as you analyze the database values. Try finding a pattern across all the users for whom the error has been occurring. You can then experiment with a different set of conditions to correct the issue.
To further aid in the debugging of edge cases, the code may be significantly modified to include early returns, conditional returns, etc.
Forgetting what you know about the product is a crucial step in trying to recreate something. Typically, when we have been working on something for a very long time, we are familiar with all the tricks and know what will work and what won't. Due to this, the bug's reproduction is biassed. Consider your product from the user's perspective and create the most absurd scenario possible.
It's not always some Big Change
When you encounter a bug, don't always think of the issue to be some big code change or rewrite. Start small.
Check for null values, any before action validations, if else conditions.
When I originally started, I believed that significant modifications would be necessary, but over time, I've learned that it's better to focus on the little aspects first.
Read the Unit Tests
You can start by reviewing the unit tests for that specific piece of code if you are entirely perplexed by the various edge situations the function handles.
Talk to the Code Owner
There is a possibility that you will not always have to debug something that you have worked on. When this happens, it can be helpful to speak to someone with a broader understanding.
You can bounce your ideas for solving when working on a critical flow, if modifying something, with the working team to ensure you haven't missed any edge cases.
Have a Proper Alert & Monitoring System
Using tools like HoneyBadger, and Sentry can help you keep a log of the errors. You can add them to all your APIs & critical workflows.
Other than alerting, you can have a logging system, where you can monitor the routes that are being called in actual production.
The most crucial step is the last: once you've identified the cause of the problem, remember to record it for the future you.
Lastly, if none of the above steps work, take a break, come back and start again.
PS: Keep in mind that debugging is not magic; rather, it is a sequential process that you get better at as you do it more frequently.
Did you find this article valuable?
Support Shloka Shah by becoming a sponsor. Any amount is appreciated!