This HTML version of Think Java is provided for convenience, but it is not the best format of the book. In particular, some of the symbols are not rendered correctly.
Appendix C Debugging
Although there are debugging suggestions throughout the book, we thought it would be useful to organize them in an appendix. If you are having a hard time debugging, you might want to review this appendix from time to time.
The best debugging strategy depends on what kind of error you have:
The following sections are organized by error type; some techniques are useful for more than one type.
C.1 Compile-time errors
The best kind of debugging is the kind you don’t have to do because you avoid making errors in the first place. Incremental development, which we presented in Section 6.2, can help. The key is to start with a working program and add small amounts of code at a time. When there is an error, you will have a pretty good idea where it is.
Nevertheless, you might find yourself in one of the following situations. For each situation, we have some suggestions about how to proceed.
The compiler is spewing error messages.
If the compiler reports 100 error messages, that doesn’t mean there are 100 errors in your program. When the compiler encounters an error, it often gets thrown off track for a while. It tries to recover and pick up again after the first error, but sometimes it reports spurious errors.
Only the first error message is truly reliable. We suggest that you only fix one error at a time, and then recompile the program. You may find that one semicolon or brace “fixes” 100 errors.
I’m getting a weird compiler message, and it won’t go away.
First of all, read the error message carefully. It may be written in terse jargon, but often there is a carefully hidden kernel of information.
If nothing else, the message will tell you where in the program the problem occurred. Actually, it tells you where the compiler was when it noticed a problem, which is not necessarily where the error is. Use the information the compiler gives you as a guideline, but if you don’t see an error where the compiler is pointing, broaden the search.
Generally the error will be prior to the location of the error message, but there are cases where it will be somewhere else entirely. For example, if you get an error message at a method invocation, the actual error may be in the method definition itself.
If you don’t find the error quickly, take a breath and look more broadly at the entire program. Make sure the program is indented properly; that makes it easier to spot syntax errors.
Now, start looking for common syntax errors:
If nothing works, move on to the next section...
I can’t get my program to compile no matter what I do.
If the compiler says there is an error and you don’t see it, that might be because you and the compiler are not looking at the same code. Check your development environment to make sure the program you are editing is the program the compiler is compiling.
This situation is often the result of having multiple copies of the same program. You might be editing one version of the file, but compiling a different version.
If you are not sure, try putting an obvious and deliberate syntax error right at the beginning of the program. Now compile again. If the compiler doesn’t find the new error, there is probably something wrong with the way you set up the development environment.
If you have examined the code thoroughly, and you are sure the compiler is compiling the right source file, it is time for desperate measures: debugging by bisection.
This process is ugly, but it goes faster than you might think, and it is very reliable. It works for other programming languages too!
I did what the compiler told me to do, but it still doesn’t work.
Some error messages come with tidbits of advice, like “class Golfer must be declared abstract.
It does not define int compareTo(java.lang.Object) from interface java.lang.Comparable.”
It sounds like the compiler is telling you to declare
Fortunately, the compiler is wrong.
The solution in this case is to make sure
Don’t let the compiler lead you by the nose. Error messages give you evidence that something is wrong, but the remedies they suggest are unreliable.
C.2 Run-time errors
It’s not always clear what causes a run-time error, but you can often figure things out by adding print statements to your program.
My program hangs.
If a program stops and seems to be doing nothing, we say it is “hanging”. Often that means it is caught in an infinite loop or an infinite recursion.
If you think you have an infinite loop and you know which loop it is, add a print statement at the end of the loop that displays the values of the variables in the condition, and the value of the condition.
Now when you run the program you see three lines of output for each time through the loop.
The last time through the loop, the condition should be
Most of the time, an infinite recursion will cause the program to throw a
If you know which method is causing an infinite recursion, check that there is a base case. There should be some condition that makes the method return without making a recursive invocation. If not, you need to rethink the algorithm and identify a base case.
If there is a base case, but the program doesn’t seem to be reaching it, add a print statement at the beginning of the method that displays the parameters. Now when you run the program you see a few lines of output every time the method is invoked, and you can see the values of the parameters. If the parameters are not moving toward the base case, you might see why not.
Flow of execution
If you are not sure how the flow of execution is moving through your program, add print statements to the beginning of each method with a message like “entering method foo”, where
You can also display the arguments each method receives. When you run the program, check whether the values are reasonable, and check for one of the most common errors – providing arguments in the wrong order.
When I run the program I get an exception.
When an exception occurs, Java displays a message that includes the name of the exception, the line of the program where the exception occurred, and a “stack trace”. The stack trace includes the method that was running, the method that invoked it, the method that invoked that one, and so on.
The first step is to examine the place in the program where the error occurred and see if you can figure out what happened.
I added so many print statements I get inundated with output.
One of the problems with using print statements for debugging is that you can end up buried in output. There are two ways to proceed: either simplify the output, or simplify the program.
To simplify the output, you can remove or comment out print statements that aren’t helping, or combine them, or format the output so it is easier to understand. As you develop a program, you should write code to generate concise, informative traces of what the program is doing.
To simplify the program, scale down the problem the program is working on. For example, if you are sorting an array, sort a small array. If the program takes input from the user, give it the simplest input that causes the error.
Also, clean up the code. Remove unnecessary or experimental parts, and reorganize the program to make it easier to read. For example, if you suspect that the error is in a deeply-nested part of the program, rewrite that part with a simpler structure. If you suspect a large method, split it into smaller methods and test them separately.
The process of finding the minimal test case often leads you to the bug. For example, if you find that a program works when the array has an even number of elements, but not when it has an odd number, that gives you a clue about what is going on.
Reorganizing the program can help you find subtle bugs. If you make a change that you think doesn’t affect the program, and it does, that can tip you off.
C.3 Logic errors
My program doesn’t work.
Logic errors are hard to find because the compiler and interpreter provide no information about what is wrong. Only you know what the program is supposed to do, and only you know that it isn’t doing it.
The first step is to make a connection between the code and the behavior you get. You need a hypothesis about what the program is actually doing. Here are some questions to ask yourself:
To program, you need a mental model of what your code does. If it doesn’t do what you expect, the problem might not actually be the program; it might be in your head.
The best way to correct your mental model is to break the program into components (usually the classes and methods) and test them independently. Once you find the discrepancy between your model and reality, you can solve the problem.
Here are some common logic errors to check for:
I’ve got a big hairy expression and it doesn’t do what I expect.
Writing complex expressions is fine as long as they are readable, but they can be hard to debug. It is often a good idea to break a complex expression into a series of assignments to temporary variables.
This example can be rewritten as:
The second version is easier to read, partly because the variable names provide additional documentation. It’s also easier to debug, because you can check the types of the temporary variables and display their values.
Another problem that can occur with big expressions is that the order of operations may not be what you expect. For example, to evaluate x/2 π, you might write:
That is not correct, because multiplication and division have the same precedence, and they are evaluated from left to right. This code computes x/2π.
If you are not sure of the order of operations, check the documentation, or use parentheses to make it explicit.
This version is correct, and more readable for other people who haven’t memorized the order of operations.
My method doesn’t return what I expect.
If you have a return statement with a complex expression, you don’t have a chance to display the value before returning.
Instead of writing everything in one statement, use temporary variables:
Now you have the opportunity to display any of the intermediate variables before returning.
And by reusing
My print statement isn’t doing anything.
If you use the
I’m really, really stuck and I need help.
First, get away from the computer for a few minutes. Computers emit waves that affect the brain, causing the following symptoms:
If you suffer from any of these symptoms, get up and go for a walk. When you are calm, think about the program. What is it doing? What are possible causes of that behavior? When was the last time you had a working program, and what did you do next?
Sometimes it just takes time to find a bug. People often find bugs when they let their mind wander. Good places to find bugs are buses, showers, and bed.
No, I really need help.
It happens. Even the best programmers get stuck. Sometimes you need a another pair of eyes.
Before you bring someone else in, make sure you have tried the techniques described in this appendix.
Your program should be as simple as possible, and you should be working on the smallest input that causes the error. You should have print statements in the appropriate places (and the output they produce should be comprehensible). You should understand the problem well enough to describe it concisely.
When you bring someone in to help, give them the information they need:
By the time you explain the problem to someone, you might see the answer. This phenomenon is so common that some people recommend a debugging technique called “rubber ducking”. Here’s how it works:
We’re not kidding, it works! See https://en.wikipedia.org/wiki/Rubber_duck_debugging.
I found the bug!
When you find the bug, it is usually obvious how to fix it. But not always. Sometimes what seems to be a bug is really an indication that you don’t understand the program, or there is an error in your algorithm. In these cases, you might have to rethink the algorithm, or adjust your mental model. Take some time away from the computer to think, work through test cases by hand, or draw diagrams to represent the computation.
After you fix the bug, don’t just start in making new errors. Take a minute to think about what kind of bug it was, why you made the error, how the error manifested itself, and what you could have done to find it faster. Next time you see something similar, you will be able to find the bug more quickly. Or even better, you will learn to avoid that type of bug for good.
Are you using one of our books in a class?We'd like to know about it. Please consider filling out this short survey.