Lab 04 - Debugging

Due by 11:59pm on 2024-01-23.

Starter Files

Download lab04.zip. Inside the archive, you will find starter files for the questions in this lab.

Topics

What's a bug?

A bug is an error in your code. This can include using incorrect syntax errors, passing in the wrong number or type of arguments, logic and calculation errors, etc.

Traceback Errors

Traceback Error means you're not writing your code according to the rules of the language or you used something incorrectly. For example python requires a colon after main statements such as if-statements, function definitions, loops, etc.

# This code will give a syntax error, bc it's missing a colon

def function()
# some code

These kind of errors will produce a Traceback Error. If we run the above code we'd get something like this:

File "/Users/username/folder/debugging.py", line 1
def func()
^
SyntaxError: expected ':'

The breakdown of Traceback errors goes like this.

  • File name: the name of the file where the error happened
  • Number: the line number in the file where the error happened, or the line number that contains the next function call
  • Function: the name of the function in which the line can be found.

Here's another example

def add(a, b):
return a + b

add(3, 4, 5)
Traceback (most recent call last):
File "/Users/username/folder/debugging.py", line 4, in <module>
add(3, 4, 5)
TypeError: add() takes 2 positional arguments but 3 were given

Logic Errors

Logic error means that your code does not produce the correct or expected output. These will not give you a message, so they're difficult to track down. For example: An incorrect condition on a while loop that causes 1 extra iteration than you want, adding variables incorrectly would be a logic error.

You will a need a strong understanding of what the expected output for a variety of different input, including extreme input, known as edge cases. You'll want to create tests to check that your code does what you want.

Every Line Matters

Remember, computers are very dumb. They will only do EXACTLY what you tell it. It can't infer anything. So you have to be extremely specific and precise.

The key to fixing bugs is looking at every line of code individually to make sure you've written each line correctly. If you only glance at the code as a whole you may seem parts of working code and believe that all your code should work when it doesn't.

The following techniques will help you check individual lines of code.

Required Questions

Q1: Print statements

Chances are you'll be looking through a lot of code at once. When you find there's a bug in your code, your first goal is to figure out where it is.

One way to do this is through print statements. What you do is make a print statement that tells you where you're at in your code. Add the word debug like this and then description of what the code should be doing: print("DEBUG: ...").

Here's an example of how this might look.

def add(a, b):
print("DEBUG: Inside add function")
return a + b

print("DEBUG: Declared variables")
x = 5
y = 7
z = 9

print("DEBUG: Doing math")
sum = add(x, y)

print("DEBUG: Finish")

If at some point while running one of these print statements is not shown then we know that there's an error between the last statement that was printed and the print statement that was supposed to be next.

The code below has the function digit_counter, which takes in func, a one-argument function, and num, a non-negative integer, and returns the number of digits in n for which func(digit) returns True. For example, if digit_counter was to find the number of even numbers in 1112, it would return 1. Below is some code to start it, but it's incorrect.

Write some print statements in this code to track what's happening in the function.

You'll find this in the digit_count.py file.

def digit_counter(func, num):
"""Return the number of digits when func(num) is true"""
counter = 0
while num >= 0:
if func(num % 10):
counter += 1
num = num // 10
return counter

# Function to test with
def is_even(x):
return x % 2 == 0

Write some code that will test the function with different numbers with the is_even function. Proceed to fix the digit_counter function.

Tip: You can also use print statements to track values and check if they match what you're expecting.

Tips

  • Don't just print out a variable - always add some sort of message and mark it with the word DEBUG to make it easier for you to read

    print(tmp)   # harder to keep track
    print(f"DEBUG: tmp = {tmp}") # easier
  • Use print statements inside loops to track what iteration you are on.

    i = 0
    while i < n:
    i += func(i)
    print('DEBUG: i is', i)

Q2: IDE Debugger

Code Explanation

In this problem the code uses a convergence algorithm to calculate the square root. This means that at every step in the execution, the program makes a "guess" to the correct answer based on some criteria, and then through each iteration refines the guess until it converges on the correct solution. Bounds are set low and high, to restrict the range of values that the program can get.

Initially, low is set to 0 and high is set to the current number we're calculating the square root for.

