As with any programming language, error avoidance is the best debugging strategy. Take advantage of the online documentation (available with most systems) and test functions, or even parts of functions, as you write them.
Still, you'll inevitably face the unexpected error, and you should know how to use the debugger. More often than not, a quick look at the error location as shown by the debugger will point out an obvious problem.
Some problems, though, are not obvious; your program will run without error, but produce incorrect results. When examination of the code does not reveal an error, you can rely upon built in Lisp tools to expose the details of your program's operation and find the error during execution.
There are two ways to notice an error. The intrusion of the Lisp
debugger is the most obvious. The debugger will appear whenever your
program causes Lisp to signal an error. This is often the result of
something obvious, like trying to perform arithmetic on
NIL or trying to
FUNCALL an object that is
not a function.
Your program's failure to produce expected results is also an error, even though the debugger never appears. In this case, your program doesn't make any mistakes in its use of Lisp, but the successful sequence of Lisp operations doesn't do what you had intended. Another possibility is that your program will fail to terminate at all.
The best defense against all of these problems is to write short, clear function definitions and test each one as soon as you've written it. I find it helpful to write one or more test cases and include them as comments (bracketed by #| and |#) in the same file.
Every Lisp debugger will provide at least two important pieces of information: an error message and a stack backtrace.
The error message describes how the program failed. Normally, this is a description of an error encountered while executing some built in Lisp function. If your program calls ERROR, the debugger will display the message you specify.
The stack backtrace describes where your program failed by displaying the call stack at the point of the error. The function which signalled the error will be at the "top" of the stack. Below that is the function that called the function which signalled the error, and so on all the way to (and sometimes beyond) the listener's read-eval-print loop.
The debugger relies upon certain information provided by the compiler or interpreter. Although the details vary among implementations, it's safe to say that compiler optimizations that improve speed or reduce space tend to reduce the amount of information available to the debugger. You can change these optimizations while debugging your program:
(declaim (optimize (speed 0) (space 0) (debug 3)))
If you execute this before compiling your program, the debugger should be able to give you more useful information. You should consult your vendor's documentation to learn about additional implementation-specific controls. If your Lisp system gives you a choice between using a compiler and using an interpreter, you'll probably find that the interpreter causes the debugger to give you better information.
If your program runs to completion but produces incorrect results, or if it runs but fails to terminate, then you'll need some additional tools. The first of these tools should be familiar to all programmers: insert a call to the debugger or (more commonly) insert a print statement.
BREAK causes your program to call the debugger. Once
inside the debugger you can examine the call stack. Most debuggers
also allow you to examine values local to each active function on the
call stack; by looking at these values at a critical point during your
program's execution, you may find an important clue as to why your
The debugger will allow you to continue from a break. You may
find it helpful -- if you don't yet understand the cause of a
problem -- to correct one or more wrong values before continuing; with
BREAK forms inserted at key points in your program,
this strategy may lead you to a place where the error is
Of course, you can always insert
Don't forget that you can use
FORMAT to print the
values of several variables together with explanatory text. And with
FORMAT, be careful that you
do not change the meaning of the code by inserting the debugging
statement. Remember that some flow-control forms (e.g.
UNWIND-PROTECT) expect a single form at certain places.
Also beware of wrapping
Lisp provides additional debugging tools to help you observe the dynamic behavior of your program.
TRACE allows you to observe each call and return from
a specific function, no matter where the function appears in your program.
To trace a function, invoke
TRACE with the name of the
function. You can do this for as many functions as needed. You can
also pass several function names to
When your program runs a traced function, it will print the name of
the function on entry and exit. Most
will also print the function arguments on entry and returned values on
To discontinue tracing of a function, pass its name to
UNTRACE. To discontinue tracing of all traced
See Chapter 16 for an
STEP allows you to interactively control evaluation of
an expression. If you step a function invocation, you should be able
to examine each subform of the function's definition just before it
STEP implementations vary widely, so you
should consult your vendor's documentation for further details. In
general, the same optimizations and controls that aid the debugger will
also aid the stepper.
STEP is a very labor-intensive way to debug a program,
since you must tell its user interface to evaluate each subform. This
is reasonable for straight-line code, but quickly becomes tedious in
the presence of looping or recursion.
Some Lisp implementations provide two additional tools,
WATCH, that can be of use during
ADVISE modifies a function without changing its
ADVISE can usually examine the advised
function's arguments, execute its own code, execute the advised
function, examine the advised function's return values, and modify
the returned values. For debugging purposes,
can be used to implement conditional
TRACEs, or to temporarily patch incorrect behavior
in one part of a program while you're debugging another part.
WATCH lets you specify variables to be displayed
as your program executes. This is normally available only in
Lisp implementations that provide a windowed user interface.
Because of issues of variable scope and display update timing and
WATCH is of limited value. Most Lisp
implementations do not provide this tool.
As you debug your program, you may need to see the internal details of composite objects such as lists, structures, arrays, streams and CLOS instances. Lisp lets you do this whether the data has been defined by your program or by the Lisp runtime system.
DESCRIBE is a function that accepts any object as an
argument and prints a description of that object. The form and
content of the description may vary among Lisp implementations.
DESCRIBE accepts an output stream as an optional second
INSPECT is an interactive version of
DESCRIBE. This is most useful for examining complex
objects by "drilling down" into the implementation details of
enclosed data elements.
When faced with the debugger, you will have a choice of restart
actions depending upon how the error was signalled.
requires that you abandon your program's executions. However,
many internal Lisp functions use
CERROR, which gives you
a chance to continue from an error.
In most debuggers, you can do quite a few useful things before continuing from an error:
Unwanted definitions are not usually a problem in a Lisp program.
You can get rid of function definitions using
FMAKUNBOUND, variable values with
MAKUNBOUND, and even symbols with
UNINTERN. In practice, there's usually no need to use
any of these; available memory is commonly large compared to the
size of a few misdefined variables or functions, and they will be
eliminated anyway the next time you restart your Lisp image and load
Method definitions are an entirely different matter. Remember that methods must have congruent argument lists; if you change your mind during program development about a method's argument list -- perhaps you thought that it needed two arguments at first but then realized three arguments are really needed -- then you'll have to remove the old method definition before adding the new one. Some Lisp environments facilitate this redefinition:
? (defmethod baz (a b)) #<STANDARD-METHOD BAZ (T T)> ? (defmethod baz (a b c)) Error: Incompatible lambda list in #<STANDARD-METHOD BAZ (T T T)> for #<STANDARD-GENERIC-FUNCTION BAZ #x3D2CB66>. Restart options: 1. Remove 1 method from the generic-function and change its lambda list 2. Top levl ?
If you simply add a method that you later decide is no longer
wanted, you'll need a way to remove the method. The least desirable
technique is to restart your Lisp system and reload your program
without the unwanted definition. Another approach, provided by some
vendors, is to interactively remove the definition using a special
editor command or a method browser. Failing all else, you can remove
the method manually using
(let* ((generic-function (symbol-function 'gf-name)) (method (find-method generic-function '(method-specializers) (list (find-class parameter-class-name) ; one per argument ...)))) (remove-method generic-function method))
where gf-name is the name of the generic function (i.e.
the name of the method), method-specializers is either empty
or a method combination specifier, such as
parameter-class-name is the name of the class on which a
particular method parameter is specialized.
Symbol conflicts across packages can be frustrating during
development. If you have defined multiple packages for your program,
you'll need to be careful to set the proper package (using
IN-PACKAGE) before defining an object intended for that
package. If you inadvertently create an object in the wrong package
and then attempt to define it in the correct package, Lisp will signal
an error if there is a "uses" relationship between the two packages.
The proper response is to first remove the erroneous definition using
You can also get into trouble with packages by having unexported classes defined in two packages and specializing a method based on the wrong class.
Macros must always be defined before use. This is especially
important when you redefine a macro during development: every piece
of code that uses the redefined macro
must be recompiled.
You can help yourself avoid macro redefinition problems by reloading
your source code after redefining any macro(s).
When I read code, finding the phrase "can't happen" in a comment always raises a red flag. Usually, this statement is made after the programmer has examined the code's execution environment and intended use. Unfortunately, things change and "can't happen" cases do happen.
Lisp provides a very handy facility for checking "can't happen"
statements at runtime. The
ASSERT macro expects a
form that will evaluate to true at runtime. If the form evaluates
ASSERT signals a continuable
error, transferring control to the debugger. At the very least, this
will help you to learn which assertion was violated so you can
correct your program.
ASSERT accepts an optional list of value places that
the user can interactively change to satisfy the assertion.
? (defun add-2 (n) (assert (numberp n) (n)) (+ 2 n)) ? (add-2 3) 5 ? (add-2 'foo) Error: Failed assertion (NUMBERP N) Restart options: 1. Change the values of some places, then retry the assertion 2. Top level ? 1 Value for N: 4 6See Chapter 23 for additional information about
ASSERTand other error detection and recovery techniques.
When your program needs to make a decision based on the type of
an object, you have two choices. You can use
DEFMETHOD. Unless you have a circumstance that
particularly warrants the use of
TYPECASE (or its
ETYPECASE) -- and
especially if the set of types will change during normal program
evolution or maintenance -- you should probably construct your
program to operate on the individual types via generic functions.
This more clearly exposes the intent of the program and eliminates
the likelihood that you will forget to update a
TYPECASE form during maintenance.