Previous | Table of Contents | Next |
The result is also particularly interesting because it satisfies the property that should always be required of good software documentation:
Other views are possible. For example, the EiffelCase tool of ISEs environment and a cluster-level tool integrated in Visual Eiffel propose graphical bubble-and-arrow diagram representations of system structures, showing classes and their relationsclient and inheritanceaccording to the conventions of BON (the business object notation) with, in the first case, the possibility to edit these diagrams and generate updated Eiffel text in accordance with the principles of seamlessness and reversibility.
The short formor its variant, the flat-short form, which takes account of inheritance (section 9.9.12)is the standard form of library documentation, used extensively, for example, in the book Reusable Software (Meyer, 1994). Assertions play a central role in such documentation by expressing the terms of the contract. As demonstrated a contrario by the widely publicized $500-million crash of the Ariane-5 rocket launcher in June 1996 due to the incorrect reuse of a software module from the Ariane-4 project, reuse without a contract documentation is the path to disaster. Non-reuse would, in fact, be preferable.
Another application of design by contract governs the handling of unexpected cases. The vagueness of many discussions of this topic follows from the lack of a precise definition of terms such as exception. With design by contract, we are in a position to be specific:
Note how the two basic concepts, failure and exception, are defined precisely. Although failure is the more basic conceptbecause it is defined for atomic, non-routine operationsthe definitions are mutually recursive because an exception may cause a failure of the recipient routine, and a routines failure causes an exception in its own caller.
Why only the observation that an exception may cause a failure? The reason is that a routine may have planned for the exception and defined a rescue policy. This is done through a clause with the corresponding keyword, as in
read_next_character (f: FILE) is -- Make next character available in last_character; -- if impossible, set failed to True. require readable: file.readable local impossible: BOOLEAN do if impossible then failed := True else last_character := low_level_read_function (f) end rescue impossible := True retry end
This example includes the only two constructs needed for exception handling: rescue and retry. The retry instruction is only permitted in a rescue clause; its effect is to start again the execution of the routine, without repeating the initialization of local entities (such as impossible in the example, which was initialized to False on first entry). Features failed and last_character are assumed to be attributes of the enclosing class.
This example is typical of the use of exceptions: as a last resort, for situations that should not occur. The routine has a precondition, file.readable, which ascertains that the file exists and is accessible for reading characters. Clients should check that everything is fine before calling the routine. Although this check is almost always a guarantee of success, a rare combination of circumstances could cause a change of file status (because a user or some other system is manipulating the file) between the check for readable and the call to low_level_read_function. If we assume this latter function fails if the file is not readable, we must catch the exception.
A variant is
local attempts: INTEGER do if attempts < Max_attempts then last_character := low_level_read_function (f) else failed := True end rescue attempts := attempts + 1 retry end
which tries again up to Max_attempts times before giving up.
The preceding routine, in either variant, never fails: It always fulfills its contract, which states that it should either read a character or set failed to record its inability to do so. In contrast, consider the variant
local attempts: INTEGER do last_character := low_level_read_function (f) end rescue attempts := attempts + 1 if attempts < Max_attempts then retry end end
with no more role for failed. In this case, after Max_attempts unsuccessful attempts, the routine executes its rescue clause to the end, with no retry (the if having no else clause). This is how a routine fails. As noted, it passes on the exception to its caller.
Previous | Table of Contents | Next |