Bugs are inevitable in any code base. Learning to love the process of debugging and having your own Swiss Army of tools to aid you in that process would make you multiple times more efficient at it. Below are 25 debugging techniques that help me see sense in the chaos.
1. Try to find a consistent step to reproduce the bug: This is a very important step to squash any bug. Once we determine the steps to reproduce a bug, we should try reducing the number of steps to the most minimal as possible to narrow down the scope of the problem. Once this is done, we can step through the code-base corresponding to those steps and analyze it. Additionally, once you determine a fix for the bug, you have solid scenarios to ensure that the bug is indeed fixed.
2. Unit test to reproduce a bug: When we know that the code fails to work as expected for a particular scenario, write a unit test to confirm the failure and go about debugging. By reducing a problem to a failing test case, we figure out a nice starting point to work. Unit tests also double as a measure to prevent regressions for the same test case in the future.
3. Rubber Duck Strategy: "A well-understood problem is half done" - This is my favorite strategy while debugging or any problem in general. Thinking out loud about the bug and explaining it in simple terms to a co-worker or to yourself helps uncover some false assumptions / blind spots.
4. Add logs: This is a simple and widely used debugging strategy and I have found some of the most senior Engineers adopt this technique to debug as well. Add a bunch of log statements that hold information about the state of the program to help you validate the correctness of code execution. In addition, modifying the code slightly to add early returns, conditional returns, etc could be used to debug edge cases as well.
5. Debugger: Invest in good debugging/developer tools to make debugging a better experience. Breakpoints, dynamic expression evaluation, analyzing heap dumps, chrome debugger, API verification tools such as Postman etc are a few examples.
6. Refer Documentation: Some people might find this old school but this is a surprisingly good tip. Go back to the documentation of APIs to confirm your assumptions.
7. Git Help: Determining commits that went in since the last time the code worked fine will help us narrow down the problem space. By combing through the git diff we can determine changes that could have potentially caused the bug. Git tools such as git bisect, git blame and git log are your friends to check for regressions, potential pull requests that might be the cause of the bug.
8. Check if it is the right app version / right version has been deployed or not: Oh boy, this is yet another popular cause of bugs. Always cross verify the version of the application that you are verifying your bug against to ensure that it is supposed to be working fine. In the service world, always cross-verify whether the changes have built through all the pipelines fine.
9. Multi-threading and concurrency issues: Do not discount potential multi-threading/concurrency issues as potential causes for bugs. They are not super evident right away and are tricky to root-cause or debug. Debuggers and logs among other things are your friends here to unravel such issues.
10. Breaking Dependency changes: Often times we come across bugs in features that have been untouched for the longest time. Worry not and double confirm if there are any downstream/dependency changes that might be the root-cause for the bug.
11. "If someone else had a problem, what would I suggest?": This is a good prompt that comes in handy when we are too hard on ourselves while debugging an issue. Often times, I have wonderful advice for my friends or peers when they are debugging an issue (in Tech or life in general); but when it comes to the self, I am not so great. I employ this strategy while raising pull requests as well - before hitting the publish button on a PR, I review it from the lens of a code-reviewer and catch a few changes.
12. Outdated comments/documentation: Documentation and code comments are prone to go outdated unless duly maintained. While coding / debugging take care to ensure that the comments in the code base/docs are still valid.
13. Talk to someone who is familiar with the code-base: This is a very straightforward and effective strategy. Nothing like talking to the author or someone who is familiar with the code base to understand the code and it's intent. A few minutes of bouncing off ideas and talking through possible root-causes would give immense clarity and promising leads for the problem.
14. Difficult to debug code indicates that the code might need refactoring: One thing to keep in mind while debugging is, when you come across code that is difficult to read and understand, that is probably an indicator that it needs refactoring. Something to have in the back of your mind to be added in ticket backlog.
15. Stack overflow/google: This is a super straightforward debugging step that all of us follow - copy paste the error log and look for answers. Did you know that there are efficient ways to go about flexing your Google-foo? Check out this article.
16. Keep an audit trail of your debugging process: As you wind down the debugging hole, it is good to have a list of steps or actions that you have already tried. In addition to providing some mental clarity for yourself, this would help set context when you explain the problem to someone else. Additionally, once you have solved the problem, you can document this in the ticket for future reference for yourself and the team.
17. When blocked, bring it up with your team: When you have been at a problem for a long time and haven't found a solution yet, definitely consider bringing it up in the scrum with a short blurb. This way, anyone who might have some familiarity with the bug would hit you up post scrum and help you out.
18. Make the code to intentionally fail: Sometimes I flip the question "Why is this code not working right" to "How could this code fail?". This mindset forces me to think through unhandled edge-cases.
19. Buddy builds and automation: Some bugs are hard to reproduce manually. Automated tests are best tools in this case. Add a bunch of debug logs and metrics in your code-base and run it through a suite of automation tests. If it is a timing issue or 1-in-n-times reproducing bug, chances are high that automation could capture them. Once done, you can analyze the logs from the test run or metrics emitted for more information.
20. Please remember to not break your production code: Special care should be taken when we are debugging systems that can potentially have actual production impact. Example: working with databases, making configuration changes in production ,etc.
21. Platform-specific issues: When you find weird bugs on applications that run on mobile phones/browsers, confirm if the bug reproduces across different platforms or if it is isolated to a specific platform. An example would be bugs on android applications that reproduce only on Samsung devices or bugs that reproduce only on the Android 8 version.
22. Feature flags: When new features are rolled out, they are put behind a feature flag. If a feature is not working as expected, be sure to check that the feature flags have been dialed up appropriately.
23. Check with Quality Assurance Engineers: Teams usually have folks who specialize in testing the product to ensure it's quality. More often than not, they would have handy tools to simulate tricky scenarios and know edge cases that you might probably be unaware of. Seek time with them and walk them through the bug.
24. Take a break and get back ❤️: Seriously, this can do wonders. I often forget this rule and chip away at a problem for hours straining my eyes and mind. Taking a break and looking at the problem with a well-rested mind is always helpful.
Following are some fantastic articles that I learnt a lot about debugging from: