Debugging

Introduction

A bug is a difference between what the code does and what you meant. Debugging is not guessing: it is a calm, repeatable process.

  1. Reproduce the bug with a small input.
  2. Watch the values step by step.
  3. Guess the cause, then test the guess with a change.

Everyone writes bugs, including experts, and much of a programmer's working day is spent finding them. Doing it with a method, rather than by guessing, is what turns hours of frustration into a few minutes of careful work.

How it works

A bug to find

marks = [70, 80, 90]
total = 0
for i in range(1, len(marks)):
    total += marks[i]
print(total / len(marks))
Open in compiler

The average of 70, 80 and 90 should be 80, but this prints 56.67. Step through and watch i and total: the loop starts at i = 1, so marks[0] (70) is never added, and total ends at 170. range(1, ...) is the bug.

Print debugging

def discount(price, percent):
    cut = price * percent / 100
    print("debug: cut =", cut)
    return price - cut

print(discount(500, 10))
Open in compiler

When you cannot use a visualizer, a temporary print shows a value at a chosen moment. Label it clearly, and remove it when you are done.

Syntax

print("debug: total =", total)    # label temporary prints
assert len(items) > 0             # catch a wrong assumption early

Example

marks = [70, 80, 90]
total = 0
for mark in marks:
    total += mark
print(total / len(marks))
Open in compiler

Looping over the items directly removes the index, and the off-by-one mistake with it. The answer is now 80.0. Often the best fix makes the bug impossible, not just absent.

Line by line

  1. marks holds the three values.
  2. total = 0 starts the sum.
  3. for mark in marks: visits every item, so none can be skipped.
  4. total += mark adds each one.
  5. Dividing by len(marks) gives the true average, 80.0.

Expected result

80.0

In the real world

When a website goes wrong, engineers do this same dance on a larger scale: find a small case that fails, watch what really happens, and then change one thing. Rubber duck debugging, which means explaining the code aloud, is a popular and surprisingly effective technique.

Common mistakes

  • Changing many things at once. Change one thing, then run again.
  • Reading the code you meant to write instead of the code you wrote. Step through it and trust the values you see.
  • Ignoring the error message and its line number: it is the best clue you have.

Best practices

  • Write down what you expected and what you saw.
  • Remove temporary prints when you are done.
  • Add a test that would have caught the bug.

Summary

Debugging is a calm process: reproduce the bug, watch the values, make a guess and test it with one change. Prefer a fix that makes the bug impossible, not merely absent.

Practice

Find the bug in def last(items): return items[len(items)] by running it on [1, 2, 3], then fix it.

Quick check

1. What was the bug in the buggy example?
2. What should you do first with a bug?
3. Why loop over items instead of positions here?

Key takeaways

  • Make the bug small and repeatable first.
  • Change one thing at a time.
  • The best fix makes the bug impossible.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.