CS212 Handouts
Debugging Hints

A powerful program development environment is a two-edged sword. It can tempt you into bad habits, like jumping in and hacking without thinking.

Does this scenario apply to you? You sit down and hack out a program. You try it. It doesn't work. You scan through and spot something that doesn't look quite right. You change it randomly on the off chance that that was the source of the error. Then you try it again. It still doesn't work. And so on, into the wee hours of the morning...

This is probably the worst possible approach to debugging.

You can debug systematically and effectively without fancy debugging tools.

Everybody writes buggy code, even the best programmers. It's nothing to be ashamed of.

There are two general categories of errors:

  1. Programming errors -- the code does not do what it's supposed to do.
  2. Design errors -- the code does what it's supposed to do, but it's not what you wanted.

Errors of the first kind are usually relatively minor compared to those of the second (although at times they may not seem like it). They can usually be ferreted out using some simple common sense techniques. Examples of errors of the first kind are:

or the like. The distinguishing characteristic is that they are local, i.e. the bug is hiding in just one place--you just have to track it down.

Errors of the second kind are usually harder to figure out and to fix, because they may not be local, but may involve the collective behavior of several procedures. Often when you discover them, you have to back up and redesign a good chunk of your program.

Many errors will not become known until the code that causes the error is exercised. That is because function bodies are not evaluated until they are called. For that reason, thorough testing is important.

Preventative Medicine

Include sanity checks. If a crucial property is supposed to hold at certain point in the program, include a special check for that property. You can always take it out later. The type checking system of Swindle is a convenient and effective sanity checking system--use it!

When developing a procedure A with an auxiliary lambda B that would normally be inlined using letrec, give B a global name and do not inline it at first. Thoroughly test A and B separately. Then inline B and test A again with the code B inlined.

Try to keep functions small with simple interfaces. This is called modularity. Each function should have a simply-stated contract. What is expected of the inputs? What conditions do the outputs fulfill? Include sanity checks for these conditions.

Thoroughly test routines so that all functionality is exercised. For example, when writing a routine to insert an element in a list, try it on the empty list, a list with one element, and a list with more than one element. Chances are good that if it works for those three cases, it works in general. Try to drive all pieces of code, all alternatives in a cond, etc. You can call each function explicitly in DrScheme, you don't have to run the whole program.

Design your program top down (most general procedures down to low-level utility routines), but test it bottom up. That means get low-level routines working first. These are the building blocks that the rest of your code is built on. Test them thoroughly, then check them off as tested. Then test the routines that call them, etc. Once you load your program, you can call each routine explicitly with test arguments. Don't assume that a routine works just because it looks right. You'd be surprised at how subtle some errors can be.

Isolating the Bug

If the bug is local, just reading through your program is like trying to find a needle in a haystack. You'll be up all night. You need to isolate the bug systematically.

One useful trick is to sprinkle in print statements to tell whether you got to a certain point in the program and what the values of variables are at that point. Several constructs allow multiple expressions: the body of a method, the alternatives of a cond, the body of a let or letrec. You can just insert (echo "got here") or (print "x = ") (echo x). You can use (begin ...) where multiple expressions are not allowed.

When the error occurs there are a few things you can do.

First, look at the error message. It often gives helpful information. For example, if you called a procedure with the wrong type or number of arguments, you can get this information from the error message. You can usually see which procedure is being called and what the offending arguments are.

You also have a few extra options at your disposal to figure out what is going on.

If an argument is not what it's supposed to be, you need to figure out who is calling the routine with the offending argument to see how it got produced in the first place. If you can't immediately figure it out, find all the places that could have called the routine and insert echo statements to figure out which one is calling the routine with the bad argument.

To find out the binding of x in the current environment, just type x. If you want to find out the value of a function f on the current value of x, you can just type (f x). (Of course if f has side-effects, you may not get the same result as when the program was running.) You don't need to run your program from scratch every time. You can jump in in the middle and call routines with various inputs to see what they do.


Last Updated: 3/23/99 by Brandon Bray