“Can you take a look into this bug?”
A question we hear a lot as developers. We spend a lot of time solving software errors that come up through the cracks.
I learned to debug properly by observing other developers I admire in my company. If you work in a large team, looking over someone’s shoulder isn’t always achievable — especially with large projects being rolled out all the time.
So, here’s what I learned from the people I respect here at Raygun about solving software errors. Their methods helped me to solve errors both faster and safer, so feel free to steal them!
Just a note on the order of the stages: The order of the debugging stages as they are laid out in this article are not strictly fixed. Sometimes, the stages blend together, and other times, I know what the best solution is straight away. However, the stages are ordered in the general precedence of one another.
- Get into the mindset.
So, where do I begin?
Get in the Right Mindset for Solving Software Errors First
Why does having the right mindset matter? Approaching problems in a calm manner helps me to not let important information slip by unnoticed. Solving software errors out of panic because of a looming deadline puts me in a position where I’m more likely to make mistakes. Those mistakes could be costly not only in time but I could also be introducing more bugs than I’m fixing.
“The oldest and strongest emotion of mankind is fear, and the oldest and strongest kind of fear is the fear of the unknown.” — H. P. Lovecraft
Software errors can have a lot of unknowns about them, like — why is this bug happening? Why wasn’t this fixed before release?
Why here? Why now?!?!
Sometimes, we just don’t know the answer straight away. This can stir up a lot of fear and anxiety amongst developers and management. The quickest remedy to this is arming yourself with the right information.
Everyone manages stress differently. I find being productive even when stressed comes down to relying on my established routines and taking it one step at a time.
So, what’s my next step?
Prioritize Which Bug to Fix First
Fortunately, not all bugs are created equal. When I am presented with many bugs, I prioritize them first. The best way is weighing each bug against the likelihood of it occurring versus the consequence of it occurring.
This risk assessment matrix helps me to come up with a list of bugs ordered by priority — but what about solving software errors that share the same priority according to the risk matrix? Well, I then ask myself some important questions:
- Given the time and understanding I have, am I the best person to tackle this bug?
- Are there bugs on the list that causing others on the list?
- Does resolving a particular bug downgrade the severity of others?
Answering these questions will help me determine which bug has the highest priority for me. A crash reporting tool like Raygun can help with features such as clickable error tags, which allow you to find out the volume of errors coming from specified criteria.
Next up is to diagnose the issue.
Diagnose Where the Bug Is (and Is Not)
Find out which part of the application the bug appears in.
The true aim of this stage is to know the smallest amount of steps it takes to make the bug appear reliably. Knowing how to make the bug appear reliably is important, so I can then be sure my fix has resolved the problem later.
“Successful people ask better questions, and as a result, they get better answers.” — Tony Robbins
Asking the right questions helps me to narrow down where the problem is and where to start. Generally, I keep a text editor open on another screen and type in questions and answers as they come to me. The questions will depend on the bug I’m experiencing, but generally, there are two basic questions I have to answer.
1. When Does It Happen?
When using crash reporting software like Raygun, I will usually have a stack trace associated with the bug. Using this and any other associated information, I try to determine what specific user interactions occurred before this bug appeared:
Raygun shows you exactly which line of code the error occurred on with a stack trace and provides you with other valuable information.
Knowing this then helps me to answer the second question.
2. Can I Reproduce the Bug Over and Over Again?
I need to understand if this bug requires the application to be in a specific state for it to appear. Many times, bugs are just edge cases that we have not accounted for, so it’s important to know what data is passing through your application during that moment in time.
Finally, what I’m looking for is a place where I can start testing from and then have the bug appear. I want the time between starting and having the bug appear to be as small as possible.
Using the information I have, I identify classes and methods that relate to the feature in my application the bug appears in. I can now use debugging tools to add breakpoints and pause the execution of my application as close to the bug as I can.
Once I have the bug cornered, I can move on to the next stage.
Highlight the Problem
The ultimate aim of this stage is to find the line of code that is causing the error. When I find the problem, I need to have enough information to understand why the problem is occurring. Having enough information will also help me to understand the scope of the fix needed.
Optimistically I want to be in a position where I can step over that line of code in my development environment and see the problem occur. If I am unable to do this, then I will log out which methods in which classes are being called.
When debugging applications, sometimes log statements can be lost in the noise of system events. A way I make it easier to find the information I want is to prefix my log statements with a unique identifier. To do this, I will often use my initials followed by the pipe symbol. A quick "find all" will help me copy all of my log statements into a new text file for me to review — for example:
Console.WriteLine(“MD | Method called”).
When I have managed to narrow down the bug to a particular class or method, I will take some time to understand what responsibilities that class or method has. If the code is undocumented, I will usually write comments in plain English above portions of code to help me understand what it was intending to accomplish.
For larger portions of code, I may also end up doing a quick UML diagram to see the key dependencies and relationships between classes. UML diagrams become even more useful when I am trying to come up with a solution that involves changing multiple classes and their responsibilities.
This stage of highlighting the problem is the one I tend to dwell on for too long. Pay attention to how much time you are spending gathering information and understanding it. You may already have enough information to come up with a solution, so swiftly move onto the next step.
Creating the Solution
Each bug will have its own unique solution. The information I have gathered should show me what the best fix will be. If the solution is not apparent, my first step is to use the information I do have and google it. A lot of the time, bugs cause errors that many other developers have seen and fixed before.
(We go into more detail around what you software team should be measuring around errors here.)
There is a need with software errors to balance out the time I have with the scope of the fix. I use the information I have to determine what is appropriate. If I am really unsure of how to fix the problem, I find it’s best that I present the information I do have to another developer and ask for their advice.
In conclusion, these are the stages I go through when solving software errors in my applications. The tips and habits I’ve talked about help me to reduce the stress and the time it takes to solve the trickiest of bugs. I hope they can also be of use to you too.