Each iteration goes like this

  • The guess that the algorithm makes for the solution is the midpoint between high and low.
  • The middle value is then squared and compared to the value we're solving for.
  • If the square of the guess is greater than the number, the guess is too high, and so we set high to the current middle and continue executing.
  • If the square of the guess is less than the number, the guess is too low, and so we set low to the current middle and continue executing.

In this way, we gradually decrease the range of possible values until we have converged on a solution.

Because it may be impossible to calculate exact square roots for some values, the program runs until it has reached a specified level of precision. When we reach that the while loop exits and returns a solution.

For this program when the new guess and the old guess are the same to within the level of precision specified, that tells us we cannot improve on the guess anymore and have found the correct solution. You can also stop the loop when high and low are the same within the level of precision specified.

You'll find this in the square_root.py file.

def square_root(num):
"""Calculate the square root with 0.000001 precision"""
num = abs(num)

low = 0
high = num
middle = num
old_middle = -1
iteration_count = 0

accuracy = 1
while abs(old_middle - middle) <= accuracy:
old_middle = middle

middle = (high + low) / 4
middle_squared = middle * 2

if middle_squared > num:
low = middle
else:
high = middle

iteration_count += 1

return round(middle, 6), iteration_count

# Testing code
print(square_root(9))

Let's discuss how to use the IDE Debug Mode to fix this.

Breakpoints

When we run with a debugger a breakpoint specifies a line of code for the program to pause on. Set on on the following line so we can set through the while loop.

image1-breakpoints.png

Running Debug Mode

Right click in the file and click the debug option

image1-breakpoints.png

This what your screen will look like. We can now see the variables declared before this point low, high, middle, accuracy, etc with their current values. The blue highlighted line indicates where the code is stopping

image3-running-debug-mode.png

Stepping Through Code

This is the step over button. It allows you to step over one line of code at a time.

image4-stepover.jpg

The down arrow is the step in button. This allows you to go into functions and step through function call code. If you use step over on a function call, it's treated like a single line.

image5-stepinto.jpg

Click the step over button, we'll find that our loop never ran and skipped to the return statement at the end of the function.

image6.png

To continue this code till the end to see the final result we can use the continue button. This will run the code until the next breakpoint and pause there. If no other breakpoints the code will run till finished.

image7-continue.png

The result ends up as (9, 0) 9 as the result of the calculation and 0 being the number of loops/iterations that occurred. Now that our file square_root is set up to debug we can click this button to re-run.

image8-debug-button.png

Fixing Errors

Looking at our while loop we can check what's going on. This condition finds the absolute value between old_middle and middle to see how close they are. If the difference is greater than a certain accuracy we need to do another iteration of the algorithm.

The code here says if the difference is less than or equal to the accuracy run the loop. Change this code to say abs(old_middle - middle) > accuracy. Click the continue button or the Red square button to stop next to the debug button we just used.

image9-fixing-errors.png

Now if we re-run this code in debug mode and click the step over button we should get to this point. Now our loop is running.

image10-fixing-errors.png

Fix all the code

Read the code description of this problem and use debug mode to find the rest of the errors. There are 6 total errors to fix. You may also use print statements to track what's happening. Feel free to test the function with other input besides what's given.

Submit

If you attend the lab, you don't have to submit anything.

If you don't attend the lab, you will have to submit working code. Submit the digit_counter.py and square_root.py files on Canvas to Gradescope in the window on the assignment page.


Extra Practice

Q3: Largest Factor

The following function finds the largest factor of a number n that is not n with the exception of n being 0 or 1.

Find the bugs and fix the code.

def largest_factor(n):
biggest_factor = 1
i = 2
while i <= n ** 0.5:
if n % i == 0:
biggest_factor = i
i += 1

return biggest_factor

Q4: Missing Digits

Given a number n that is in sorted, non-decreasing order, return the number of missing digits in n. A missing digit is a number between the first and last digit of a that is not in n.

For example, if missing_digits was to find the number of missing numbers in 34, it would return 0 because there are no digits missing between 3 and 4. Other valid inputs include 1, 36, and 1489.

752 would not be a valid input because it is in decreasing order.

Find the bugs and fix the code.

def missing_digits(n):
counter = 0
while n > 10:
last_digit = n % 10
second_to_last_digit = (n // 10) % 10
diff = last_digit - second_to_last_digit
counter += diff
n //= 10

return counter

© 2023 Brigham Young University, All Rights Reserved