10.2 Error Types and Debugging Techniques
Key Takeaways
- Syntax errors violate a language's grammar, such as a missing end if or a misspelled keyword; they are one kind of compile-time error.
- Compile-time errors are detected before the program runs, including syntax errors and type errors such as storing a String in an int variable.
- Runtime errors occur while the program runs, such as division by zero, an out-of-bounds index, or using a null reference.
- Overflow occurs when a result exceeds the range of its type, and round-off error occurs because floating-point values are stored approximately.
- A logic error lets the program run to completion but produce wrong results; it is found by tracing, testing, and well-placed print statements.
What this competency asks
Two ETS competencies cover errors:
- Under correctness: identify the type of error produced by a code segment (syntax, runtime, compile-time, overflow, round-off, logic), and identify errors in incorrect code and the changes that correct them.
- Under debugging: differentiate between types of errors, describe useful debugging techniques (for example, where to put print statements), and identify errors in code and solutions to those errors.
The six error types
| Type | When it appears | What you see | Example |
|---|---|---|---|
| Syntax | Before running (while parsing) | Error message; the program does not run | Missing end if; misspelled keyword whlie; unbalanced parentheses |
| Compile-time | Before running (translation) | Error message; no executable produced | Syntax errors, plus type errors such as int n ← "ten", or using an undeclared variable |
| Runtime | While running | Program stops with an error (it crashes or throws an exception) | Division by zero; a[n] on an array of length n; calling a method on null; stack overflow from runaway recursion |
| Overflow | While running | A value too large (or too small) for its type; may wrap around silently or raise an error, depending on the language | Adding 1 to the largest int, 2,147,483,647, wraps to −2,147,483,648 in Java |
| Round-off | While running | Slightly inaccurate floating-point results | 0.1 + 0.2 is 0.30000000000000004; a loop that tests x ≠ 1.0 never stops |
| Logic | While running | Program finishes normally but produces wrong output | Using / instead of %; < instead of ≤; adding instead of multiplying |
Syntax versus compile-time. Every syntax error is caught at compile time, but not every compile-time error is a syntax error. int n ← "ten" is grammatically well formed, yet a statically typed compiler rejects it as a type mismatch. In an interpreted language, some of these problems appear only at run time.
Runtime versus logic. A runtime error stops the program. A logic error lets it finish with the wrong answer, which makes it harder to notice. ETS's sample procedure that should print the odd integers from 1 to n tests ( c / 2 ) ≠ 0 instead of using %. It runs without crashing but prints the wrong numbers, so it is a logic error, fixed by replacing / with %.
Overflow and round-off come from the limits of how numbers are stored (Section 13.1). Overflow affects integers (and very large floating-point values). Round-off affects floating-point arithmetic. Neither is a mistake in grammar.
Identifying the error in a code segment
| Code | Error type | Fix |
|---|---|---|
if ( x > 0 ) with no matching end if | Syntax | Add end if |
int total ← "0" in a typed language | Compile-time (type) | int total ← 0 |
avg ← sum / count when count can be 0 | Runtime | Check count ≠ 0 first |
for ( int i ← 0; i ≤ n; i ← i + 1 ) reading a[i] | Runtime (out of bounds) | Use i < n |
int big ← 2000000000 + 2000000000 (32-bit int) | Overflow | Use a wider type, or check the range first |
while ( x ≠ 1.0 ) with x ← x + 0.1 | Round-off (leads to an infinite loop) | while ( x < 1.0 - 0.0001 ), or count with an integer |
| Product accumulator initialized to 0 | Logic | Initialize to 1 |
max ← -1 used for a list that may be all negative | Logic | Initialize to the first element |
Debugging techniques
Debugging means locating the cause of a failure and fixing it. A systematic process beats guessing:
- Reproduce the problem with the smallest input that fails.
- Hypothesize where the state first goes wrong.
- Gather evidence with print statements, a debugger, or a hand trace.
- Fix the cause, not just the symptom.
- Retest, including the tests that passed before (regression testing, Section 10.3).
Where to put print statements
Print statements show intermediate values so you can find the first place where the program's state differs from what you expect.
| Placement | What it reveals | Example |
|---|---|---|
| Inside a loop, at the top of the body | The loop variable and key values on every iteration | print "i=" + i + " total=" + total |
| Just before a suspect statement | The inputs to that statement | Print count before sum / count |
| Just after a suspect statement | Whether it produced what you expected | Print avg after computing it |
| At procedure entry and exit | The arguments received and the value returned | print "findMax called with n=" + n |
| In each branch of an if / else | Which path actually ran | print "took else branch" |
Label every printed value, print as little as needed, and remove or disable debugging output when you are finished. A useful strategy is divide and conquer: print at the halfway point of the code. If the values are correct there, the bug is in the second half. Keep halving.
Other techniques
- Interactive debugger: set a breakpoint to pause at a line, step through statements one at a time, and watch variables change. Stepping into a call enters the procedure; stepping over runs it as one step.
- Read error messages and stack traces: they give the error type, the line, and the chain of calls that led there.
- Hand trace with a trace table (Section 4.4).
- Rubber-duck debugging: explain the code line by line, aloud, to someone else or to an object. Stating what each line should do exposes mismatches.
- Simplify: comment out parts, or test a procedure by itself with known inputs.
- Check the boundaries: first and last iterations, empty input, zero, and negative values.
A program compiles without errors. When it runs with an empty list, it stops with the message "division by zero" while computing an average. What type of error is this?
In Java, int big = 2147483647; is followed by big = big + 1; and then System.out.println(big);. The program prints −2147483648. What type of error has occurred?
A loop is supposed to add the numbers 1 through n to total, but the final total is wrong. Where should a programmer place a print statement to find the first iteration where total goes wrong?
A program adds 0.1 to a double variable ten times, starting from 0.0, and then tests whether the variable equals 1.0. The test is false. Which error type explains this?