Debugging software is an art, perhaps even more so than writing the software itself. All software we write will have bugs: it is important to know how to diagnose a bug when it happens and how to write code defensively so you have less bugs in the first place.
Bugs come in two categories: those that crash your program and those that don't. Bugs that crash your program are friendlier because they're obvious. Bugs that you don't notice are much more dangerous: one nagging question in every programmer's mind should be "is the program doing what I think it is doing?"
gdb. By far the most useful tool for finding bugs is a good debugger, a shell you can run a program under and set breakpoints, inspect the values of variables, etc. The best free debugger for Unix is probably gdb, available from the GNU ftp site. gdb seems unfriendly and confusing at first, but it is definitely worth your time to learn it.
The most important gdb commands are help: browse online help, where: show me a stack trace, where did we crash?, list: show me the source code where we are, break, set a breakpoint, and print: show me the value of of some expression. If your program is crashing on you, run it under gdb and look at the stack trace. If it looks to be buggy but you don't know where, start setting breakpoints and see when things go awry.
gdb and Objective C. Unfortunately, at this time gdb does not directly support Objective C. There are some workarounds that make debugging Objective C programs possible. They are based on the knowledge that Objective C is little more than a glorified syntax for structs (objects) and strange function names (methods).
defobj: xprint(), xprintid(), xfprint(), xfprintid(), xexec(), and xfexec(). Swarm also has a few functions defined that can be used to make debugging easier. In particular, the function xprint(object) prints out the class of an object, and xexec(object, "message") calls the message specified on the object. These can be invoked under gdb as call xprint(aHeatbug). Note that you can't pass arguments to a message, nor can you see the return value. There are also methods xfprint(collection) and xfexec(collection, "message") that print or exec foreach member of a collection.
gdb and Java. Swarm models written in Java will ultimately use the Swarm libraries which are still written in Objective C. The Java Native Interface (JNI) is the magic glue that binds these languages together. Thus if the crash occurs is the user (Java) portion of the code then the user is advised to use the standard Java debugging tools jdb and the like. (If a crash happens in the Java virtual machine (JVM), it should generally be clear from the error message that it is a Java-related problem). gdb is only useful when the crash occurs inside the Swarm libraries (i.e. outside the user's Java code). In this case you can invoke gdb in the following way:
$JAVASWARMGDB=gdb javaswarm StartModelName |
Defensive programming can help prevent a good number of bugs. When writing code, try to test it incrementally: make small changes whose effects you think you can predict and then test them. Don't outsmart yourself with cleverness: write code correctly first, then go in and hack it up if you need it to be more efficient. Put in sanity checks for conditions that shouldn't go wrong in normal usage, but might if you make a mistake.
-Wall. Swarm currently compiles all code with "gcc -Wall", which tells gcc to emit warnings for a lot of things that it wouldn't normally complain about. Warnings are not (necessarily) errors - warnings will be generated for legal code if gcc thinks that what you're doing could easily be a mistake. You might find this frustrating at first, but it helps catch a lot of common errors, including forgetting to include a prototype or forgetting to return a value from a function. Passing -Wall is good discipline.
nil_method. Objects in Objective C are essentially pointers to structs. So what happens if you send an object to the pointer 0x0, "nil" in Objective C parlance? Unfortunately, most implementations of Objective C, including gcc, define methods to nil as having no effect. The code:
aHeatbug = [Heatbug create: aZone]; aHeatbug = 0; // oops! bug. [aHeatbug setIdealTemperature: idealTemp]; |
will not generate any errors.
The reason this is unfortunate is that it's a common bug to trash a pointer accidentally, set it to 0. It would be nice if your program then crashed when you tried to send a message to that mangled object: instead, the message send will fail silently and the program will continue to execute. This can make it hard to find bugs.
There are two ways to make messages sent to nil crash your program. The simplest is to put a breakpoint on nil_method under gdb: nil_method is invoked every time a message is sent to nil. Alternately, you can make a copy of the libobjc runtime source and edit nil_method to do whatever you want. The source code in src/defobj contains a file objc.patch that patches the runtime from gcc 2.7.2.
The good news for Java programmers is that since Java is a strongly typed language, many of the pitfalls that beset Objective C programmers, never materialize in the Java context. Nevertheless the reader is cautioned to use as many of the standard defensive programming techniques as possible.