Concepts & Techniques Rule One:View exception handling as part of an error management strategy
Exceptions are not the only way of handling or reporting error conditions. Other
ways include;
When an error condition is detected, simply exit the program. This approach is often used in C programs when dynamic memory allocation fails.
Use return or error codes from functions rather than exceptions. This approach is commonly used in C and C++ for errors that occur with file input and output.
Both of these approaches may be used, whether exceptions are used or not. The
first approach is common in C programs as C does not support exceptions, but
less common in well-designed C++ code as an unhandled exception gives the same
effect, but also provides an opportunity to catch and recover from the error.
The second approach is appropriate if the error condition is not severe. This
is a double edged sword, as it requires the caller to deliberately check if an
error has occurred, but the error will be silently ignored if the caller forgets
to do that check. An error code is therefore appropriate if the program can sensibly
continue executing even if the error code is not checked. An example where error
codes are appropriately used is "end of file" (EOF) condition with C and C++
file input. This allows parsing of a file using a loop that repeats until EOF
is reached. Forgetting to check for EOF is also rarely a critical error (the
program can often continue sensibly afterwards). One of my pet peeves with the
Java I/O library is that it throws exceptions for some non-critical errors, forcing
code that reads from a file to be more complex (several local try/catch blocks)
than necessary.
It often makes sense to handle errors by different mechanisms. There is nothing
wrong with a program that makes use of multiple error reporting mechanisms. Essentially,
the errors encountered by an application fall into four categories.
Those that can be recovered from immediately, by the code that detects them.
Those that cannot be recovered from immediately, but do not cause chaos if
they are ignored.
Those that cannot be recovered from immediately, but will cause chaos if recovery is not initiated.
Those that are irrecoverable.
An example of the first would be a function that dereferences a pointer detecting
that the pointer is NULL. Such an error might be recovered from silently by doing
nothing if the pointer is NULL. This would only be appropriate if behaviour of
other code will not be adversely affected by our function silently takes no action.
An example of the second is file I/O. Encountering an EOF is often an expected,
and non-critical, event when reading a file.
An example of the third category is a function that detects failure of supplied
data to comply with a complex set of required pre-conditions, but has insufficient
information to be able to correct the data. Throwing an exception is appropriate
to return control to a caller that (presumably) provided the required data.
To develop a useful error handling strategy, it is necessary to characterise
the severity of errors, the urgency and feasibility of recovery, and whether
or not it is possible to correct an error at the point it is detected. If a particular
error falls neatly into one of the categories above, the approach to handle that
error will be obvious. A lot of errors will not fall neatly into only one category.
In this case, an analysis of of benefits and costs will determine options to
realistically handle the error.