Unfortunately, as much as we would like to write perfect code, we sometimes write code that is less than perfect. When that happens, we can spend more time trying to figure out what is wrong than we actually spent writing the code itself. In order to make sure our code is working, we have to make sure it has no bugs (or at least as few bugs as possible).
How do we know that code is bug-free? One hint is that it passes all of our well-designed unit tests. How do we know that code has bugs? It fails to pass a unit test.
How do you avoid bugs? You can never completely avoid bugs. Every programmer makes mistakes, fails to predict certain situations, or misunderstands how a language or library works. But you can still work on strategies that help you avoid bugs in the first place.
If your code still ends up failing the tests, what do you do next? Sometimes just walking through the procedure again helps. (A student claims the most valuable lesson he ever got from me was “Explain your code to someone, even your golden retriever”. That is, if you sit down and explain what you’re trying to do and how you acheive it, you’re likely to find the bug.)
More often, though, you’ll need to see what’s happening while your code runs, as what’s happening is clearly different than what you expect. You may be tempted to insert a bunch of print statements, but you are much better off using a debugger. And, fortunately, VSCode, like most IDEs, has an integrated debugger (at least after you install the Java debugger plugin).
Debuggers are tools for tracing code to find if there is something wrong and what that something is. More precisely, they give you the opportunity to find out where in your code things start going wrong. How do they achieve this? First, they let you single-step through your code, printing values as you go. They also let you set breakpoints so that your steps can be large. Using a debugger will be more efficient and informative than simply putting a bunch of print statements in your code to trace what is going on.
What capabilities can you expect to find in most debuggers?
Many programmers debug by inserting print statements throughout there code. While there are some situations in which this strategy is appropriate, most experienced programmers prefer to use a debugger.
The preceding list of capabilities may suggest many reasons that debuggers are better than just inserting some print statements. Here are some other reasons why debugging with print statements is not the best choice.
Why not use a debugger instead?
It’s much easier to understand the VSCode debugger in practice than by reading about it, so we will leave our discussion of the debugger to the corresponding lab.
While debuggers are useful and can help you identify the locations of the errors in your code, they are not a silver bullet. In the end, you will still need to analyze your code for errors, both obvious (well, obvious in retrospect) and subtle.
Some errors can be quite subtle (and infrequent). For example, a debugger is unlikely to help you find the error in the following code. (An error which experienced programmers may be more likely to note.)
/**
* Average two longs. If the average has a fractional portion,
* may round up or down.
*/
public static long average(long x, long y) {
return (x + y) / 2;
} // average
List a few times you’ve had errors in your programs and how you identified the errors. Would a debugger have been useful?
What are some techniques you already use to write less buggy code?
When your program doesn’t work, how can you determine where in the program things are going wrong?
average
a. What’s wrong with the average
procedure at the end of the reading?
b. How would you address that problem in Java?
c. How would you address a similar problem in C?