Debugging programs is an important skill for a computer scientist.
Here are some hints about debugging:
- Document clearly what each function should do. Write a comment
(after a semicolon) that states what the function does.
Write a comment describing each argument, unless it is obvious.
Write a comment with an example of a call to the function.
; Compute the value of an investment of a fixed contribution for a
; given number of time periods at a specified interest rate.
; contribution = amount added at the end of a time period
; time = number of time periods
; interest = interest paid per time period, e.g. 6% = 0.06
; Example: (invest 1040 60 0.06) ; $1040 per year for 60 years at 6%
(define (invest contribution time interest) ...)
These comments are a specification of your program.
One source of bugs is an unclear (poorly understood, unwritten) specification.
Sometimes, a clear specification will make it obvious how to write the
program. One of the best ways to debug is not to write bugs; a good
specification helps.
- Test the function on an easy case first. Often, the easy case is
the base case of a recursive function. Make sure the answer is correct
for the easy case. Then try a slightly harder case -- one for which
you understand the answer.
> (invest 100 0 0.06)
0
> (invest 100 1 0.06)
100.
> (invest 100 2 0.06)
206.
Investing $100 zero times gives zero. Investing $100 at the end of one time
period leaves us with the given $100. Investing $100 for two time periods
gives us interest of $6 on $100 during the second time period.
If your function works on the base case and a few more cases, it probably
works for all cases; if not, it will be easier to understand and fix the
function when the case is an easy one.
- Trace your functions. Use (trace fn ) to
trace the function fn. You can turn off a trace with
(untrace fn).
The trace will show the arguments of a function on entry and the result on
exit. The entry and exit are matched by vertical alignment of the trace
printouts.
- As soon as you see a bug, track it down and fix it.
Develop a keen eye for anything that looks unusual in your output; if
you see something abnormal, focus on it.
- Test your functions one at a time.
This is easy in Lisp: simply type in a call to a function
with test arguments, even if it is not the ``main'' function.
It is easy to test a single function in isolation, but hard to test a
large system.
- Use rapid prototyping and evolve your programs:
write a program that does part of the job, test it, then expand it.
When you get part of the program working, you can expand
on that model; this is better than repeating the same mistake many times
in your code before doing any testing.
- If your program ``cannot possibly be doing what it is doing'',
the bug is probably something that you are not looking at. Take a wider
view and look at other things.
- Instrument your code with debugging printouts: print out internal values
and look for abnormalities. Sometimes students stay up all night trying
to reason about why their program does not work, when a simple printout
would make the problem obvious.
You can detect internal errors and stop your program (and then get variable
values) using an (error   "message") call.
(if stopping-condition
(error "The elusive bug has occurred!"))
- When you get an error break, DrScheme can provide additional information:
- Click the function name to get instant documentation of the
function in which the error occurred.
- Click the Bug symbol to get backtrace information.
This will show the sequence of calls that reached the error and
variable values.