4 Bears Online

Comics | Jokes about India | Men and Women Jokes | Funny articles and essays | Only in America | More serious Articles and Essays |
Articles/News   | Readings   | Humor   | Travel/Hiking   | More/Informational Links  

Defensive programming

(c) 1995 andrew thomas-cramer
This document may be folded, spindled, or mutilated as desired, as long as the copyright notice and this restriction remain untouched.

[From the UNIVERSITY OF WISCONSIN-MADISON's Data-Structures Course (CS367) Homepage.]

Some problems can be prevented before they arise. Debugging is a curative approach; defensive programming is a holistic, preventative approach.

assert( condition ); HIGHLY RECOMMENDED FOR CS367.
This preprocessor statement can save you much time while you're testing the program. If the condition evaluates to true, execution continues normally. If the condition ever evaluates to false, the assert macro will print to stderr a message, such as:

Assertion failed: condition, file filename, line linenum

Then execution stops immediatately.

Typically, asserts are placed at the beginnings of functions, testing that their arguments satisfy limitations assumed:

#include ... real fun( Link *plink, int val ) { assert( pLink != NULL && val >= 0 );

With the inclusion of the assert, if the assumptions are violated, the program announces this, rather than skipping merrily along and generating unexpected behavior. The assert gives you a starting point: you know your assumptions were violated before the function was called. You can backtrack, perhaps with a debugger, to find the source of the error.

Asserts don't slow down the released software at all, by the way, if they're turned off. Defining the macro NDEBUG turns off asserts. This can be done easily as a compiler flag:

g++ -DNDEBUG train.C

This means that you should not hesitate to use asserts for concerns of efficiency, and also that they are effective during testing, but not after release of the software.

default: or else
Even if the switch or if-else-if selection construct has no default case, it may be useful to include it anyway, to announce an error if an "impossible" case has occurred.

switch( ws ) { case '\t': ... break; case ' ': ... break; default: cerr << "ERROR! Unexpected character in ws switch! Run for the hills!" << endl; break; } If you like, you can bracket this error-checking default case in #ifndef NDEBUG and #endif, so that it may be turned of in the release version similarly to asserts.

Documentation
Write documentation while developing your code. This improves the readibility of your program and clarifies your intent both during and after development.

#ifndef THISFILE_H
#define THISFILE_H 1
...
#endif
HIGHLY RECOMMENDED FOR CS367. Many header files cause compiler errors if they're #included more than once in the same source file. But it's common in larger programs for a header file to appear in more than one place in the #include tree -- it may be included by each of two other header files, or included by both a header file and the source file. The code above, bracketing the contents of a header file called thisfile.h, tells the preprocessor to #include the file only once, regardless how many times it's requested.

Including the dummy 1 after THISFILE_H should not be necessary, but some compilers have difficulty with macros defined to be white space.

Some compilers have special means to accomplish the same effect; e.g., flags and #pragmas. Such are nonstandard, however; the code above is portable to any compiler.

_new_handler
Your program should behave gracefully if an attempt to allocate memory fails. This can require you to include an if statement after every use of new: Link *p; ... p = new Link; if ( ! p ) { cerr << "Memory allocation failed." << endl; exit(-1); } If you have many allocation statements, this quickly becomes tiresome. An alternative allows you to use new without explicit tests.

If new fails, it checks a global variable _new_handler. If that variable has the value 0, as is the default, then new just returns 0. If that variable has the address of a function, however, new calls that function. Typically, the function prints an error message and performs any last-minute cleanup, such as saving files, before the program quits.

The following code sets the new handler in GNU g++:

#include void StandardNewHandler( ); int main() { set_new_handler( StandardNewHandler ); ... } void StandardNewHandler( ) { cerr << "Memory allocation failed." << endl; }

The set_new_handler() function is nonstandard. In other compilers you need to assign directly to the _new_handler variable. For example, in Symantec C++:

#include extern void (*_new_handler)(); // ( _new_handler is a pointer to a void // function which accepts no arguments ) int main() { _new_handler = StandardNewHandler; ... } void StandardNewHandler( ) { cerr << "Memory allocation failed." << endl; }

Note that you can make your program easily portable between two compilers through the use of -Dmacro in your Makefile, and #ifdef-#elif defined()-#endif in your code.

Style
Use a consistent, readible style in your programming.

Jan. 20, 1995 Andy Thomas-Cramer andrewt@cs.wisc.edu

http://www.4bearsonline.com/collections/cs/index.shtml