Debugging 101
Note to reader: this post was written before students had access to VS Code and its associated debugging capabilities. Consequently, some valuable debugging tools like breakpoints are not covered in the post.
Debugging can seem like a difficult challenge, but once you understand how to see what’s under the hood, it becomes much simpler. Many of your problems may not actually be problems at all, but simple placement or division errors. Hopefully these 5 tips will help solve all your problems – they have for me!
Tip 1: Look at the Error
If something is wrong, the error will give you the line, file, and sometimes even tell you how it went wrong. This is the most helpful for silly errors as the console will tell you if something is not defined, the wrong type, dividing by 0, etc. Once you know the error, it’ll either be an easy fix or a big problem.
For definition/attribute errors, check the variable names and look around where they are defined. You might have misspelled the name by accident or defined the variable after you called it or the function it’s in. Other than that, check the lines around the error and make sure your parentheses, brackets, curly brackets, quotes, and apostrophes are closed.
Example 1.1: Misspelled, Missing and Misplaced Items
Consider the code below. This is the proper version that will output 6 with no errors.
[1] one = 1 [2] five = 5 [3] sum = one + five
Now, consider the following three snippets. The first snippet has a misspelling.
[1] one = 1 [2] fove = 5 [3] sum = one + five
The second snippet has a missing item.
[1] one = 1 [2] [3] sum = one + five
The third snippet has a misplaced item.
[1] one = 1 [2] [3] sum = one + five [4] five = 5
All of these have the same error code:
File "file_name.py", line 3, in <module> sum = one + five NameError: name 'five' is not defined
Anything having to do with a name will be a Name Error. Attribute errors (these have to do with classes) also have the above causes.
Example 1.2: Wrong Types
Type errors are very simple: they occur when you try to put two things together that don’t go together. The two kinds I see most often are as follows.
- Any code of the form
'string' + number
will have an error codeTypeError: can only concatenate str (not "int") to str
. With strings, the $+$ will put two strings together. But if the second variable is an integer and not a string, the console will print that it cannot concatenate str with integers. If it was a different kind of variable, the console would say what the variable type is in the quotation marks. - Any code of the form
None + number
will have an error codeTypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
. This is saying that there is no way for these two types to combine using the $+$ sign, and then displaying the two types you are trying to use. The types will come up in order of which came first in the code. For these two errors, you just have to find a way so that this doesn't happen. Maybe making an if statement or changing the type first.
Example 1.3: Zero Division
This is really self explanatory. Anytime you wind up dividing by zero, the console will spit out ZeroDivisionError: division by zero
.
Example 1.4: Syntax Error
Syntax Errors are your missing ending or starting quotations, parenthesis, brackets, curly brackets, etc, or just when something is typed wrong and is making the computer confused on what you’re trying to tell it to do. The error code usually says where the syntax is going wrong, so you can just head to that area and fix the syntax. If you don’t understand the error code, go to tip 4.
Tip 2: Look Under the Hood
There are times where no error is thrown, but you still aren’t getting the right output from your code. During times like this it is best to print out your variables. Printing out your variables can also help show if you are in an infinite while loop, as those will just go forever.
In general, I start by following the returned variable (abbreviated RV) from its initialization to its return to see exactly what happens to it. If the RV is based off of one or more intermediate variables, check first to see if the RV was ever correct in the first place. If not, just keep on backtracking until you get to a variable that IS computed correctly.
Tip 3 might be helpful here depending on how long your code is. Closely monitor and double check every way that variable is changed until the RV is finally correct. Now to double check that everything is working properly, try to run your function with a different set of data. You should know what the correct answer should be and the steps between before you try.
Example 2.1: Infinite While Loops
A “while loop” is a kind of loop that will keep on restarting as long as its condition is true. Infinite while loops are created when the condition is never false (when it hits the end of each run through), if there is no “break” command, or if the “break” command is never triggered.
You can check if your loop is infinite by printing out the values of any variables involved in the condition. If things start going too fast for you to read, you can import the time
library and use time.sleep(number of seconds)
to make the code pause before continuing.
If the condition variables go past the stopping condition, check what values they take at the end of the looped code. You may need to make it a break condition instead (just the “break” command inside an if statement). If the condition variable is cycling, check what it is each time you change it. You may have a logical problem in your code.
Tip 3: Separate into Chunks
As your coding gets more advanced, the code you write generally gets longer. Longer code is more difficult to debug, so if there is any code that you don’t actually need, delete it. This reduces how much code you have to look through when something goes wrong. Of course, don’t simplify to the point that you can’t read your code, but try to remove as many unnecessary things as possible.
Second, generalize and create helper functions and child classes when possible. In general, try to break up your code so that in the long run there is less code to go through each time something breaks.
- Helper functions are functions that carry out sub-tasks involved in your main function. I personally like to create helper functions for things that will be run over and over or things that are long and complex. For example, in almost all of my various graph classes,
set_breadth_first_distance and_previous
is a long, semi-complex bit of code that is run multiple times and is just part of what the main function accomplishes. We call this incalc_distance
andcalc_shortest_path
. - A child class gains access to all of the parent class's methods. This means that you can state one or more core methods inside of one class and then create child classes off of it. Each child class now has access to these core methods without having to restate them. Here is how you state a child class:
class ChildClassName (ParentClassName): ...
Now that your code is hopefully simplified, it’s time to find where things go wrong. You want to separate your code into “chunks” so that it’s more of a sieve. I would suggest making a copy of your code at this point. You find the general area that the code went wrong, and then begin dissecting it. Check each helper function to see if they are returning the proper things, expand out comprehensions and make sure everything is being set properly. Sometimes, everything will be correct except for one variable. Keep tracing back the error by repeating the same process on the area where that variable came from.
Tip 4: Look it Up the Error Code
I personally find this method VERY helpful for Haskell. Haskell can be very particular in what it accepts, where, and how, as well as not giving the most readable error codes. Let us go into an example from a semi-recent quiz, where all of us had a certain problem.
Example 4.1: Looking Up a Haskell Error
As you might remember in Quiz 2-3, Problem 2 was a Haskell problem on calculating the GPA Average. The code I turned in went like this:
calcPoints :: Char -> Int calcPoints char | char == 'A' = 4 | char == 'B' = 3 | char == 'C' = 2 | char == 'D' = 1 | char == 'F' = 0 calcTotalPoints list = sum([calcPoints x | x <- list]) calcGPA list = (calcTotalPoints list) / (length list) main = print( calcGPA ['A', 'B', 'B', 'C', 'C', 'C', 'D', 'F'] )
If you remember this, you know it didn’t work. The first thing I did to fix it was to look at the error code thrown out:
No instance for (Fractional Int) arising from a use of '/' \\ In the expression: (calcTotalPoints list) / (length list) \\ In an equation for 'calcGPA': \\ calcGPA list = (calcTotalPoints list) / (length list)
From that I knew the problem was in calcGPA
, but I checked calcTotalPoints
just in case (it was fine). There was nothing else to do but look up the error code, so I copied and pasted the first line into the Google search engine.
I would recommend selecting one of the results from Stack Overflow, a response page where people put out questions on coding and others will respond. The answers and tips on there are usually pretty good and the people usually explain why something is wrong or why they think their version is better.
From the first link that I found, one of the answers explains that this error means that these are Ints, which do not have division when the result would be fractional. Hence Haskell is asking where the Fractional Ints are. We just need to divide these Ints and get a decimal. So now we look up a new search, “haskell how to divide ints and get a decimal”.
Again choosing a link from Stack Overflow, we see that we need to convert these Ints into Floats BEFORE dividing them, and we can do that with a nice little built in function called fromIntegral (...)
. Now knowing this we can make a few small adjustments to our code and see if it works.
calcPoints :: Char -> Int calcPoints char | char == 'A' = 4 | char == 'B' = 3 | char == 'C' = 2 | char == 'D' = 1 | char == 'F' = 0 calcTotalPoints list = sum([calcPoints x | x <- list]) calcGPA list = fromIntegral(calcTotalPoints list) / fromIntegral(length list) main = print( calcGPA ['A', 'B', 'B', 'C', 'C', 'C', 'D', 'F'] )
This will return $2.125,$ which is the correct answer for the problem.
Tip 5: Ask for Help from Teachers or Peers
Sometimes, you really can’t tell what the problem is with your code. You’ve taken it apart, checked everything, it all seems to be working right, but for some reason it just isn’t outputting what it’s supposed to. Now it is time to surrender and ask for someone else to check your code. Sometimes the answer is such a small stupid thing that you never thought to check.
I would personally recommend asking if your debugging takes more than 45 minutes. You don’t want to annoy other people with silly questions that you could resolve yourself, but you also don’t want to waste time making no progress (even though there is nothing more satisfying than fixing your code after having worked on it for almost 10 hours straight!).
Now that your code is finished and bug free, get ready to go through the whole debugging process again when you implement the next thing!