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:

  It is truly abstract, free from the implementation details of what it describes but concentrating on its functionality.
  Instead of being produced separately—an unrealistic requirement, hard to impose on developers initially and becoming impossible in practice if we expect the documentation to remain up to date as the software evolves—the documentation is extracted from the software itself. It is not a separate product but a different view of the same product. This prolongs the single product principle that lies at the basis of Eiffel’s seamless development (section 9.3.1).

Other views are possible. For example, the EiffelCase tool of ISE’s environment and a cluster-level tool integrated in Visual Eiffel propose graphical “bubble-and-arrow” diagram representations of system structures, showing classes and their relations—client and inheritance—according 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 form—or 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.

9.8.6. Exception Handling

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:

  Any routine has a contract to achieve.
  Its body defines a strategy to achieve it—a sequence (or other control structure) involving instructions. Some of these operations are themselves routines with their own contracts, but even an atomic operation, such as the computation of an arithmetic operation, has an implicit contract, stating that the result is representable.
  Any one of these operations may fail, that is, be unable to meet its contract; for example, an arithmetic operation may produce an overflow (non-representable result).
  Failure of one of these operations is an exception for the routine.
  As a result, the routine may fail too—causing an exception in its own caller.

Note how the two basic concepts, failure and exception, are defined precisely. Although failure is the more basic concept—because it is defined for atomic, non-routine operations—the definitions are mutually recursive because an exception may cause a failure of the recipient routine, and a routine’s 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