16. Debugging
At this stage we have written quite a few algorithms
We have also gotten comfortable writing assertion tests for our algorithms
By this point, we have also seen plenty of bugs
Having bugs in your code is normal
Debugging is a large part of programming
Finding and fixing bugs is part of the process
You have probably already started developing your own debugging habits
In this topic, we will look at a few accessible debugging strategies
Before getting started, it is worth emphasizing what this is all about
It’s not just about getting your program to work
It’s about understanding your program and understanding why it is behaving the way it is
16.1. The Importance of Debugging
As programs get more complex, errors can become harder to track down
No matter how experienced you are, debugging remains part of programming
Beyond the obvious benefit of fixing your code, debugging helps us improve many important skills:
Knowledge of algorithms
Knowledge of the programming language
The ability to reason logically
The ability to reason about flow of execution in code
16.2. Error Messages
In many cases, Python will generate an error message giving you an idea of what went wrong
Sometimes these error messages are very helpful
line 17
print(5 + 5 +)
^
SyntaxError: invalid syntax
Other times the error messages are less clear
Often, you can search for the error message and find an explanation
Common error messages you will come across are
Syntax errors
Indentation errors
Name errors
Type errors
Index errors
16.2.1. Syntax Errors
Syntax errors often produce fairly helpful messages
These messages typically tell you where the error is, as seen in the above example
Usually, the main job is to read what Python is telling you
Activity
See if you can find all the errors in the following example. You may be able to find them just by looking at the code, but feel free to copy/paste the code into Colab to see what Python reports.
1deff borken(a(
2 a = a + * 3
3 walrus
4 return a + 2
5
6broken(5)
16.2.2. Type Errors
As we have seen, Python is pretty good about figuring out types
1a = 5 # It's an integer
2b = 5.5 # Float since there is a decimal
3c = a + b # Mixing types in the expression (int and float), but no big deal
4print(c) # Results in 10.5
But Python can only do so much
For example, Python cannot figure out what it should mean to add an integer and a string together
Think about it this way: what would it mean for
"Hello" / 32?
1print(99 + "bottles of beer on the wall")
2TypeError: unsupported operand type(s) for +: 'int' and 'str'
Sometimes a type-related issue does not generate an error message
Consider the
inconsistentfunction defined in the following example1def inconsistent(a, b): 2 return a + b
There is nothing wrong with this function, but notice what happens with these two calls
inconsistent(1, 1)returns2inconsistent("1", "1")returns"11"
It may seem obvious to just avoid the wrong argument types
But remember that Python’s
inputreturns a string, even if the user typed digitsThis is a very common source of bugs
16.3. Logic errors
Logic errors can be quite difficult to debug
Everything may seem like it’s working, but the program still behaves incorrectly
Sometimes the errors may be obvious, like an infinite loop
Sometimes they show up only in edge cases, so things work most of the time
Most people develop their own habits as they gain experience — a few strategies are discussed below
16.3.1. Print
Probably the simplest debugging method is to call
printin your codePrint out the value of some variable
Add a print to see if Python actually executed a specific code block
Prints are useful because they let us compare what we expect with what is actually happening
Activity
There is a problem with the following function. It almost works, but it is slightly off. Read the description, see
if you can identify the issue, and then use prints to inspect values and track down the problem.
1def sum_numbers_up_to(n: int) -> int:
2 """
3 This function adds up all the numbers from 0 - n exclusively.
4 Eg. 5 -> 0 + 1 + 2 + 3 + 4 -> 10
5
6 :param n: The number we are summing to. Note we do not count n
7 :return: The sum of the numbers
8 """
9
10 total = 0
11 c = 0
12 while c < n:
13 c += 1
14 total += c
15 return total
16
17assert 0 == sum_numbers_up_to(0)
18assert 10 == sum_numbers_up_to(5)
Debugging with
printoften follows a simple patternForm a hypothesis about the value of a variable at a specific place in your program
Add a
printto print out the variable’s valueCompare your expectation with reality
If they matched, perhaps the problem is elsewhere
If they do not match, investigate why they differ
Each
printgives us a little more informationDepending on the problem, you may need several
prints
16.3.2. Pencil & Paper
One challenge with debugging logic errors is avoiding assumptions about your code
Do not assume a variable has a specific value
Do not assume any specific functionality of a block of code
This is easier said than done, especially when you keep rerunning your code
Like in the case of using
print
A good alternative is to not run your program on the computer
Instead, execute the program on paper
Create a table to keep track of each variable and the current value
Execute the program on paper one line at a time, keeping track of the values of the variables
The benefit is that it forces you to slow down and be deliberate — assumptions are harder to make when you have to write everything out
This can feel tedious, but it often helps
Note
The point here is to take your time and think about every line of code. Rushing through and making assumptions will inevitably cause this strategy to fail — the program is behaving unexpectedly precisely because something you assumed was true, isn’t.
16.3.3. Delta Debugging
Another strategy is Delta Debugging
Delta Debugging is a more systematic way to isolate where an issue is arising
A simple version of the strategy is
Comment out every line of code and run it
Un-comment out a logical unit of code and run it
This should only be a line or two of code
You might also add a
print
Did it do what you expected?
No? You found at least part of the problem
Yes? Keep looking
This mirrors the recommended coding strategy from earlier — write a little, test, repeat
16.3.4. Rubber Duck Debugging
A surprisingly effective form of debugging
One helpful strategy is to explain your code to something
Sometimes you have a friend
Sometimes you have your mom
Sometimes you have a pet
And sometimes you have a rubber duck
The point is that explaining your code out loud often helps you notice what you skipped over mentally