May 2002
Exceptions
Introduction
This document explores the optimal semantics for exception handling.
Exception Semantics are Brief
Exceptions are handled by branching the execution of the code. Legitimate return values could also having the same branching effect. We want exception branches be handled different than legitimate branches because the latter is more important to understanding what the program intends to do. Let us look at a function that returns a value, and how that value determines the flow of execution:
if (x=1){
//option 1
}else if (x=2){
//option2
}else if (x is Exception){
//option 3
}//endif
If we allow functions to return exceptions we could write our exception handler like above. This case appears quite clean without the help of exception handling semantics. It is when the return value, under normal circumstances, does not determine the flow of execution that showing the exception handler appears messy. You can imagine how this can become unwieldy if there are many types of exceptions that must be checked.
if (x is Exception){
//exception handler
}else{
//normal execution
}//endif
When a series of instructions that can throw exceptions do not alter the "normal" path of execution, handling errors gets very messy.
Structured Programming and Error Handling
Catching exceptions with classic structured programming is not pleasant. Below are three examples. The first will be an excerpt from some Java code, showing structured programming. The second being the same, but using Goto. The third using existing exception handling semantics.
Structured Example
if (record!=null){
Class classs=record.getClasss();
if (classs!=null){
//DO REAL WORK
}//endif
}//endif
//CONTINUE
Structured programming, without exceptions, leads to an indent nightmare; the meaning of the code is lost. Once the indentation becomes overpowering, programmers resort to other methods to improve the look. One is to not indent; a little dangerous. Another is adding temporary state variables; and this will confuse code. The last is breaking the method into many helpers, scattering implementation.
Goto Example
if (record==null) goto END;
Class classs=record.getClasss();
if (classs==null) goto END;
//DO REAL WORK
END:
//CONTINUE
The Goto example is better. It is very much like the Exception Handler, but does not hide the error identification code
Exception Handler Example
Class classs=PrimitiveFunction.getRecord().getClasss();
//DO REAL WORK
}catch (NullPointerException exception){
//DO NOTHING
}//catch
//CONTINUE
Exception semantics involve blocking a set of instructions and giving them a single exception handler. This is semantically efficient.
If there is still doubt that exceptions are useful, we are reminded of the benefits we see so far: Exception handling reduces the size of the source code, with the added benefit of emphasizing the primary code path. These two benefits are sufficient to conclude that exception semantics are useful.
Exception Types
Exceptions appear to be the result of differentiating between "normal" and "abnormal" operating conditions. The advantage of this distinction is to remove error handling from the code that handles normal operation. We will further partition our exceptions into types so to provide better semantics for each..
We identify two aspects of exception handlers, the first is whether the exception handling is trivial, and the second is where they resume control. This makes for four different exception types: "Exit", "Abort", "Correction" and "Ignore". Each will be introduced in existing exception handling semantics, then we will discuss the best semantics for each.
Exit
The normal code has thrown an exception that makes the rest
of the function irrelevant. We would like to do some cleanup and exit the
function gracefully.
//DO WORK
}catch (Exception exception){
//DO SOME CLEANUP
return;
}//catch
//CONTINUE
Abort (aka Runtime Exception)
The Abort exception type is a
simple version of the Exit exception type. We, cancel all execution of the
current frame and return an exception to the caller.
//DO WORK
}catch (Exception exception){
return exception;
}//catch
//CONTINUE
Correction
If an exception is to be handled, and corrected, we
will have to explicitly mention how we are going to do the correction. This
means detailing both paths: one for normal operation, and the other for
exception handling.
//DO NORMAL OPERATION
}catch (Exception exception){
//DO CORRECTION
}//catch
//CONTINUE
Ignore
Like Correction, but there is nothing to correct. It is
useful for prototyping and for those few legitimate cases where only side
effects are important.
//DO WORK
}catch (Exception exception){
//DO NOTHING
}//catch
//CONTINUE
Further Brevity
We now look at the particular semantic optimizations that can be made for each of the exception types. We start with the simplest handlers first
The Abort exception type is the most common, and all instances are handled in the same way. We can completely remove any mention of exceptional program flow from the code and leave the exception handling implicit (default). We show an entire function below to stress the complete lack of exception handling code.
//DO WORK
}//MyFunction
The Ignore exception type is also trivial; mentioning how to handle the exception is identical to Abort (do nothing). We only have to indicate where to continue in the event of an exception. In this first case, we can ignore any exceptions that appear in a single line of code
//TRY A LINE OF CODE
//CONTINUE
//TRY ANOTHER LINE OF CODE
//CONTINUE
In the second case we may have a block of code that must be run as a transaction, execution must resume right after that, exception or not.
//DO OPERATIONS AS TRANSACTION
END Exception
//CONTINUE
The Exit exception type only has to indicate the actions needed to cleanup before exiting. Therefore the exit code can be completely separated from the normal operations.
//DO SOME CLEANUP
RETURN; //AND EXIT
END Exception
//DO NORMAL OPERATION
The CATCH/DO statements are not executed in lexical order, but rather executed in the event of an exception in any code following. The CATCH/DO stament has implicit lexical scope identical to variable declarations. We may want to handle the same exception in different ways in different points in the code. In this case blocking is unavoidable, but indenting is not:
//DO SOME CLEANUP AND EXIT
TRY
//DO NORMAL OPERATION
END Exception
CATCH (e AS Exception) DO
//DO SOME OTHER CLEANUP AND EXIT
END Exception
//CONTINUE
The Correction exception type has too many links with the original code to be far removed from it. We must default back to existing exception handling semantics. We could reverse the order of the normal code from the exceptional code (below), but from a logical point of view it does not matter.
//DO CORRECTION
TRY
//DO NORMAL OPERATION
END Exception
//CONTINUE
The order of TRY and DO does not matter, therefore the following is also valid.
//DO NORMAL OPERATION
DO
//DO CORRECTION
END Exception
//CONTINUE
Summary
Exception handling semantics have been minimized by identifying exception types and optimizing semantics for each.
Update
may 2002, improved clarity
feb 2000, first writing