Exception Mechanism
Motivation: Concerning Robustness
The notion of software correctness that we saw in Design by Contract and Assertions is half of the formula for software reliability. Correctness covers what the software is supposed to do, that is, its specification.
Of course,there is always a potential for things to go out of the bounds of the specification. This happens, for exampleif a client makes a call to a routine from a state in which the routine's precondition is not true.
How well software responds to situations which outside specification is called robustness. Together correctness and robustness define software reliability.
Consequences of Contracts
In the presence of Design by Contract, what happens to running software boils down to a simple rule:
A routine callcan complete in one of only two ways:
- The routine fulfills its contract.
- The routine fails to fulfill its contract.
As a follow-on, we can add that:
- Any routine that fails to fulfill its contract must cause an exception in its caller.
Reacting to Exceptions
Again, because of Design by Contract, we can state the following rule for dealing with exceptions:
A routine that incurs an exeception can react in one of only two ways:
- It can return the instance to a stable state and retry the entire routine with the same or a different strategy.
- It can fail, causing an exception in its caller.
There is an Eiffel mechanism called the rescue clause which facilitates the first alternative.
The Rescue Clause
The rescue
clause is part of the routine structure we saw in Adding Class Features . The rescue clause is a sequence of instructions introduced by the keyword "rescue
". At the point that an exception occurs, the processing of the normal instructions in the routine body will cease, and the instructions in the rescue clause will be executed instead.
If the instructions in the rescue clause can set things up so that it might prove fruitful to attempt to retry the routine, it can do so by issuing a retry
instruction. When the retry
instruction is executed, the routine is restarted from its beginning, although local entities are not re-initialized. You will see why in the example below.
If the rescue clause exits without issuing a retry
instruction, then the routine fails.
It should be noted that rescue clauses and retry instructions are not something that are used commonly. Out of the approximately 2000 classes in the delivered Eiffel libraries, there are only 16 occurrences. Many of these are oriented toward network and database operations for which some reasonable recovery might be possible. transmit: (p: PACKET)
-- Transmit packet `p'
require
packet_not_void: p /= Void
local
current_retries: INTEGER
r: RANDOM_NUMBER_GENERATOR
do
line.send (p)
rescue
if current_retries < max_retries then
r.next
wait_millisecs (r.value_between (20, 500))
current_retries := current_retries + 1
retry
end
end
In the example above, rescue
is used to recover from a situation in which an exception occurs in trying to send a packet. When the exception occurs the rescue clause will, if the maximum number of retries has not been reached, wait for some random length of time. Then, after having updated the number of retries, it will issue the retry
instruction. If the maximum number of retries is reached, the rescue clause will exit without executing the retry, constituting a failure.