DEV Community

Cover image for Debugging - you’re doing it wrong. 10 techniques to find a bug in your code
Nik Poltoratsky
Nik Poltoratsky

Posted on

Debugging - you’re doing it wrong. 10 techniques to find a bug in your code

Do you remember those long, long hours spent on debugging? When you're staring at a codebase and can't figure out what exactly went wrong? You're not alone! I think all developers struggle with debugging from time to time. That's why in the article I'm going to tell you about my favorite approaches to finding bugs in the code.

Table of contents


Google error message

I'm not going to sort those tips by usefulness but google error message is at the first place by a reason. I think, if you can't even understand an error message from the first sight, the easiest way to solve the problem is to google error message.

In most of the cases, the error message you are facing was googled before by someone else. We have a lot of beautiful places like StackOverflow and GitHub issues, where people aim to help each other. That's why you can not only find the answer but also steps to avoid such error or motivation why this error occurred in the first place. So, why not to google the error? It's the easiest way here.


Console log everything

I think it's one of the most popular ways to find a bug in the codebase - add a tenth of console.log(...) statements across the codebase, rerun the application and try to figure out what went wrong.

I like it, and from my point of view, it's the right solution for simple issues, which are already localized to a few classes. But it's always a bad idea to start seeking with console.log(...)s when you have no idea what's going on here and where that creepy bug resides. Because in that case you may miss something important not logged and you'll have to add console logs and rerun application multiple times until you find the cause of failure.


Use debugger

I don't think I have too much to say here. All developers know what debugger is and why to use it. Instead, I'm going to tell you about a small discussion that flared up the other day in the office with one of my teammates.

