Section 3.2 How to Avoid Debugging
Perhaps the most important lesson in debugging is that it is largely avoidable—if you work carefully.
Understand the Problem You must have a firm grasp on what you are trying to accomplish. You must understand what the program should do in a specific circumstance—what output should be produced for some given input. This will allow you to test your progress. You can then identify if a solution is correct or whether there remains work to do or bugs to fix. This is probably the single biggest piece of advice for programmers at every level.
-
Make a Plan Now that you know what the program should do, you need to figure out how to solve the problem. A good technique for planning is to solve the problem by hand. As you solve it, write down the steps you took. Act like Dr. Watson, constantly asking Sherlock Holmes “how did you know that?” (You have to be both Watson and Holmes, because you’ll have to answer the question.) Do your planning away from the computer! When you are at the computer, the keyboard is whispering “type something”, and the monitor is whispering “look at me”.
The plan you make does not have to go into microscopic detail, but it should contain the essential elements of a solution. Remember also that your plan is not engraved in stone. If you find that the program isn’t accomplishing what you want, you can come back to this step and modify the plan.
Start Small It is tempting to sit down and crank out the entire program at once. But, when the program—inevitably— does not work, you have a myriad of options for things that might be wrong. Where to start? Where to look first? How to figure out what went wrong? I’ll get to that in the next section. So, start with something really small. Maybe just the first two lines of your plan and then make sure that runs. Clicking the run button is quick and easy. It gives you immediate feedback about whether what you have just done works or not. Another immediate benefit of having something small working is that you have something to turn in. Turning in a small, incomplete program is almost always better than nothing.
-
Keep Improving It Once you have a small part of your program working, the next step is to figure out something small to add to it— how you can move closer to a correct solution. If you keep adding small pieces of the program one at a time, it is much easier to figure out what went wrong. (This means you must be able to recognize if there is an error. And that is done through testing.)
As long as you always test each new bit of code, it is most likely that any error is in the new code you have just added. Less new code means it’s easier to figure out where the problem is.
This notion of Plan the solution, get something working, and keep improving it is a mantra that you can repeat throughout your career as a programmer. Think of it this way: every time you have a little success, your brain releases a tiny bit of chemical that makes you happy. So, you can keep yourself happy and make programming more enjoyable by creating lots of small victories for yourself.
OK. Let’s look at an example. Let’s solve the problem posed in question 3 at the end of
Exercises 2.18. Ask the user for the time now (in hours 0 - 23), and ask for the number of hours to wait. Your program should output what the time will be on the clock when the alarm goes off. For example, if
current_time
is 8 and
wait_time
is 5,
final_time
should be 13 (1 pm).
In this case, the statement of the problem is the plan, to a large extent. But we will write a somewhat more formal plan anyway, filling in some detail about what calculations are necessary:
Ask for time now; call it current_time
.
Ask for number of hours to wait; call that wait_time
.
Add current_time
and wait_time
to get the final_time
.
Because final_time
could be greater than 24 (if you set an alarm for 45 hours at 4 pm., for example), we need to set final_time
to be in the range 0-23 by taking the final_time
modulo 24 and re-assigning the result to final_time
.
Print the value of final_time
(properly labeled).
So, where to start? The problem requires two pieces of input from the user, so let’s start there and make sure we can get the data we need.
So far so good. Now let’s take the next step. We need to figure out what the time will be after waiting wait_time
number of hours. Our plan says to add wait_time
to current_time
and print out the result. So let’s try that.
If you enter 14 for the current time and wait for 5 hours, you will see the message: Final time is 145
. No, Python is not broken; it really does know how to add numbers. The input
function, however, is giving us back a string, not a number. It turns out that when you use the +
operator on two strings, Python concatenates them (joins them together). For example:
word = "door" + "bell"
print(word) # prints doorbell
This error was relatively easy to spot because we printed out the value of final_time
, and the result shows that the “numbers” were concatenated together rather than added.
So what do we do about the problem? We will need to convert both current_time
and wait_time
to int
. At this stage of your programming development, it can be a good idea to include the type of the variable in the variable name itself. So let’s look at another iteration of the program that does that, and the conversion to integer.
Now, that’s a lot better, and in fact depending on the hours you chose, it may be exactly right. If you entered 8 for current_time
and 5 for wait_time
then 13 is correct. But if you entered 17 (5 pm) for current_time
and 9 for wait_time
then the result of 26 is not correct.
This illustrates an important aspect of testing: it is important to test your code on a range of inputs. It is especially important to test your code on boundary conditions. For this particular problem, you should test your program with current_time
of 0, 23, and some values in between. You should test your wait_time
for 0, and some larger values. What about negative numbers? Negative numbers don’t make sense, and since we don’t really have the tools to deal with telling the user when something is wrong we will not worry about that just yet.
So to account for those numbers that are bigger than 23, we need one final step: using the modulus operator to make sure that the time is always in the range 0-23. We will also change the final output to a more complete sentence.
Even in this simple progression, there are other ways you could have gone astray. We’ll look at some of those and how you track them down in the next section.