Software design must include error handling from the beginning,
not as an afterthought. If the design includes declared
"checked" exceptions, as in Java, then correcting any
oversights may require painful refactoring. There is no
guarantee new exceptions can be handled properly without
introducing major contradictions in the code. Only extensive
testing can uncover all possible exceptional conditions. We do
not want to make this chore more painful than necessary.
You catch a specific checked exception (such as an
"IOException") because you know how to work around the problem.
If not, your method declares the exception to be thrown to a
client who might know how to work around it. Sometimes you do
both because you can clean up part of the mess before
rethrowing. Sometimes you catch a specific "FooException" and
rethrow as a specific "BarException" more appropriate to the
context.
These seem like easy enough rules to follow. Right?
Most everyone knows better than to catch, log, and then simply
ignore an exception. An error has not been handled just
because it was documented. No one looks at logs of stack
traces until after a program has crashed for other reasons.
But there are some implied rules we must also follow:
o Avoid catching a generic "Exception," "RuntimeException," or
"Throwable." A generic exception can arise from too many
sources. If you must catch a generic exception in a context
that does not allow any exceptions, then arrange to have it
rethrown elsewhere. Otherwise, you might as well terminate the
process with a useful fatal error message.
o Do not simply wrap an exception and rethrow as a generic
"RuntimeException", unless your intention is to crash the
process. A checked exception must be declared for clients, or
it must be fixed in the handler. There are no safe
alternatives.
Unchecked RuntimeExceptions are expected to be avoidable.
The programmer failed to foresee a common situation, so the
remaining logic is likely to be flawed. These bugs need to be
uncovered as soon as possible in tests. Think through the
possibilities: An ArrayIndexOutOfBoundsException could have
been avoided by checking the index first. An object should be
checked with instanceof to avoid a ClassCastException. And so
on for NullPointerException and IllegalArgumentException. Does
your exception belong to this category? Even if it does, you
still want to throw a specific unchecked exception. No one
inspects the causes of generic exceptions.
If you throw a generic exception, then someone must catch it.
Why is catching a generic exception so troublesome? You may
think you know the cause, but there are always more obscure
causes possible. An ignored InterruptedException might prevent
your process from shutting down properly. What if it is a
NullPointerException from an uninitialized member variable?
Then you have just caught and hidden a bug. Buggy race
conditions in threaded code may fail only occasionally.
Data-dependent errors may only show up during beta testing.
GUI Listeners or threaded Runnables often implement methods
that cannot declare exceptions. This is probably the single
most common location for caught and ignored exceptions, and one
of the most dangerous. In this case, you can catch a specific
FooException, save it, then handle it from another thread.
When running client-implemented code, you might catch specific
RuntimeExceptions, such as an ArithmeticException, if you do
think you know how to recover from the problem. Just avoid
catching generic exceptions that could be caused by a general
problem with the process. It is still better to crash early
and often than to let a client remain unaware of an error that
can be fixed. Generic RuntimeExceptions should only be caught
at the highest level of the program, as in a
``ThreadGroup#uncaughtException,'' and then only for cleanup
and useful logging.
If you inherit code that already hides too many exceptions,
then expect to go through a painful refactoring, adding
declarations and deciding how far they must go before they can
be handled correctly. Let them go all the way to main() before
you let yourself catch and ignore one. Eventually you will
decide what must be done. Let your unhandled exceptions remind
you.
Let us review. Why is the following code such a problem?
=>
public Foo getFoo() {
try {
return fooSource.getFoo();
} catch (Exception e) {
return null;
}
}
<=
You might have seen a MissingResourceException or
NoSuchElementException, so you felt a null result would be just
as informative. What if you accidentally caught and hid a
ConcurrentModificationException or a SQLException? Those could
be bugs that left your application in a broken state. Your
client might only see odd side-effects, or a crash five minutes
later. How are you going to debug that?
(You may disagree philosophically with the intention of
declared exceptions and prefer all exceptions to be unchecked
and undeclared like RuntimeExceptions. Too bad. If you use
only RuntimeExceptions, you are inventing a non-standard Java
style that will not integrate well with others. You will force
clients to catch all passing RuntimeExceptions and examine
their source.
http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html
)
Here is a suggestion for stabilizing an application that
already has poor error handling:
[[Stabilizing_a_large_interactive_Java_application.html]].
Bill Harlan, Feb 2009