Lecture 19: Exception Handling

Keep the code for the elegant, interesting, algorithmic main
(non-exceptional) flow of the program uncontaminated by
the ugly, boring
list of special cases that constitutes the exception handling.

Provide for rapid unwinding of code. That is, in many cases,
you're deep inside some sophisticated, complex algorithm, and then
an error comes along that makes nonsense of the whole thing, and the only
thing to throw the whole thing away. You want the low-level routine
to be able to signal this without including a test for an error
condition at each function call on the call sequence.

The extreme, but common, case of (2) is that the error is
irrecoverable; you have no idea what to do. In that case, you want the
low-level routine to be able to signal that the program should be
terminated with extreme prejudice.

Patch things up in the course of unwinding. The abrupt termination
is liable to leave data structures or even files or devices in an
inconsistent state. If possible, these should be fixed.

Transmit useful information through this unwinding of code. That is:
successful error recovery often involves combining information from
the low-level routine, where things went wrong, with information
from the high-level routine, which knows the larger context. This is
not a thing that programming languages are very good at; the whole
thrust of top-down programming, information hiding etc. is that
low-level routines do what they do without needing to know the high-level
context, and that high-level routines can count on low-level routines
doing the right thing without knowing how that is implemented. Exception
handling features often include some mechanism for this; but it is far
from a solved problem. This is one of the fundamental reasons why error
messages from complex software (e.g. compilers) tend to be hard to
interpret.

Programming language design and software engineering is largely about
making things elegant. Exception handling is pretty much inescapably ugly.
The exception handling features give some leverage over this ugliness, but
mostly it's still ugly.

Exception Handling in Java

User-defined class of exception

class MyEmptyStack extends Exception {}

Throwing (raising) an exception

throw new MyEmptyStack()

Catching an exception

When the try is executed,
the body block1 is executed. If an exception is thrown while
executing block1, then it jumps immediately to the catch clauses.
The first catch clause whose argument class matches the class of
the exception thrown is executed. (More detail below.)

The finally clause is always executed at the
end of the try,

if no exception has been thrown

if an exception has been thrown and caught

if an exception has been thrown and not caught

even if a return statement has been executed.

Useful for clean-up that must always be done; e.g. release a resource or
close a file.

The exception thrown must be a subclass of the exception declaration of the
catch, so that the variable in the catch can be bound to the exception
thrown.

Finding the right catcher

If exception E is thrown in method M

Work lexically out through try blocks in M to find a
catch clause with a matching argument.

If there is no such catch clause in M, then

M terminates, after
executing the applicable finally clauses:

go to where M was called, in method P

work lexically out through try blocks in P, looking for a
matching catch clause

Iterate back through callers.

If you get back to main and no one has caught the error,
then terminate.

If R throws but does not catch E,
P calls R and P is declared as throwing
E, then the call to R in P does not
have to be in a try block.

Nice consequence for software engineering

Suppose that you are writing a mid-level routine Q.
Q calls R
which may throw an exception E, and Q is called by
P.

You know that you have to make some decision about E
because R is declared as throwing E. If you don't
bother to look up the declaration of E, the Java compiler will
do that for you, and, if you don't either

put the call to R in a try block with a catcher
for E; or

declare Q as throwing E

then the compiler will complain.

If you can figure out what Q should do about E, then
you can write a catcher for it.

If not, you can pass the buck to whatever programmer is in charge of
P.

Of course, if you're sure that E can't actually occur
in this context (often the case), then just provide a catcher for E
with a null body, so that other methods can call Q without
worrying about it.

Complex recovery

Suppose that P calls Q calls R calls S
which throw exception E,
and that there are now a whole collection of things that have to be done:
S has the information to do some of them,
R has the information to do some of them,
Q has the information to do some of them, and
P has the information to do some of them. There are two ways
to deal with this. With both methods, all the calls involved and the
throw have to
be inside try blocks.

Raise the exception in S, have the catcher for E
in P, and have finally clauses in each of
S, R, Q, P that do what needs to be done. But note that these are
always executed whether or not E is raised, so
this is not always applicable.

In each of S,R,Q,P have a catcher for E
that first does what needs to be done and then throws E
again. (A throw inside the body of a catch is not caught by that
same catcher.) Note that this works even if you are dealing with a recursive
routine, and you have to do something at each level of the recursion; e.g.
if P, Q, R, and S are all the same method.

Runtime exceptions

Many library functions and built-in operators throw an exception of class
Runtime Exception. Examples
Dividing by zero throws an ArithmeticException .
Applying Integer.parseInt(s) to a non-integer argument throws a
NumberFormatException.

These are unchecked exceptions: that is, the calling function
does not have to either catch them or declare itself as throwing them.
(If they did, Java programming would be hopeless.) However, you can
provide a catcher for them.

Programmers can define their own unchecked exceptions, by defining
the exception to be an extension of RuntimeException. However,
this is generally advised against.

IOException

A lot of the basic Java functions for reading from files or devices throw
the exception IOException. This is a checked exception, so you
have to provide a handler.

Examples

A generic stack that throws errors for popping an empty stack and overflowing.