Discussion
Team LiB
Previous Section Next Section

Discussion

It is crucial to crisply distinguish between errors and non-errors in terms of their effects on functions, especially for the purpose of defining safety guarantees (see Item 71). The key words in this Item are precondition, postcondition, and invariant.

The function is the basic unit of work, no matter whether you are programming C++ in a structured style, an OO style, or a generic style. A function makes assumptions about its starting state (documented as its preconditions, which the caller is responsible for fulfilling and the callee is responsible for validating) and performs one or more actions (documented as its results or postconditions, which the function as callee is responsible for fulfilling). A function may share responsibility for maintaining one or more invariants. In particular, a nonprivate mutating member function is by definition a unit of work on its object, and must take the object from one valid invariant-preserving state to another; during the body of the member function, the object's invariants can be (and nearly always must be) broken, and that is fine and normal as long as they are reestablished by the end of the member function. Higher-level functions compose lower-level functions into larger units of work.

An error is any failure that prevents a function from succeeding. There are three kinds:

  • Violation of, or failure to achieve, a precondition: The function detects a violation of one its own preconditions (e.g., a parameter or state restriction), or encounters a condition that prevents it from meeting a precondition of another essential function that must be called.

  • Failure to achieve a postcondition: The function encounters a condition that prevents it from establishing one of its own postconditions. If the function has a return value, producing a valid return value object is a postcondition.

  • Failure to reestablish an invariant: The function encounters a condition that prevents it from reestablishing an invariant that it is responsible for maintaining. This is a special kind of postcondition that applies particularly to member functions; an essential postcondition of every nonprivate member function is that it must reestablish its class's invariants. (See [Stroustrup00] §E.2.)

Any other condition is not an error and therefore should not be reported as an error. (See Examples.)

The code that could cause an error is responsible for detecting and reporting the error. In particular, the caller should detect and report when it cannot meet a to-be-called function's preconditions (especially ones the callee documents that it will not check, such as vector::operator[] which does not promise to range-check its argument). Because the called function cannot rely on callers to be well-behaved, however, the called function ought still to validate its preconditions and to report violations by emitting an erroror, if the function is internal to (only callable from within) the module, so that any precondition violation is by definition an error in the module's programming, by asserting (see Item 68). This is defensive programming.

A word of caution about specifying a function's preconditions: A condition should be a precondition of a function f if and only if it is reasonable to expect all callers to check and verify the condition's validity before calling f. For example, it is wrong for a function to state a precondition that can only be checked by doing some of the function's own substantial work, or by accessing private information; that work should stay in the function and not be duplicated in the caller.

For example, a function that takes a string containing a file name would not normally make the file's existence a precondition, because callers cannot reliably guarantee that the file exists without taking a lock on the file (if they only check for the file's existence without a lock, another user or process could delete or rename the file between the caller's check and the callee's attempt to open). One correct way to make the file's existence a precondition is to require the caller to open the file and make the function's parameter an ifstream or equivalent (which is also safer, because it works at a higher level of abstraction) instead of passing a bald file name as a raw string. Many preconditions can thus be replaced by stronger typing, which turns run-time errors into compile-time errors (see Item 14).

    Team LiB
    Previous Section Next Section