A few days ago, I was discussing different ways of debugging with one of my teammates (that's the reason why this article appeared). And I said that one of the most robust ways to find an issue is to use a debugger. Easy as that - you're setting breakpoints, then, performing some actions and going step by step through your codebase, observing how the state of the application changes. What can be more straightforward as that?

While my teammate introduced an exciting idea - when you're using the debugger, you are not training your analytical skills and critical thinking. The reason is that with debugger you're just staring at the window with variables and waiting for incorrect behavior. While when you're digging through the codebase without the debugger, you're more focused and trying to understand what's going on and learn something new each time you're seeking for a bug.

What do you think about debugger vs. manual investigations? Please, give me your ideas in the comments.


Problem localization

The key idea of the Problem localization method is to comment/remove a code step by step until you figure out what went wrong. It works especially useful in cases when you are writing an algorithm or some business logic a pretty long time without compiling and executing the application.

In such cases, I'm always doing something wrong, and the easiest way for me is to comment code partially and try to intercept disappeared errors. Then, repeat it until all bugs will be found and revamped.

In some cases it might be a good idea to use the binary search line approach when you comment half of the code (or remove half of the files) - then check if the error is still here. If yes repeat the same with the half of this half, if now - same with the other half.

Of course, this only works in particular cases when you are able to comment/ remove code parts still keeping it working.


Create a few tests

Ok, that's the crazy one, whereas it could be useful in some particular cases. From my sight, it works well when you have some algorithm that works incorrectly, and is too complex to write console.log's or go through it using the debugger.

In that case, it might be useful to write a few tests for it. Tests could help you localize the issue in the algorithm.

After that, when the bug is localized, and you know where to search, you may use another approach to fix it finally.


Analyze logs

Yep, I know you hate to analyze all those 10mb text files with logs. But quite often it could save you a few hours of debugging. Of course, logging has to be configured adequately first of all. Collected log files have to be kept during the appropriate amount of time. But if all conditions are met - you're lucky. It's like console logging, but you already have all console.logs on their places and can just read through actions performed on your system.

But unfortunately, quite often it's a dream... We're not always paying enough attention to logging.


Ask a friend

It's pretty obvious, but we're not doing it quite often. I think all of us have more experienced teammates at the office. Otherwise, we can find an expert across the internet. Don't be afraid, ok? People are always ready to help you!

But. You need to satisfy one requirement - check all the available sources of the information before asking somebody. If you're asking for help with something easy, which is written on the second page of the documentation, then. Well. Be ready to run away!


Git bisect

Git not only helps us to keep an application revisions history, but it also provides us with a few tools for debugging. One of those tools is a git bisect - tool for performing a binary search across your git history. It's quite useful in case you didn't work with the codebase for a while, and since your last intervention, a few hundred commits were added. And now, you encounter a bug and have no idea how and when it appeared. But you remember you didn’t have it in version 2.0.15, for instance.

In that case git bisect will help you. The idea is pretty simple, you start the debugging process with git bisect start, then, we need to mark the current version as bad version because we have a bug here - git bisect bad. After that, we have to tell git about a good working version: git bisect good 2.0.15. On that stage, setup is done, and we can start searching.

git bisect selects commit in the middle of bad-good range and checks out on it. Then, we have to check if we have that bug in this revision? If yes - run git bisect bad, if no - run git bisect good. Then, git will choose a new commit in the original bad-good range, and we have to repeat the process until a commit with the bug is found.

git bisect is a pretty powerful tool that's why a full description will be out of the format of the article, but here is a link with a good explanaition.


Talk to a rubber duck

It's one of the most potent methods of understanding what's going on in the code. The main idea is that you need to find a rubber duck, put it in front of yourself and then, explain your system to it, starting from general concepts and then going line by line. You can read more about it here. I like this idea and use it regularly, so, let me tell you a short story about how I used it the first time and even didn't notice it.

When I just started my career as the programmer, I was building an Android application which contained quite complex handwritten animations. I made one of those animations carefully, step by step, and everything was fine until I stuck.

I had an animation code for no more than 150 lines. And I couldn't find a problem why it wasn't working. I checked all the algorithms multiple times but still nothing. After staring at the screen for a few hours, I decided to ask a teammate for help.

I've started a detailed explanation of how that animation works, and in a minute, I found the issue! One of the methods implicitly converted float to int🤦‍♂️🤦‍♂️🤦‍♂️🤦‍♂️🤦‍♂️. And It took me one minute to understand it when I tried to explain it in detail to my teammate.

That time I even didn't know that's it's a method of a rubber duck. I realized that, later. But I already understood how powerful it is.


Tambourine Dancing

It's my favorite way to solve problems with the software. All you need is tambourine. You need to dance around your workstation, knocking on a tambourine. It will be a big plus if you have a tambourine with the logo of technology you're working with. For instance, I have a tambourine which helps me to solve issues with my microservices:

Java Tambourine

But it's utterly useless for front-end applications.


Conclusion

Thank you for reading this! Hopefully, you've learned something new from the article. The last thing I want to reveal here for those of you who read the material to the end is that the most powerful way to deal with bugs and issues in the codebase is to write code without bugs. Just, ask your manager. 😅

Anyway, be focused and consistent. Do your investigation step by step and use the techniques mentioned above. And you'll resolve all your bugs efficiently.

Follow me on Twitter to staying tuned and let me know if you have any particular topics you would like to hear about!

Top comments (41)

Collapse
 
hamatti profile image
Juha-Matti Santala

Great post, thanks Nikita!

Debuggers are amazing, my absolute favorite tools in my toolbox. The ability to jump into the code to examine variables, to jump back and forth in the execution and figure out where things go awry is just wonderful.

One thing I would add is making sure you can reproduce it and find the boundaries of the problem: under which circumstances does it happen. If you can't define the problem, it's much harder to start fixing it.

I've been burnt so many times by jumping into trying to fix something without first fully understanding when and how it happens. That way, you can also manually test those use cases after you have attempted to fix it.

And yes, I'm in the rubber duck wagon myself too! I have two at work (a regular one for easier problems and a Sherlock Holmes duck for the times when things get though.

Collapse
 
nikpoltoratsky profile image
Nik Poltoratsky

Hey man! Thank you for sharing your thoughts about debugger. I agree with you

Collapse
 
ssimontis profile image
Scott Simontis

I was taught as an eager Electrical Engineer that there are two rules to fixing any problem.

1) Start from a known good: You need to be certain you are using components you can trust. Start from code (or hardware) that you can prove is working properly. If HW or SW is in an unknown state, you cannot reason about it.

2) Divide the problem in half: What would it take for you to get rid of half of the test cases or possible inputs? Can you isolate a module and remove dependencies so that you don't have to worry about the interaction of components, but only the functionality of a single system?

This combo has never failed me.

Collapse
 
monfernape profile image
Usman Khalil

