Once your code compiles, you will often get "run-time" errors. These errors (also called "exceptions") halt the execution of your program. There are fewer possible run-time errors than there are compile-time errors, but the procedure for debugging them is not always clear. This page suggests a general method of debugging any run-time errors you encounter.
Debugging run-time errors in your code can be an extremely time-consuming process. However, there are a few points that you should keep in mind when attempting to determine where the problem is in your code.
The only time the flow of code execution will "jump" to another place is when
you instruct it to do so. A loop, an if
statement, or calling
another method will change cause "out of order" execution in that Java will
execute something other than the next statement in top-to-bottom order.
For example, consider the following code:
01 numPeople++; 02 numRegistered++; 03 int age = person.getAge(); 04 if( age >= 16 ) { 05 person.setDrive(true); 06 } else { 07 person.setDrive(false); 08 } 09 ...
The important thing to recognize here is that Java will execute lines 1 through
3 in that order, but will not execute line 4 directly after line 3.
Rather, because line 3 calls another method: Java will execute the first
line of the getAge()
method immediately upon reaching line 3
in the above code. When the getAge()
method finishes, then execution
will be returned to the code shown above: execution of line 3 will be completed,
storing the value rturned by setDrive(...) into the variable age, and execution
will continue on to line 4.
Similarly, when Java evaluates the if
clause at line 4, either line
5 or line 7 will be executed next, followed by line 9. Understanding
the order in which lines of code are executed is crucial to understanding
what a program does, and hence to debugging a program that doesn't do what
it should.
When a run-time error occurs, DrJava will tell you the line that was executing when the program crashed. That line, and only that line, is responsible for causing the run-time error to occur.
Although the execution of one specific line is what caused your program to stop running, this line is not necessarily incorrect. For example, say we get a NullPointerException on the following line in our code:
Coordinate c = room.getClick();
Although this line caused the program to halt, it's actually correct - this is how you get a board position. The problem is that the variable room has, elsewhere in the program, been set to null. In other words, the specific line that is the cause of the error may not necessarily be the reason for the error; code elsewhere in your program may need fixing instead.
Making assumptions about which values certain variables hold during the execution of your program can lead to several problems. For instance, consider the following method:
01 public double getCourseAverage() 02 { 03 int total = 0; 04 for( int i=0; i<grades.length; i++ ) 05 { 06 total += grades[i]; 07 } 08 09 int n = getNumberOfStudents(); 10 double avg = total / n; 11 12 return avg; 13 }
When we look at this method, we expect that it will work because we assume
that grades
holds all of the students' correct marks and
we also assume that the method getNumberOfStudents()
will correctly
return the number of students in the course. However, upon running the program,
we may find that the getNumberOfStudents()
method returns 0,
causing a "Division by Zero" error. Or we may find that the grades
array
has not been filled with values, has been filled incorrectly, or has a length
greater than what we expected.
In short, don't assume anything about your code during runtime, no matter how trivial. Often, the thing you don't expect may actually be the reason for the error. In fact, that's usually the case - if you had expected it, you would have written different code!
The next section outlines steps you should take in order to find bugs in your
code that are the source of run-time
errors.
How to Debug Your Code
Reading the previous section, it would seem that there may be errors anywhere in your code, even where you expect them the least, and that they are very hard to find. This is partially true: errors can be in many different places and can take a lot of time to fix, but fortunately there is a very logical way of going about doing this.
Keeping the above facts in mind, the following lays out a series of three important steps you should take every time you get a run-time error. Following these steps, you will always be able to find the bugs in your code (even though some may take a lot longer than others).
As mentioned in the previous section, one line is going to be the reason why your program stopped running. Different development environments (e.g. Dr. Java, JBuilder, JCreator) will most likely report errors in a slightly different format, but the the error messages you receive in the console output will all contain the same basic information:
You should pay special attention to this information and use it as a starting
point for debugging your code. The following example shows a sample error
message.
Code | What shows up in the interaction pane |
This code's |
Upon execution, we receive this message in the interaction pane. Line (b) tells us the type of the error: a null pointer exception. Then line (c) tells us which method of which class caused the error, along
with the line number of the statement within that method that caused the
error - in this case, line 26 in the Line (d) tells us that line 20 in the file BoardGoneMissing.java contains the statement from which the method shown in line (c) was called. And so on, all the way back to (Lines (g) through (j) tell us what methods were called by the Java runtime system in reaching execution of main(...), which are of no use to us.) In this case, it's easy to spot the assignment of null to room that resulted in the exception. In real life, it may be more difficult - room might have been set to null by a method called from outsideTheRoom() before calling insideTheRoom(..), for example. |
01: public class BoardGoneMissing { 02: 03: public static void main( String[] args ) 04: { 05: BoardGoneMissing program = new BoardGoneMissing(); 06: program.run(); 07: } 08: 09: public void run() { 10: // ...omitted code... 11: Board room = new Board(12,12); 12: // ...omitted code, that doesn't mention room 13: outsideTheRoom( room ); 14: } 15: 16: public void outsideTheRoom( Board room ) { 17: // ...omitted code... 18: room = null; 19: // ...more omitted code 20: double howFarFromHome = insideTheRoom( room ); 21: } 22: 23: public double insideTheRoom( Board room ) 24: { 25: room = null; 26: Coordinate c = room.getClick(); 27: return Math.sqrt( c.getRow()*c.getRow() 28: + c.getCol()*c.getCol() ); 29: } 30: 31: } |
(a) > java BoardGoneMissing (b) NullPointerException: (c) at BoardGoneMissing.insideTheRoom(BoardGoneMissing.java:26) (d) at BoardGoneMissing.outsideTheRoom(BoardGoneMissing.java:20) (e) at BoardGoneMissing.run(BoardGoneMissing.java:13) (f) at BoardGoneMissing.main(BoardGoneMissing.java:6) (g) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) (h) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) (i) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) (j) at java.lang.reflect.Method.invoke(Method.java:585) (k) > |
Using this information, we can "trace through" the order of execution in our
code, which will lead us to the error.
Using the error message we receive from the console, start at the line which caused the error and trace backwards through the method calls that reached that line, asking yourself questions about your code as you go along. As a simple example, let's use the code example in step 1:
c
and room
.
Room
is the only object on which I am calling a method (in this
case, the getClick()
method),
so it must be the value of room
that's null.
room
to be null. Why doesn't it properly refer to a Board
object?
room
after
I set it to null on line 18. (Or perhaps it shouldn't have been set
to null ... its depends on what the program is actually intended to do,
and that's not obvious because of the omitted code.)
After creating another board object and assigning it to room, you should re-compile and run your code again to test whether you've actually fixed the bug. And of course you may run into other errrs...
Certainly, the types of questions you ask yourself will change with your programming experience. This works for some errors, but not for all of them. If your code is too complicated for you to just be able to "see" the problem (and this is usually the case), then the next step often helps.
DrJava, like most Interactive Developent environments (IDEs), allows you to step through your code one statement at a time. That is, after each statement is executed, execution is suspended until you click a button. This enables you to follow the sequence of statements actually executed, which may not be what you expected ... which may be your problem. In DrJava, you accomplish tracing by
Of course, if you already partially localized your bug, you can put your initial breakpoint at the beginning of the code you believe contains your bug, and start debugging from there.
While you're in debug mode, DrJava can "watch" variables for you: type a variable name in the "name" cell on the first unused line of the Watch pane, and DrJava will display its value for you. Even better, DrJava will update those values for you automatically as you step from line to line via the Step Into or Step Over buttons.
As you do this, the Stack pane will show you the chain of method calls by which the program reached the current line of execution from main(...).
Moreover, while the program is paused, you can type commands into the interaction pane and they'll be executed. You can call methods, such as those that return a value from inside an object visible from the statement at which execution has been suspended.
More information on using Debug Mode in DrJava can be found in DrJava's Help menu.
Another approach to isolating the problem is use System.out.println()
to
print out the value of variables as your program executes.
The example below is identical to the one from step 1, except I have included output to the console (in bold).
Code | Console Output |
We execute this code's |
The console output from running this code. We should take note of the following things:
|
01 public class Test 02 { 03 private Person p; 04 05 public static void main( String[] args ) 06 { 07 System.out.println("Creating a Test object"); 08 Test t = new Test(); 09 System.out.println("Done program"); 10 } 11 12 public Test() 13 { 14 System.out.println("Inside Test constructor"); 15 System.out.println("p = " + p); 16 String personName = p.getName(); 17 System.out.println("personName = " + personName); 18 int nameLength = personName.length(); 19 System.out.println(nameLength); 20 } 21 } |
(a) > java Test (b) Creating a Test object (c) Inside Test constructor (d) p = null (e) NullPointerException: (f) at Test. |
As you can see, printing out our own messages to the console would have revealed the source of the problem to us immediately. For more complex and involved code with several different classes/methods interacting with one another, visually displaying this information is very useful.
In general, consider including System.out.println()
statements in the following helpful places:
System.out.println("Method xxx of class yyy called.")
).
System.out.println("Method xxx of class yyy completed.")
).
if
statements to ensure that they are being entered (or are repeating an appropriate number of times).
A good general rule is "never delete these System.out.println(...) statements." Instead, declare a boolean variable - eg debug - and execute the print statements only when the value of that boolean is true. In this way you can turn debugging output on and off as needed.
public class Something { private boolean debug = true; ... public int someMethod(...) { ... if( debug ) System.out.println(...); ... } ... }
Difficult bugs require methodical analysis and testing.
Created by Terry Anderson (tanderso at uwaterloo dot ca) and adapted for CS 133 by JCBeatty. Last modified on 24 Sep 2006.