16. Debugging

  • At this stage we have written quite a few algorithms

  • We have also gotten comfortable writing assertion tests for our algorithms

  • However, at this stage we’ve likely had more bugs than working code

    • Having bugs in your code is normal — programmers spend more time debugging than actually writing code

    • It would be weird if you didn’t have bugs

    • Remember, your code will be wrong every time you run it until it’s correct

  • You have probably come up with a system for debugging

  • In this topic, we will discuss a few popular and accessible debugging strategies

  • Before getting started however, it is important to emphasize 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

../../_images/hi.jpg

16.1. The Importance of Debugging

  • As our programs get more and more complex, the types of errors we come across can increase in complexity

  • No matter how good of a programmer you are, you will still spend much of your time debugging

  • 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 quite cryptic

    • Fortunately, you can often copy/paste the error into Google and find an explanation

  • Common error messages that you will come across (if you have not already) are

    • Syntax errors

    • Indentation Errors

    • Name Errors

    • Type Error

    • Index Error

16.2.1. Syntax Errors

  • Syntax errors will cause error messages that are often quite helpful

  • These messages typically tell you where the error is, as seen in the above example

  • There is not much of a debugging strategy with these errors as you just look at what Python tells you

Activity

See if you can find all the errors in the following example. You may be able to find them all just by looking at the code, but feel free to copy/paste the code into Colab to see if the error messages help.

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 suddenly figure out what it means to add an integer and a string together

    • Think about it this way, what would you say if I asked you what “Hello” divided by 32 means?

1print(99 + "bottles of beer on the wall")
2TypeError: unsupported operand type(s) for +: 'int' and 'str'
  • However, sometimes we can have an issue caused by types that does not generate an error message

  • Consider the inconsistent function defined in the following example

    1def inconsistent(a, b):
    2    return a + b
    
  • There is nothing wrong with this function, but what would happen if you called the function with the following two sets of arguments

    • inconsistent(1, 1) returns 2

    • inconsistent("1", "1") returns "11"

  • It may seem obvious that one should just not call the function with the wrong argument types

  • But also consider reading input from the user, and how Python’s input returns a string, even if the inputs are numbers

    • How many times have you made the mistake in assuming the input were numbers when in fact they were strings?

16.3. Logic errors

  • Logic errors can be quite difficult to debug

  • Everything may seem like it’s working, but at the end of the day, something is off

  • Sometimes the errors may be obvious, like an infinite loop

  • And sometimes they can be quite sneaky — the errors can be in edge cases, so things work most of the time

    • Here’s hoping you tested your code thoroughly

  • There are a few strategies for distilling these bugs

  • A few accessible strategies for debugging are discussed below

  • Most people develop their own strategies as they gain experience

  • But just like everything else, you will get better at debugging the more you practice

16.3.1. Print

  • Probably the simplest method for debugging is to call print in your code

    • Print out the value of some variable

    • Add a print to see if Python actually executed a specific code block

  • Prints are great since they allow for a quick investigation into what we expect vs. what is actually happening

Activity

There is a problem with the following function. It almost works, but it’s slightly off. Read the description, see if you can identify the issue, and then make use of prints to print out the values and hopefully pinpoint and fix the issue.

 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)
  • The process of debugging with print typically follows a pattern

    • Form a hypothesis about the value of a variable at a specific place in your program

    • Add a print to print out the variable’s value

    • Compare your expectation with reality

    • If they matched, perhaps the problem is elsewhere

    • If they do not match, investigate why they differ

  • Each print enables us to form a new hypothesis and continue debugging

  • Depending on the complexity of the problem, you may find that you need multiple prints in order to make any progress

16.3.2. Pencil & Paper

  • One of the tricks about debugging logic errors is to not make any assumptions about your code

    • Do not assume a variable has a specific value

    • Do not assume any specific functionality of a block of code

  • However, this is easier said than done, especially when depending on running your code repeatedly for debugging

    • Like in the case of using print

  • A workaround is to not actually 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

  • Big benefits of this approach are

    • It slows you down

    • It becomes easier to not make assumptions

    • Requires you to be very deliberate and thoughtful

  • This may sound tedious, and it can be, but if you do this right you can very often find the problem

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.

Remember, you are here in the first place because what your program is actually doing is different from what you expected — it would be rather silly to use your incorrect assumptions about what is going on in your program to solve the problem caused by your incorrect assumptions.

16.3.3. Delta Debugging

  • Another strategy is Delta Debugging

  • Delta Debugging is a relatively sophisticated debugging strategy, but we can use it in a simple way

  • The main goal is to try to isolate, systematically, where the issue is arising

  • The strategy we can use is

    • Comment out every line of code and run it (obviously it will do nothing)

    • Un-comment out a logical unit of code and run it

      • This should only be a line or two of code

      • Maybe add a print

    • Did it do what you expected?

      • No? You found at least part of the problem

      • Yes? Keep looking

  • You may have realized that this is effectively the recommended coding strategy from earlier in the course

    • Write only a few lines at a time and validate that it is working correctly with tests

16.3.4. Rubber Duck Debugging

  • Rubber Duck Debugging

  • A shockingly effectively form of debugging

  • This is not a joke — one of the best debugging 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

16.4. For Next Class