Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Debugging by checking one thing at a time

After this lesson: I can find a bug by reading the error and checking one thing at a time.

60 min lesson13 commandsEveryday Linux commands lab: the thirty commands engineers type from memory

How to debug a program that is wrong

Debugging is finding out why a program does the wrong thing, and then changing it. Most of the work is not typing the fix. It is knowing which of a hundred things to look at.

Guessing and changing code at random can take all afternoon. Checking one thing at a time takes a few minutes, and this lesson shows how.

By the end of this lesson

Note

I can find a bug by reading the error and checking one thing at a time.

Key words

WordWhat it means
Buga fault in a program that makes it do the wrong thing
Reproducemake the bug happen again, the same way, on demand
Tracebackthe list of calls Python prints when a program stops with an error
Hypothesisa guess about the cause that one check can prove wrong
Print checka line that shows a value, to see what the program really holds
Halvingchecking the middle of what is left, so each check rules out half

The cells students fill in are left blank in this preview.

First make the bug happen on demand

You cannot fix what you cannot see. Before you change anything, find an input that makes the bug happen every time, and keep it.

Here a small program averages the sales in a file, one line a day. Its data has a gap. Make the folder and the file first.

Make a folder for the lesson

Command
mkdir -p ~/debugtest

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Go into it

Command
cd ~/debugtest

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the sales file

Command
printf 'mon 120\ntue 80\nwed n/a\nthu 200\n' > sales.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the report program

Command
printf 'def parse(line):\n    day, amount = line.split()\n    return day, int(amount)\n\n\ndef average(lines):\n    result = 0\n    for line in lines:\n        day, amount = parse(line)\n        result = result + amount\n    return result / len(lines)\n\n\nprint(average(open("sales.txt").read().splitlines()))\n' > report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it and watch it stop

Command
python3 report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read a traceback from the bottom up

A traceback lists the calls that were open when the program stopped. The newest call is at the bottom, so read from the bottom.

The last line says what went wrong: ValueError, with a message. The File line above it says where: a file, a line number and a function. Lines above that say how the program got there.

List only the File lines

Command
python3 report.py 2>&1 | grep "File"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Check one thing, then the next

The error shows where the program stopped, not why the data was wrong. So make a hypothesis: one guess that one check can disprove.

Here the guess is that one line of the data is not a number. Search the file for it. If the check shows nothing, the guess was wrong, and that is also progress.

Search the data for the text

Command
grep -n "n/a" sales.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A quick fix can hide the next bug

Line 3 says wed n/a: there was no sale figure that day. The quickest change is to count it as 0. Try it, and run the program again.

Count a missing figure as 0

Command
sed -i 's|    return day, int(amount)|    if amount == "n/a":\n        return day, 0\n    return day, int(amount)|' report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it again

Command
python3 report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

No error does not mean no bug

The program ran, and printed 100.0. But the average of the three days that have figures, 120, 80 and 200, is 133.3. The program is wrong and says nothing.

Now there is no traceback to read. Add a print check where the numbers are added and look at what it really holds.

Print each day as it is added

Command
sed -i 's|        result = result + amount|        print(day, amount)\n        result = result + amount|' report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it and read each line

Command
python3 report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Halve the problem when there are many stages

The program has four stages. Check the middle one first. If the total is right, the fault is after it. If it is wrong, the fault is before it. Each check rules out half.

This is how a team finds which of a thousand changes broke a feature.

Worked example: checking the stages

StageWhat it should holdWhat it heldVerdict
Read the file4 lines4 linesFine
Read each amount120, 80, n/a, 200120, 80, 0, 200The 0 is a made-up number
Add them up400400Fine
Divide by the days counted400 / 3 = 133.3400 / 4 = 100.0It counted a day with no figure

The cells students fill in are left blank in this preview.

Write the program again, with the fix

Command
printf 'def parse(line):\n    day, amount = line.split()\n    if not amount.isdigit():\n        return None\n    return day, int(amount)\n\n\ndef average(lines):\n    result = 0\n    count = 0\n    for line in lines:\n        pair = parse(line)\n        if pair is not None:\n            result = result + pair[1]\n            count = count + 1\n    return result / count\n\n\nprint(round(average(open("sales.txt").read().splitlines()), 1))\n' > report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run the fixed program

Command
python3 report.py

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people change code before they know the cause, then change something else when that fails. After five changes nobody knows which one helped. Check first, change one thing, and run again.

Going further: finding the change that broke it

A team keeps every change in Git. When a bug appears, they know an old version was good and the newest is bad. The command git bisect asks you to test the middle version, and halves the history until one change is left.

Write the steps for 1000 changes. How many versions do you test at most?

About this lab and its source: Everyday Linux commands lab: the thirty commands engineers type from memory

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.