Debugging logic errors
Finding the mistake that gives no message: watching, printing, stepping and common bugs.
Do this lesson in the simulatorSyntax errors and runtime errors announce themselves. A logic error does not: the program runs, finishes, and does the wrong thing. Finding one is detective work, and like detective work it goes faster with a method. This lesson is that method, and the tools that help.
Step 1: know what should happen
You cannot find a mistake until you know exactly what right looks like. This program should drive a square of 20 cm sides and end where it started, facing the way it began:
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
side = 20
for i in range(3):
forward(60, distance=side)
turn_left(30, angle=90)
print("back at", position(), "facing", round(heading()))
Run it. It ends in the wrong place, facing the wrong way. No error message. The trail on the mat is the first clue: count its sides.
Step 2: find where it first goes wrong
Look for the first moment the program does something different from what it should. Everything before that point is fine, and the bug is at or just before it. Three ways to find that moment:
- Watch it. The trail shows three sides, not four, and each turn goes the wrong way.
- Print it. Add a
printinside the loop showingi,position()andheading()each time round: the program writes its own trace table. - Step through it. Press Debug. The program stops before each line; press Step to run one line at a time, and read the variables beside the editor as they change.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
side = 20
for i in range(3):
print("side", i, "starts at", position(), "facing", round(heading()))
forward(60, distance=side)
turn_left(30, angle=90)
print("back at", position(), "facing", round(heading()))
The printout shows i going 0, 1, 2: only three times round. And the heading goes to 270, then 180: anticlockwise, when the square was meant to turn clockwise like a positive angle.
Step 3: form a theory and test it
Say what you think is wrong, change one thing, and run again:
- Theory:
range(3)gives three sides. Change it torange(4). Run: four sides now, but still turning the wrong way. - Theory:
turn_left(30, angle=90)turns anticlockwise. Change it toturn_right(30, angle=90). Run: a square, back at the start.
Changing one thing at a time matters. Change both at once, and if the result is still wrong, you do not know which change helped and which did not.
Common logic errors
| Error | Example | How it shows |
|---|---|---|
| Off by one | range(3) for four things |
one too few (or too many) times round |
| Wrong operator | < instead of <= |
the edge case, exactly at the limit, is wrong |
| Wrong sign | turn_left(30, angle=90) for turn_right(30, angle=90) |
the robot goes the opposite way |
| Reset in the loop | total = 0 inside the loop |
a total that never grows |
| Wrong variable | print(side) when you meant total |
the right kind of value, but the wrong one |
| Order of steps | reading the sensor before driving, when it should be after | values that lag one step behind |
Most bugs are one of these. When a program is wrong, run down the list.
Explaining it out loud
Programmers sometimes explain their code, line by line, to a rubber duck on the desk. It sounds silly, and it works: saying what each line should do makes you notice the line that does not. A classmate works even better than a duck.
Task: fix the square
This program should drive a square of 25 cm sides and finish back at the start, facing the way it began. It has three logic errors. Find them with the trail, prints or Debug, and fix them one at a time.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
side = 25
for i in range(3):
side = 20
forward(60, distance=side)
turn_left(30, angle=90)
Challenges
- Write a program with an off-by-one error in it for a partner, and time how long they take to find it with Debug.
- The parking sensor from F2.8 stops too close to the wall at high speed. Is that a logic error? How would you find the best stopping distance?
- Add a
printthat shows a variable's value and its type. When would the type be the clue?