Tuesday, January 24, 2012

Debug java apps for beginners

Introduction

When posting in forums I often see people new to Java that don't know how to debug their applications. Which is a right shame as using the debugger can be an immense help to learning to program using Java. Hence I decided to write this simple article with many pictures, to be able to link to when I again run into such a case.

Of course debugging requires an IDE; this article is based on Eclipse. But the Netbeans debugger works nearly the same (they both operate on the same JVM debugging features after all).

Defining some test code


First we need something to debug. Create a regular java project in a fresh workspace, just to be sure you are in a "virgin state". Let us use some stupid code that is easy to follow. We require two classes: DebugTest and StepIntoTest.

package test;

public class DebugTest {

  private int classMember = 0;
  
  public DebugTest(){
    
    System.out.println("Breakpoint line");
    System.out.println("After breakpoint line");
  }
  
  public void myMethod(){
    System.out.println("In myMethod");
    classMember = 5;
    System.out.println("After classMember change (it is now " + classMember + ")");
  }
  
  public void stepIntoTest(){
    System.out.println("In stepIntoTest");
    
    int localvar = 0;
    StepIntoTest sit = new StepIntoTest();
    localvar = sit.stepIntoThis();
    System.out.println("localvar is now " + localvar);
  }
  
  public static void main(String[] args){
    
    DebugTest dt = new DebugTest();
    dt.myMethod();
    dt.stepIntoTest();
  }
}

package test;

public class StepIntoTest {

  public StepIntoTest(){
    System.out.println("StepIntoTest created.");
  }
  
  public int stepIntoThis(){
    System.out.println("Stepped into!");
    return 10;
  }
}

If you run DebugTest right now, you'll get the following output:

Breakpoint line
After breakpoint line
In myMethod
After classMember change (it is now 5)
In stepIntoTest
StepIntoTest created.
Stepped into!
localvar is now 10

The runtime output is not really important, what I do find important is that it will demonstrate a thing or two while we do some debugging runs.

Setting a breakpoint

The most important tool you have while debugging is the ability to set breakpoints. A breakpoint is exactly that - a break point, or a point to pause execution. Lets see that in action right now. To set a breakpoint easily, simply double click in front of the line where you want to set it. Lets set it in the DebugTest constructor, on the "breakpoint line".


A little blue dot appears in front of the line. That means a breakpoint is set on that line. Now we can run our application in debug mode. You can do that by either right clicking and then choosing debug as -> java application:




or you can do it by pressing that little bug icon.




Either way, Eclipse will run the application in debug mode. As soon as it reaches the line with the breakpoint, Eclipse will start to nag you with a question.




Eclipse will ask you to switch to the debug perspective, which you should do. In fact, I would recommend checking that remember my decision checkbox so Eclipse doesn't ask you again (for this workspace) because when debugging you really do want to work in the debug perspective.




After you click yes, the workspace will show a slightly different view with all kinds of useful information. First of all, in the source view you will see that the line with the breakpoint is now highlighted and there is a little arrow in front of that line. The output console is currently still empty. The debugger has paused the execution flow, waiting for you to do something. A visual indicator that you have hit a breakpoint is seen in the button bar:




These are the buttons that allow you to control what the debugger is to do next. I have labelled the buttons 1-4; these are the buttons we'll be exploring in this article. The remainder I hardly ever use myself, but perhaps I'll cover them eventually anyway.

The fact that button 1 & 2 are active means that the application is currently halted on a break point; if the application is running these buttons are greyed out. It may seem silly to explain this but believe me - there are going to be times when you are going to need that visual queue to understand what is going on. Eclipse isn't perfect :)


Button 1: continue execution


An important thing to note is that even though the breakpoint is on a line that does a system out, nothing is printed yet. The line on which you set the breakpoint is the line that is going to be executed next.

Lets explore the different buttons shall we. First, press the green arrow, button 1. You'll find that this makes the debugger continue execution of the program - the console now fills up with the system outs.

Lets try that again, but this time we are going to set a second breakpoint in myMethod, on the line that prints "In myMethod".




After doing that, run in debug mode again. The debugger halts on the first breakpoint. Press the green arrow (1) again. You'll see a change from the last time; in stead of completing the application, the debugger is now paused on the second breakpoint. Whatever code was between the two breakpoints is executed; you can see that in the console output.




That is exactly how you use the green button; to run from breakpoint to breakpoint until the code terminates.


Button 2: terminate prematurely

If everything is correct, the program is still paused on the second breakpoint. If it is not, make sure it is now.

At this point, press the red button (2). The result is immediate: the application terminates before finishing. When debugging new code you'll often cut the debug run short because you notice something is wrong. Stop debug, fix problem, restart debug is something you'll do often.

Button 4: step over

Skipping button 3 for the moment, lets go directly to button 4 which is the step over button. This button allows you to execute code one line at a time, a very powerful tool. You'll see just how powerful in a short moment.

At this point, lets remove the first breakpoint. There are multiple ways to do that of course, the two most useful are:

- double click the breakpoint again
- open up the breakpoints tab; from here you can remove or temporarily disable breakpoints




The breakpoints tab also allows you to remove or disable all breakpoints at once, which is quite useful after a heavy duty debugging session where you set dozens of them.

Whichever way you choose, make sure that the breakpoint in the constructor is gone. Now run the application in debug mode so it hits the breakpoint in myMethod(). As you can see, this method changes a class member. At this point, open up the variables tab. In the variables tab you can not only see the members of the current class (the one that this points too), but also all the local variables that are declared in the current method. That we'll see later.

Expand the this variable to see the classMember. Right now, the debugger will show you that it is 0.




