What is the Problem?
Classes have their public interfaces, which traditionally have simply specified what happens under normal circumstances.
But it’s important to define how things work under exceptional conditions, as well.
Traditionally, C programmers have used a large set of inconsistent approaches towards indicating success or failure. Often, by returning special values from a function call – but the types of values vary all over the place!
For example:
FILE *fp = fopen("myfile.dat", "r");
if (fp == NULL)
fprintf(stderr, "Failed to open file\n");or:
int ch = getchar();
if (ch == EOF)
exit(0);and so on…
But this approach has problems:
- It’s difficult to find an appropriate return code value (
EOF,NULL,-1,0, … ) for a function (e.g.atoi()) - Can produce problems with types (e.g.
getchar()) - A single return code needs to be augmented (e.g.
errno) - Checking for exceptional conditions tends to obscure the algorithm for the normal case, and is error-prone.
- Sometimes the code for error checking consumes more space than the normal code.
- Most Important: It’s much too easy for programmers (even well-intentioned ones) to ‘forget’ to check the return code.
Exceptions: A Solution
For these reasons, some languages, such as Ada, have a feature called exception handling:
declare
LOW_FLUID_LEVEL : exception
begin
RUN_ENGINE; -- a procedure call
exception
when LOW_FLUID_LEVEL =>
OPEN_VALVE;
SOUND_ALARM;
when NNUMERIC_ERROR =>
CLOSE_VALVE;
raise;
when others =>
LOG_UNKNOWN_ERROR;
end;Exceptions are not necessarily errors
Note that exceptions are not necessarily errors – they are exceptional or unusual conditions that are not handled in the normal in-line code.
In Ada, exceptions are a built-in type:
- an exception must be declared before it may be used (some are predeclared, built into the compiler)
- an exception may be raised (explicitly or implicitly)
- an exception may be handled by one or more exception handlers
- An exception has scope, like any other declaration
Nested Exception Handling
Exceptions may be raised in inner scopes, and handled in outer scopes.
Here, in nested blocks (again, in Ada):
procedure MAIN is
begin
...
declare
LOCAL_ERROR : exception; -- exception is a type
begin
...
exception -- an exception block
when LOCAL_ERROR =>
DO_SOMETHING;
end;
...
exception -- another exception block
when CONSTRAINT_ERROR =>
DO_SOMETHING_ELSE;
when NUMERIC_ERROR =>
DO_SOMETHING_MORE;
end MAIN;LOCAL_ERROR is handled by the exception handler in the inner block, but built-in exceptions CONSTRAINT_ERROR and NUMERIC_ERROR are handled in the outer block.
Exceptions in Nested Functions
Here, in nested functions (Ada, again):
procedure MAIN is
...
type SMALL is digits 5 range 0.0..10.0;
function INVERSE(I : float) return SMALL is
begin
return SMALL(1.0/I);
exception
when NUMERIC_ERROR =>
return 10.0;
end INVERSE;
...
begin
...
Y := INVERSE(X);
...
exception
when CONSTRAINT_ERROR =>
DO_SOMETHING;
end MAIN;If we call INVERSE(0.0), the division by zero raises an exception NUMERIC_ERROR, which is handled locally and converted to a return value of 10.0
If we call INVERSE(0.0001), the resulting value is not within the range of a SMALL, and when we attempt to return it, a CONSTRAINT_ERROR is raised, which is handled outside the function.
Exception Handling Choices
With exceptions, you have the following choices:
- Handle the exception
- Don’t handle it – in this case, the program will exit abnormally, when the exception handling facility fails to find an exception handler for the exception.
When you decide to handle the exception, you have the following choices:
- Abandon the program (last resort – not user-friendly!)
- Try the operation again (e.g. when interacting with a user)
- Use an alternative approach (e.g. have extra redundancy in the program to handle such conditions)
- Repair the cause of the error and try again.
Without exceptions, you can ignore a problem (intentionally, or otherwise), but it’s not a good idea.
With exceptions, it’s not so easy to just ignore the problem. If you really want to ignore an exception you have to do it explicitly. (Not only is this good practice, it helps the programmer keep track of things.)