Whenever there is a contract, the risk exists that someone will break
it. This is where exceptions come in.
Exceptions -- contract violations -- may arise from several causes.
One is assertion violations, if assertions are monitored. Another is the
occurrence of a signal triggered by the hardware or operating system to
indicate an abnormal condition such as arithmetic overflow or lack of memory
to create a new object.
Unless a routine has made specific provision to handle exceptions, it
will fail if an exception arises during its execution. Failure of
a routine is a third cause of exception: a routine that fails triggers
an exception in its caller.
A routine may, however, handle an exception through a rescue
clause. This optional clause attempts to "patch things
up" by bringing the current object to a stable state (one satisfying
the class invariant). Then it can terminate in either of two ways:
The rescue clause may execute a
retry instruction, which causes the
routine to restart its execution from the beginning, attempting again to
fulfil its contract, usually through another strategy. This assumes that
the instructions of the rescue clause,
before the retry , have attempted to
correct the cause of the exception.
If the rescue clause does not end
with retry , then the routine fails:
it returns to its caller, immediately signaling an exception. (The caller's
rescue clause will be executed according
to the same rules.)
The principle is that a routine must either succeed or fail:
either it fulfils its contract, or it does not; in the latter case it must
notify its caller by triggering an exception.
Usually, only a few routines of a system will include explicit rescue
clauses. An exception occurring during the execution of a routine
with no rescue clause will trigger
a predefined rescue procedure, which does nothing, and so will cause the
routine to fail immediately, propagating the exception to the routine's
caller.
An example using the exception mechanism is a routine attempt_transmission
which tries to transmit a message over a phone line. The actual transmission
is performed by an external, low-level routine transmit; once started,
however, transmit may abruptly fail, triggering an exception, if
the line is disconnected. Routine attempt_transmission tries the
transmission at most 50 times; before returning to its caller, it sets
a boolean attribute successful to true or false
depending on the outcome. Here is the text of the routine:
- attempt_transmission (message: STRING) is
- -- Try to transmit message,
at most 50 times.
- -- Set successful
accordingly.
- local
- failures: INTEGER
- do
- if failures < 50 then
- transmit (message); successful := true
- else
- successful := false
- end
- rescue
- failures := failures + 1; retry
- end
Initialization rules ensure that failures, a local entity, is
set to zero on entry.
This example illustrates the simplicity of the mechanism: the rescue
clause never attempts to achieve the routine's original intent;
this is the sole responsibility of the body (the do
clause). The only role of the rescue
clause is to clean up the objects involved, and then either
to fail or to retry.
This disciplined exception mechanism is essential for software developers,
who need protection against unexpected events, but cannot be expected to
sacrifice safety and simplicity to pay for this protection.