Now press button 4, step over. You'll see that the debugger progresses to the next line and "in myMethod" is now printed to the console. classMember is at this point still 0, because as you now know the line is only executed once the 'debug cursor' moves out of it. Press step over again to move to the next line.

Bingo, the line that changes the classmember is now executed and you can check the variables view:




A thing of beauty isn't it? Debugging allows you to see exactly what is going on inside your classes! You no longer have to make assumptions about the content of class members or have to follow their progress in your head as you stare at the code, you can see first hand by stepping through your code what changes happen and why.

Lets see what happens when we continue to step over lines. Press the step over button two more times. The following will happen.




The cursor jumped from myMethod() back to the main()! Of course the debugger does not execute code linearly from the top to the bottom, the execution flow is exactly how the JVM executes it. So at the end of myMethod(), control is passed back to the main() method which will in turn invoke stepIntoTest(). You have to keep this in mind because while stepping through code the cursor will not only fly all over the place within the same source file, but also to other source files. It can get a bit disorienting if you don't keep an eye on things.

Okay, let the application finish or cut debugging short. Remove the breakpoint in myMethod() and set a new breakpoint in stepIntoTest(), on the line that prints "in stepIntoTest". Debug until that line.

This method declares a local variable, which will ultimately be changed by the stepIntoThis() method. If you check the variables tab, you'll see no variable declared there. This makes sense: the variable doesn't exist yet, it is declared two lines down. So step over until the line that constructs the StepIntoTest object.




I moved things around a bit in my IDE to be able to make this compact screenshot. Now the local variable appears in the variables view. Step over twice more to execute until after stepIntoThis() and see the variable value change.




Also note how the StepIntoTest object appears in the variables view. You can unfold it and inspect its members. Give it some properties to see how this looks in the variables view, if you are curious.


Button 3: step into


The button we skipped over, with step into we can step into a method. In the previous paragraph we stepped over the stepIntoThis() method to see the local variable change its value from 0 to 10. Now lets step into it to see which lines were executed when we stepped over the entire method.

Stop debugging if you have to, restart debugging until the breakpoint in stepIntoTest(). Step over until the cursor is on the line that invokes stepIntoThis(). In stead of pressing step over, now press the step into (3) button.




As expected, the cursor is now at the start of the stepIntoThis() method of the StepIntoTest class and you can continue stepping over lines. As soon as the method finishes, control goes back to the stepIntoTest() method of the DebugTest class. This allows you to intimately follow the flow of the code and see the current class and variable states.


Changing code on the fly

Java is a very powerful thing. One of its neat features is to, while debugging, swap in code changes into a running instance. That's right, you can make changes to your application while it is still running.

Lets take that for a spin. Run the application until the stepIntoTest() breakpoint. Step over that line so that in the console is printed "In stepIntoTest". I don't like that test. While still debugging, alter the line of code to in stead print "Hello World!".




As soon as you save the change, you'll notice that the cursor pops back to the beginning of the method! When you now step over the line again, hello world is printed in the console. Cool huh!




This hot swapping function is a really powerful debugging tool, but it will take some getting used to before you really learn to work with it. There are of course limitations to what you can do; for example changing the signature of a method cannot be hot swapped. Experiment and see what works and what doesn't, but now at least you know the possibility exists. This feature becomes increasingly powerful when you want to debug web applications by the way.

The importance of toString()

When debugging you'll learn to love the toString() method. Run until any breakpoint, then open the variables tab and click on the this variable. You'll see something like this:




That is the default output that Java generates for an object that has no toString() method of its own. Very much not helpful. Lets give the DebugTest class one:

public String toString(){
    return "DebugTest; classMember=" + classMember;
  }

Debug again and when you select the this object again, the output has changed:




Much more helpful. Just imagine when you start to debug code with people, addresses and what not. Implementing proper toString() methods can actually help you to quickly see what is going on without needing to go through the trouble of hunting through endless class members.

Debugging in a loop

Finally, an interesting element to encounter while debugging is a loop. Lets see that in action with this piece of code:

public void loopTest(){
    
    for(int i = 0; i < 5; i++){
      System.out.println("Loop " + (i+1));
    }
  }
Put a breakpoint on the println line in the loop and debug until that line (of course, make sure that loopTest() is actually invoked or else the debugger won't ever touch it). Now press the green arrow button. Loop 1. Press the green arrow button again. Loop 2. This will repeat until the code loops 5 times. So by putting a breakpoint inside a loop, the breakpoint will be triggered for as many times as the code actually loops. This may be a problem when the code loops 2000 times; you don't want to be clicking that green arrow button 2000 times. Of course you could simply disable the breakpoint, but lets say you want to see what is going on during iterator 1125 of the loop. Do you press the green button 1125 times? No, you create a conditional break point like this.
When debugging, sometimes you have to put some additional code in there just to help you narrow down problems. To illustrate, this is the output I got by running and then stepping through the code:
...
Loop 1121
Loop 1122
Loop 1123
Loop 1124
Temporary conditional breakpoint.
Loop 1125
Loop 1126
Loop 1127
Loop 1128
This is just one trick. When you become an expert debugger you'll pick up many more such tricks. Just don't forget to remove them again when you are done debugging :)

In closing


Yeah, I'm going to halt the article right here. There is much more to tell about debugging, but this article is for novice developers who want to learn how to use a debugger; lets let the more advanced features rest until you are comfortable with what you already know. Hope this helps someone.

But remember: debuggers are cool, but in some cases there is just nothing better than peppering your code with system.out statements to see what is really going on :)

1 comment:

  1. Thanks a lot!!!
    I am on the start web programming with JSF and jboss7.1 and it has match helped me!!!

    ReplyDelete