Some notes on defensive programming Mark Moraes, moraes@cs.toronto.edu Program in modules -- declare everything in a module static unless you want it exported. Don't export variables, only procedural interfaces. Use header files to declare (and document?) exported functions. Always include a header file in a procedure when using functions it exports. Use Posix unistd.h for Unix functions; use a dummy version on systems that don't have one (eg. the one provided with Jove). Consider declaring prototypes (with a proto() macro?) and using a picky ANSI compiler to take advantage of them. gcc -W -O and lint can be good friends. Other compilers with excellent warnings include hc on the IBM PC/RT and Turbo C++ on the IBM PC. Use printfck to check printfs -- SGI Irix cc has this capability and prototypes. Check system call error returns. Use error() or strerror() for good error reporting. Don't #ifdef DEBUG. Use if (debug) instead, it may be worth planning for multiple debugging levels from the start. Letting each module have a debugging variable, like C news relaynews, is good practice. Use a debugging malloc when developing. If using CSRI_MALLOC, insert mal_verify(1) at suitable checkpoints if you can't afford mal_debug(3). When using routines that take pointers, check who is responsible for storage allocation. What is the lifetime of that storage? The use of automatics where you should be using static or malloced storage can cause hard-to-reproduce bugs. Document a test procedure, attempt to collect a test suite. Is it possible to run a test instance of your program in addition to the installed version? (i.e don't rely on a config file being installed in /etc/foobar -- allow runtime reconfiguration via a few environment variable or command line options) For daemons or complex multiprocess applications, plan for testing in captured, controlled environments. Think about taps for collection of real world test data for regression. Handle long lines gracefully. In general, dynamically allocate objects and avoid static limits. If you have static limits, check that you don't overrun them and recover gracefully from them. strsave(), str3save() are useful. Avoid #ifdefs in C code. They're a symptom of portability problems, not a solution. Instead, define a system dependent layer if your application is very system dependent, so it's easy to retarget it. If you have to #ifdef, use feature-based ifdefs rather than system #ifdefs. eg. use #ifdef HAS_DIRENT rather than #ifdef SYSV. The world now consists of hybrid systems and it's going to get worse. Write system dependent modules so that they can be re-used; it greatly eases pain when moving to a new system. Keep a Change Log for the code, indicating *WHY* you changed the things you changed in addition to what you changed. When working on code that exec's other programs, use Hugh Redelmeier's forktest (mod.sources/comp.sources.unix, Vol 7, Issue 90) to check the uids, open file descriptors, signal states etc. Mark.