I used to use debuggers, but never really liked them, and on languages with complex data structures they always seemed cumbersome to me. The last straw came when I had a C++ program with a memory leak -- which went away whenever i ran the program inside the debugger.
After that I stopped using a debugger. Here's what I do instead, in the hope that it may be helpful to you and others (I don't use Lisp, but most of this is relevant to all languages)...
What I use now instead of debuggers are unit tests. For every module, write a unit test. Ideally, write the test before the code. If you have modules or classes that're tightly coupled, write other tests that test how they work together. Then write one big test that runs all the other tests, starting first with modules that don't depend on other modules, then progressing to more complex functionality. In this way you can run the entire test suite easily.
Code your big test so that it writes a log of what it's doing to stdout, and also so it stops on the first error it encounters. (This is because you don't want to be burdened by thinking about more than one error at a time.)
You should endeavour that as you write your code, it passes 100% of the test suite all the time. (If you have to rework a whole section of code, you may have to comment out a series of tests for a time to do this.)
As you write the code, run the unit test after you add any functionality to the module. If the code affects other modules, run the entire test suite every time instead of the unit test. That way if your new code breaks some complex dependencies somewhere, you'll know instantly.
This means that your entire test suite should complex quickly -- at most a few tens of seconds -- or you'll be deterred from using it often enough. Obviously if your code is doing something very processor-intensive, you might not be able to do this.
The other thing I do is have a debug flag in each module which when set outputs debugging statements, typically when a function starts running, e.g:
debug = true
...
def foo(a, b):
if debug: printf "called foo(a=%r,b=%r)" %(a, b)
Finally, the golden rule is: never, EVER, make your code more complicated or harder to understand than it needs to be. Every time you break this rule, you cause more trouble for yourself later.
Comments
I used to use debuggers, but never really liked them, and on languages with complex data structures they always seemed cumbersome to me. The last straw came when I had a C++ program with a memory leak -- which went away whenever i ran the program inside the debugger.
After that I stopped using a debugger. Here's what I do instead, in the hope that it may be helpful to you and others (I don't use Lisp, but most of this is relevant to all languages)...
What I use now instead of debuggers are unit tests. For every module, write a unit test. Ideally, write the test before the code. If you have modules or classes that're tightly coupled, write other tests that test how they work together. Then write one big test that runs all the other tests, starting first with modules that don't depend on other modules, then progressing to more complex functionality. In this way you can run the entire test suite easily.
Code your big test so that it writes a log of what it's doing to stdout, and also so it stops on the first error it encounters. (This is because you don't want to be burdened by thinking about more than one error at a time.)
You should endeavour that as you write your code, it passes 100% of the test suite all the time. (If you have to rework a whole section of code, you may have to comment out a series of tests for a time to do this.)
As you write the code, run the unit test after you add any functionality to the module. If the code affects other modules, run the entire test suite every time instead of the unit test. That way if your new code breaks some complex dependencies somewhere, you'll know instantly.
This means that your entire test suite should complex quickly -- at most a few tens of seconds -- or you'll be deterred from using it often enough. Obviously if your code is doing something very processor-intensive, you might not be able to do this.
The other thing I do is have a debug flag in each module which when set outputs debugging statements, typically when a function starts running, e.g:
Finally, the golden rule is: never, EVER, make your code more complicated or harder to understand than it needs to be. Every time you break this rule, you cause more trouble for yourself later.