Cool and makes great sense. I'm mechanical engineer myself and love when paths cross

Collapse
 
shamimahossai13 profile image
Shamima Hossain

I have never used a debugger. As for me, I have learnt the most by manually tracing and grinding at the code. Yes it may be inefficient but a lot of the times you don't get an expected output due to some wrong logic. I guess manually analyzing code helps catch those.

Great read btw!

Collapse
 
simbo1905 profile image
Simon Massey • Edited

I set up a debugger to step through some open source code that I had volunteered to help out with. I was wanting to add a small feature so wanted to step through to get familiar. On the first step through the code I saw a bug that had been around a while and saw some dead code. I wasn’t looking for bugs but I was reading ahead and stepping and it suddenly did stuff that I mentally said “oh! why? that cannot be right...”.

This particular project is a bash tool to encrypt secrets in git called git-secret. No-one had run a debugger as its bash and folks don’t really thinks about debugging bash with breakpoints. Fortunately visual studio code on mac can integrate with bashdb and you can step through. So i made a video to show others on that project how to use a bashdb with vs code.

Maybe you will see something for the first time if you try stepping through code. Just a thought.

Collapse
 
lishine profile image
Pavel Ravits

Me too, never use debugger, just console.log

Collapse
 
charliechauri profile image
Carlos Echauri

Regarding to debugger vs. manual investigations, I think a good approach is to first do some manual investigation to get more context on what's going on and what could possibly be the root cause of the problem. Once you have an idea the real problem then you can totally use the debugger since it will save you some time. This is what I've done and it has worked for me 🤓

Collapse
 
markerikson profile image
Mark Erikson • Edited

Excellent post! Three other similar articles that are worth reading as well:

I will say that I've never tried the "tambourine" technique before. I'll have to keep that in mind next time I run into an issue :)

Collapse
 
nikpoltoratsky profile image
Nik Poltoratsky

Thank you for sharing those articles! I'll check them.
And yep, you have to try tambourine dancing. 😅

Collapse
 
cutiko profile image
Erick Navarro

I think the tambourine is very smart, frustration is the most common bug inducing and detouring factor; let's solve our internal issues with a nonsense ritual! As long as it is known as a ba ritual is great, in the minute it's become a hippy thing with spirituality, just move one

Collapse
 
nikpoltoratsky profile image
Nik Poltoratsky

Well said 🙃

Collapse
 
johannesvollmer profile image
Johannes Vollmer

Nice compilation! I think the most underrated debugging technique is static typing - the more things your compiler checks, the less bugs will appear.

Collapse
 
nikpoltoratsky profile image
Nik Poltoratsky

Yep, static typing can help you with reducing bugs, but I'm not sure it's a debugging technique 😅

Collapse
 
konstantinklima profile image
Konstantin Klima

I would just advise caution when doing console.log() on an object or an array objects you are tracking.
As i usually stick to backend languages, it really took me by surprise when I spent three hours basicly stripping several hundred lines of code in JS looking for a bug, just to realise that thr objects I was loging were printed out as they were at the end of execution and not in their current state for that current iteration as one would expect.
This was denoted by a little alert sign in chrome, which I checked out a bit too late. :D

An easy but boring workaround is to use JSON.stringify on the object when loging it. :)

Collapse
 
nikpoltoratsky profile image
Nik Poltoratsky

Thanks for sharing that tip. I've faced it multiple times and it still confuses me

Collapse
 
havarem profile image
André Jacques

Great advice. I like the "talk to a rubber duck!" A long time ago, my project manager was literally my rubber duck. I would talk about a problem and she would just "hmm hmmm" me for 3 minutes until I say "and... oh! wait a sec" to see everything resolve.

Furthermore, I would add this: baby steps. Either when you develop or debug, doing one thing and then make sure it works as intended will save you a lot of trouble. Sometimes it is easier said than done, you don't need to do TTD per se (but it will help you a lot). It is especially true in embedded system development when you do not necessarily have access to a console or have enough storage for log files. (Debugging with gdb on an embedded system that is time critical can be in the range of the impossible).

Collapse
 
mykezero profile image
Mykezero

Add breakpoints / console.log statements in every try/catch statement that you can find. Chances are, you may be swallowing the real problem, without even realizing it. I wish debuggers had an option to toggle this on when needed.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.