Lesson 6.2
Find a bug by tracing and printing
Part 6: Errors, testing and filesReading, about 6 min2 exercises in this lesson
Java answers can't run on Spine yet. You can still read Java, trace it, and put an answer's lines in order.
Finding a bug by tracing and printing
By the end of this you will be able to track down a logic error by tracing the code by hand, use extra print lines to watch a program as it runs, and explain the fix you make.
A bug is a mistake in a program. A logic error is the hardest kind to find, because the program runs with no message and simply gives the wrong answer. You know something is wrong, but not where. Tracing tells you where.
A program with a bug
This program is meant to count how many marks are a pass. A pass is 50 or more.
marks = [72, 50, 38, 50]
passed = 0
for mark in marks:
if mark > 50:
passed = passed + 1
print(passed)public static void main(String[] args) {
int[] marks = {72, 50, 38, 50};
int passed = 0;
for (int mark : marks) {
if (mark > 50) {
passed = passed + 1;
}
}
System.out.println(passed);
}It prints 1. Work out the right answer by hand first: 72, 50 and the second 50 are passes, so the answer should be 3. Now you know there is a bug, and you have a test that shows it.
Trace it, and compare with what you expect
Trace the code with a table, as you have done before, one row for each pass of the loop. Next to the value the code gives, write the value it should have by then.
| mark | Is mark > 50? | passed | passed should be |
|---|---|---|---|
| 72 | yes | 1 | 1 |
| 50 | no | 1 | 2 |
| 38 | no | 1 | 2 |
| 50 | no | 1 | 3 |
The first row matches. The second row is the first one that does not: the mark is 50, which is a pass, but passed stays at 1. The bug is in the line that decided this row, the condition. mark > 50 is false when the mark is exactly 50. The condition should be mark >= 50.
Look for the first row that goes wrong. Every later row is wrong too, but only because of that one. Fixing the first difference usually fixes the rest.
After the fix, run the same test again. It prints 3. Then run the tests that passed before, to check the fix broke nothing else.
Choose a test that shows the bug
With the marks 72, 38 and 91, the program gives 2, which is right, so the bug hides. It shows only when a mark is exactly 50, the point where a pass begins. Values at the edge of a rule, like this one, are where bugs most often hide, so always include them when you test.
Print lines: let the program trace itself
For a longer program, tracing by hand is slow. Instead, add a temporary print that shows the variables each time round the loop. Here it goes inside the loop, after the if.
marks = [72, 50, 38, 50]
passed = 0
for mark in marks:
if mark > 50:
passed = passed + 1
line = "mark " + str(mark)
line = line + " passed "
print(line + str(passed))
print(passed)public static void main(String[] args) {
int[] marks = {72, 50, 38, 50};
int passed = 0;
for (int mark : marks) {
if (mark > 50) {
passed = passed + 1;
}
System.out.println("mark " + mark
+ " passed " + passed);
}
System.out.println(passed);
}When it runs:
mark 72 passed 1 mark 50 passed 1 mark 38 passed 1 mark 50 passed 1 1
This is the same trace table, written by the program. The second line shows the mark 50 with passed still at 1, which points at the condition again.
Take the extra lines out once the bug is fixed. They change what the program prints, so a program that must print one exact answer would now be wrong.
Many code editors can also stop a program at a line you choose, called a breakpoint, and show the value of every variable at that moment. A breakpoint does the same job as a print line, without changing the code.
Explain the fix
When you fix a bug, you should be able to say three things: the test that showed it, the line that was wrong, and why the change fixes it. For this program: the marks 72, 50, 38 and 50 gave 1 instead of 3; the condition mark > 50 was false for a mark of exactly 50; with mark >= 50, a 50 counts as a pass.
Two common mistakes
- Changing code until the answer looks right, without knowing why. A change that fixes one test by luck can break another. Find the first row that goes wrong, and fix the line that made it.
- Testing only the values that already worked. A bug often shows only at an edge, such as a mark of exactly 50, the first or last value in a list, or an empty list.
Now try it yourself on the questions below.
Exercise 6.2.1 · Trace itTrace: find skipped value
Complete the trace table for this Python code. This code is meant to add up every value in readings. The right total is 21. The code runs with no error message, but total ends up wrong. Then answer the question below the table. [4]
Line 1: readings = [6, 2, 9, 4]Line 2: total = 0Line 3: i = 1Line 4: while i < len(readings):Line 5, indented 1 level: total = total + readings[i]Line 6, indented 1 level: i = i + 1
Fill in one row each time a line changes i or total. The table already has the right number of rows. In each row, write only what that line changes; you can leave the other cells empty or copy their values down. Write values the way Python shows them: 3 and 3.0 are different, and text goes in quotes.
| Row | i | total |
|---|---|---|
| 1 | ||
| 2 | ||
| 3 | ||
| 4 | ||
| 5 | ||
| 6 | ||
| 7 | ||
| 8 |
Exercise 6.2.1 · Trace itTrace: find skipped value (Java)
Complete the trace table for this Java code. This code is meant to add up every value in readings. The right total is 21. The code runs with no error message, but total ends up wrong. Then answer the question below the table. [4]
Line 1: int[] readings = {6, 2, 9, 4};Line 2: int total = 0;Line 3: int i = 1;Line 4: while (i < readings.length) {Line 5, indented 1 level: total = total + readings[i];Line 6, indented 1 level: i = i + 1;Line 7: }
Fill in one row each time a line changes i or total. The table already has the right number of rows. In each row, write only what that line changes; you can leave the other cells empty or copy their values down. Write values the way Java shows them: 3 and 3.0 are different, and text goes in quotes.
| Row | i | total |
|---|---|---|
| 1 | ||
| 2 | ||
| 3 | ||
| 4 | ||
| 5 | ||
| 6 | ||
| 7 | ||
| 8 |
Exercise 6.2.26.2.2 · Write itFix the dry spell counter
A weather station records the rain on each day, in whole millimetres, in a list (an array in Java). A dry spell is a run of days in a row with 0 mm of rain. A classmate wrote this function to return the length of the longest dry spell, but it gives a wrong answer for some lists.
def longest_dry(rain):
longest = 0
run = 0
for mm in rain:
if mm == 0:
run = run + 1
else:
if run > longest:
longest = run
run = 0
return longest
public static int longestDry(
int[] rain) {
int longest = 0;
int run = 0;
for (int mm : rain) {
if (mm == 0) {
run = run + 1;
} else {
if (run > longest) {
longest = run;
}
run = 0;
}
}
return longest;
}
Trace the classmate's function by hand with the list 3, 0, 0, 0 to find the bug. Then write a version that is always right.
- Return the number of days in the longest dry spell. If no day is dry, return 0.
- The list always has at least 1 day and at most 31 days.
- Every value is a whole number from 0 to 500.
Only the number your function returns is checked.
def longest_dry(rain: list[int]) -> int
Examples
Your function is tested on 13 cases: the 4 examples below, and 9 hidden ones that check the rules above.
- longest_dry([3, 0, 0, 0]) should return 3: the longest spell runs to the end of the list
- longest_dry([0, 5, 0]) should return 1: two spells of 1 day, split by a wet day: they are not in a row
- longest_dry([0, 0, 7, 0]) should return 2: the longer spell comes first, before the wet day
- longest_dry([4, 2, 6]) should return 0: no dry day at all (rule 1)
public static int longestDry(int[] rain)
Running Java here comes later. You can put this answer's lines in order instead. You can still write your Java answer (it stays while this page is open) and compare it with our worked solution. Or try this exercise in Python.Checking whether Java answers can run here.
Examples
Your function is tested on 13 cases: the 4 examples below, and 9 hidden ones that check the rules above.
- longestDry(new int[]{3, 0, 0, 0}) should return 3: the longest spell runs to the end of the list
- longestDry(new int[]{0, 5, 0}) should return 1: two spells of 1 day, split by a wet day: they are not in a row
- longestDry(new int[]{0, 0, 7, 0}) should return 2: the longer spell comes first, before the wet day
- longestDry(new int[]{4, 2, 6}) should return 0: no dry day at all (rule 1)