How to be a more Efficient Debugger

Sharing is Caring

For the last 10+ years or so, I’ve been programming professionally. Even with ten years of practice, I still am unable to write a bug free applications. In this blog post, I have put together some tips on how to be a more efficient debugger and how to actually resolve the issue. A lot of my bugs aren’t as obvious as they used to be, but they are definitely still there. In the book Code Complete, Uncle Bob Martin, suggests that every error is an opportunity to learn.

Have a Positive Attitude

I believe that having a positive attitude is the most important part of being able to debug quickly and efficiently. Don’t waste time trying to blame somebody else for why the error exists and don’t bother blaming the computer or the software or framework you’re using – instead focus on the problem.

Debugging is a process

Debugging is a logical process, don’t worry about whether you have intuition. The process follows something like this:

  1. Identify the problem. Look at the high level symptoms – don’t worry about finding the exact problem yet.
  2. Diagnose the Problem – analyze what went wrong by looking at logs, the stack trace, etc.
  3. Isolate the Problem – use tests, manually reproduce.
  4. Resolve – change the code, the process, etc and resolve the error.
  5. Confirm everything is resolved. Run automated tests, manual testing, etc.

Don’t be scared to take a divide and conquer approach for resolving bugs like null exceptions and type errors. Basically this can be debugging at the start of the program, debugging a few function calls before the error, and debugging where the error actually occurs. Most likely, the error isn’t exactly where you think it is – but avoid making assumptions without spending time hypothesizing.

Make one change, then get feedback before trying lots of changes.

The bug is rarely where you think it is when you are trying to find it. Isolating where you are making changes will help you avoid causing lots of other little bugs.

Avoid Making Useless Assumptions

I’ve spent lots of hours staring at code and thinking “this should work” and missing out on an obvious error because I assumed my code was perfect or that the caller was calling my function exactly as I had expected they would.

You should make some assumptions, write them down, but don’t assume your code is correct. Don’t just randomly assume and start changing code – that will cause lots of other problems. Always go back to debugging is a process – there’s very little that is random.

Always Reproduce the Bug Before Making Any Changes

Before rushing into your debugger or reviewing the code – always spend the five or ten minutes and try and reproduce the bug. If you can’t easily reproduce the bug it will be very difficult to fix the bug, and then to subsequently prove that you have fixed the bug. With race conditions or asynchronous operations it may be nearly impossible to reproduce the bug with the first attempt.

If you can’t reproduce the bug, don’t be scared to ask for more information about what the user did or spend time looking through error logs or stack traces.

Learn the Debugger

If the environment and language you use can support a debugger, you need to spend time learning how to use the debugger. Learn how to set breakpoints, break on exceptions, pause the threads, switch threads, and log outputs. A lot of web developers and JavaScript developers don’t realize there are debuggers available. NodeJS and JavaScript do have debuggers available, JavaScript can be very easily debugged in the Chrome Developer Tools, in FireBug in Firefox or even in the Developer Tools in Edge.

  • Add watches – this lets you see the variable’s values.
  • Set breakpoints where applicable.
  • Go through the code step by step and validate input and output match what you would expect.

Use Logging to Gather Knowledge of what happened

If your environment, doesn’t provide a debugger you need to try and be logging output when there’s errors especially the stacktrace. Print statements can be used to gather information about the state of variables as the problem is running – this is a really common technique in environments like Salesforce.

Learn How to Read a Stack Trace

A stack trace is basically a report of everything that has happened and what’s available in memory at the time the software crashed. Usually stack traces are displayed as part of an error message which can be given to us. Stack traces usually contain the sequence of functions that were called up to the point when the there was an unhandled exception.

In most languages, the most important line usually starts with something like “Caused by” and then whatever the problem was. I find a lot of the time the errors are related to null variables or unexpected types.

Rubber Ducky it

Take time and talk to somebody about what the problem is and explain how it can be reproduced. In the book Pragmatic Programmer, they talk about talking to a rubber duck and explaining the code line by line to the duck. The concept is that as you teach a subject you can gather deeper understanding or benefit from somebody else’s experience solving problems.

Give yourself a Timelimit

I have spent hundreds of hours trying to resolve a problem, and not being able to and getting discouraged. I recommend setting a time limit, and if it still isn’t solved at that time go work on something else for a little bit or go get some coffee and think about the problem some more. There’s been many times I’ve resolved a problem in 5 minutes after I went and ate lunch or went and got more coffee.

Write a Test that can Cause the bug

Test Driven development is really good at preventing a lot of bugs because it can eliminate a lot of the really trivial and silly bugs like nulls, invalid types, and just other general runtime exceptions. Testing helps make smaller simpler functions which makes isolating and replicating problems a lot easier. The reason you want to write a test that fails due to the bug is that it will help you in reliably knowing that the error is fixed.

Small Functions Help with Debugging

Smaller, more modular, functions can really help in debugging. Small functions that take in a lot less parameters are a lot more predictable in what they respond with – they also tend to be much easier to test. If your code has very large functions, this is unfortunately not the time to try and make functions smaller because you could and probably will produce even more errors.

Code Quality matters

Code that is consistently styled and structured is a lot less likely to have small logical errors. If you are coding in JavaScript, I recommend looking at things like Prettier and ESLint which can easily enforce consistent coding standards across a team and codebase. Visual Studio Code has really good plugins for both which can automatically make changes and point out potential errors. For example, “=” instead of “==”.

Refactoring by Martin Fowler, Design Patterns by the Gang of Four, Code Craft by Bob Martin, and Code Complete all provide different ways to improve the quality of code. If you want to be a serious developer, you really need to take the time and read through these books and start understanding some of the concepts they present.

Sharing is Caring

Brian is a software architect and technology leader living in Niagara Falls with 13+ years of development experience. He is passionate about automation, business process re-engineering, and building a better tomorrow.

Brian is a proud father of four: two boys, and two girls and has been happily married to Crystal for more than ten years. From time to time, Brian may post about his faith, his family, and definitely about technology.