Debugger is your friend

Debugger has always been a big help for me. It is one of the main tools I uses in my day-to-day work, both in coding and troubleshooting. One great thing about debugger is that you can get big benefit form it just by using some simple features like step-over or step-into. I don’t quite get it why debugger hasn’t been used as much as it should be (or may be it’s just where I work that debugger is underused). I have listed my top reasons for the question of why we should utilize debugger

1. Debugger can show you what EXCATLY is happening.

This may be the most obvious reason. The ability to see the actual current value at each execution step is great. I have seen many posts on technical forum asking why a small code snippet doesn’t work like expected. Example of these kind of questions are “Why does my loop run forever?” or “Why I got NullPointerException”. The problematic program is often quite small and primitive. Well, if you are good at reading source code then you may able to trace from the output back to root cause. But if you are not, or your code just too hard to read then why don’t you run the code with debugger and see why that expression doesn’t return false so your loop run forever

Sometimes, I get frustrated with this kind of questions. The answer can be found just by using debugger. It’s more effective and faster than posting question on forum and wait for reply next day later

2. Finding the Happy Path

This is my favorite reasons for using debugger. It’s useful when I have to deal with unfamiliar modules of my project or when I am trying to get some information form third-party framework. I don’t want to fully understand everything in the code I am working with. I just need enough information that enables me to proceed. The benefits of debugger in this category are two folds.

Stepping through components according to default configuration

A program may contain many configurations. A different set of configurations may result in different implementations or different modules glued together at runtime. I may not familiar with the framework perform configuration parsing or some configurations are derived from several values. I don’t want to read all this code for starting up the program. I have a business logic that I am interested in. I just want to know that if I run the program with default configurations, what the class performing the logic is. When I see abstract factory class, I may not want to know how it retrieves the real implementation. I just want to jump right to the implementation code. With debugger, I can just step over the parts that I don’t interested in and try step-into the part that I think might lead me to the actual implementation according to the default configuration

Stepping through Happy Path
The Happy Path, in my definition, means the default path that runs successfully without unexpected thing. It is the execution path on which a program will spend most of its time.

A program contains a lot of conditional branches which dispatch execution to different paths. An execution path may be used only when a special component is present or when a program is in a specific state like suspended or when the program has been run on a specific type of runtime. Again, I don’t want to understand all the possible paths. I can just step-over each expression to see that Happy Path for a use case scenario. With debugger I can filter out a lot of information that is not directly effect the usage scenario I am interested right now.

You may think that this is not much useful because you can guess the Happy Path by just reading the code. All competent developers are good at reading source code to get the knowledge of how the system works. If you see something like:

if( throwable != null){ //….}
 
if( processor.configureWith(Interceptor.PRE_PROSESSOR ) ){ //…} 

Then it is quite easy to guess. Unfortunately, not all developers like to keep their code clean and easy to read. If the code is something like:

if( slot.length > 0&& attributedList.contains(currentSymbol) && link.nextLink != null){ //..}

You may need some time to see when each variable is in a specific state to know if this code is a part of Happy Path or it’s for another scenario of requesting.

3. Visualize code execution

Writing code needs imagination. Programmers perform some level of design in their head then translate it to source code. Reading code sometimes needs more imagination than writing it, especially when you are not the one who developed the code base. Programmers will load a chunk of code into their brains, execute it line-by-line trying to figure out what the code is doing while keeping values of all variables on the current stack trace in their mind at the same time. I think the ability of visualizing code execution is essential. When I have to deal with the code that I am not familiar with or read a complicated logic, I sometimes use debugger to help me visualize how the code works

For example, I am doing self-study on Continuation-Passing Style. I found a short and nice java example program showing who to find factorial value of a number using CPS

public class Factorial {
    public static void main(String[] args) {
        int n = 3;
        int factorial = faci(n,
                new Cont() {
                    public int k(int v) {
                        return v;
                    }
                });
        System.out.println(factorial);
    }
 
    static int faci(int n, Cont cont) {
        while (n != 0) {
            cont = new FacCont(n, cont);
            n--;
        }
        return cont.k(1);
    }
}
 
interface Cont {
    int k(int v);
}
 
class FacCont implements Cont {
 
    private final int n;
    private final Cont cont;
 
    public FacCont(int n, Cont cont) {
        this.n = n;
        this.cont = cont;
 
    }
 
    public int k(int v) {
        return cont.k(n * v);
    }
}

A bright and fast-brain developer may read the example and able to figure everything out in instantly. Sadly, I am not that kind of developers. I used debugger to see the actual value in each step to help me understand it faster. I have stepped into faci() method and went through executions until I went out of while loop. What I saw at this point was a chain of FacCont object

The FactCont instance #64 contained variable n with value 1 and a reference to FactCont instance #63

The FactCont instance #63 contained variable n with value 2 and a reference to FactCont instance #62

It went on like this through the chain. Then I stepped into the first call of k() method ( at the line > return cont.k(1); )

1*1 = 1 so the value 1 had been sent to the next FactCont in the chain.

2*1 = 2 so the value 2 had been sent to the next FactCont in the chain.

3*2 = 6 so the value 6 had been sent to the next FactCont in the chain.

Now, I knew what was going on. FactCont encapsulates the continuation; the next execution that need to be done. In the build-up code in while-loop, a continuation has been passed to another continuation to form a series of execution

4 Cross Check some facts with my brain

Human brain is incredible. Sometimes, it’s effective and innovative. Sometimes’ it’s so stupid and un-reliable. Have you ever had a moment that you spent a large amount of time finding simple little ridiculous thing that caused a bug in your system. In a good day that your mind is quite clear, you may need just 5 minute to spot it out. But in the day before release date or the day you just hear from the manager that your bonus may not be what you have expected then you may look at the root cause 10 times and still don’t be able to figure it out

This brain dysfunction can occur even with a few line of code. I remember a time when I spent half an hour trying to figure out what went wrong in a JUnit test method with 10 line of code. Apparently, I have performed assertion on the wrong object

Debugger doesn’t directly solve this problem. I use it as a tool to crosscheck that the things in my mind is the same as the things that are actually happening in the execution. If I have spent some time unsuccessfully finding some thing in my code then I may switch to run it with debugger and see if my brain has play a trick with me or not

Those are all my top reasons for using debugger. I am sure there are more useful usage scenarios of it. I think most competent developers are making a heavy use of it already so I will just encourage those novices to give it a try. Debugger is your friend