Constraint Rules

Constraint Rules

Much of what you are doing in drawing a class diagram is indicating constraints.

Figure 4-2 indicates that an Order can be placed only by a single Customer. The diagram also implies that each Line Item is thought of separately: You say 40 brown widgets, 40 blue widgets, and 40 red widgets, not 40 red, blue, and brown widgets. Further, the diagram says that Corporate Customers have credit limits but Personal Customers do not.

The basic constructs of association, attribute, and generalization do much to specify important constraints, but they cannot indicate every constraint. These constraints still need to be captured; the class diagram is a good place to do that.

The UML allows you to use anything to describe constraints. The only rule is that you put them inside braces ({}). I like using an informal English, emphasizing readability. The UML also provides a formal Object Constraint Language (OCL); see Warmer and Kleppe (1998).

Ideally, rules should be implemented as assertions in your programming language. These correspond with the Design by Contract notion of invariants (see sidebar).

Design by Contract

Design by Contract is a design technique developed by Bertrand Meyer. The technique is a central feature of the Eiffel language he developed. Design by Contract is not specific to Eiffel, however; it is a valuable technique that can be used with any programming language.

At the heart of Design by Contract is the assertion. An assertion is a Boolean statement that should never be false and, therefore, will only be false because of a bug. Typically, assertions are checked only during debug and are not checked during production execution. Indeed, a program should never assume that assertions are being checked.

Design by Contract uses three kinds of assertions: post-conditions, pre-conditions, and invariants.

Pre-conditions and post-conditions apply to operations. A post-condition is a statement of what the world should look like after execution of an operation. For instance, if we define the operation "square root" on a number, the post-condition would take the form input = result * result, where result is the output and input is the input value. The post-condition is a useful way of saying what we do without saying how we do itin other words, of separating interface from implementation.

A pre-condition is a statement of how we expect the world to be before we execute an operation. We might define a pre-condition for the "square root" operation of input > = 0. Such a pre-condition says that it is an error to invoke "square root" on a negative number and that the consequences of doing so are undefined.

On first glance, this seems a bad idea, because we should put some check somewhere to ensure that "square root" is invoked properly. The important question is who is responsible for doing so.

The pre-condition makes it explicit that the caller is responsible for checking. Without this explicit statement of responsibilities, we can get either too little checking (because both parties assume that the other is responsible) or too much (both parties check). Too much checking is a bad thing, because it leads to lots of duplicate checking code, which can significantly increase the complexity of a program. Being explicit about who is responsible helps to reduce this complexity. The danger that the caller forgets to check is reduced by the fact that assertions are usually checked during debugging and testing.

From these definitions of pre-condition and post-condition, we can see a strong definition of the term exception, which occurs when an operation is invoked with its pre-condition satisfied, yet it cannot return with its post-condition satisfied.

An invariant is an assertion about a class. For instance, an Account class may have an invariant that says that == sum(entries.amount()). The invariant is "always" true for all instances of the class. Here, "always" means "whenever the object is available to have an operation invoked on it."

In essence, this means that the invariant is added to pre-conditions and post-conditions associated with all public operations of the given class. The invariant may become false during execution of a method, but it should be restored to true by the time any other object can do anything to the receiver.

Assertions can play a unique role in subclassing.

One of the dangers of polymorphism is that you could redefine a subclass's operations to be inconsistent with the superclass's operations. Assertions stop you from doing this. The invariants and post-conditions of a class must apply to all subclasses. The subclasses can choose to strengthen these assertions, but they cannot weaken them. The pre-condition, on the other hand, cannot be strengthened but may be weakened.

This looks odd at first, but it is important to allow dynamic binding. You should always be able to treat a subclass object as if it were an instance of the superclass (per the principle of substitutability). If a subclass strengthened its pre-condition, then a superclass operation could fail when applied to the subclass.

Essentially, assertions can only increase the responsibilities of the subclass. Pre-conditions are a statement of passing a responsibility on to the caller; you increase the responsibilities of a class by weakening a pre-condition. In practice, all of this allows much better control of subclassing and helps you to ensure that subclasses behave properly.

Ideally, assertions should be included in the code as part of the interface definition. Compilers should be able to turn assertion checking on for debugging and remove it for production use. Various stages of assertion checking can be used. Pre-conditions often give you the best chances of catching errors for the least amount of processing overhead.

When to Use Design by Contract

Design by Contract is a valuable technique in building clear interfaces.

Only Eiffel supports assertions as part of its language, but Eiffel is, unfortunately, not a widely used language. It is straightforward, if awkward, to add mechanisms to other languages to support some assertions.

UML does not talk much about assertions, but you can use them without any trouble. Invariants are equivalent to constraint rules on class diagrams, and you should use these as much as possible. Operation pre-conditions and post-conditions should be documented within your operation definitions.

Where to Find Out More

Meyer's book (1997) is a classic (albeit now huge) work on OO design that talks a lot about assertions. Kim Walden and Jean-Marc Nerson (1995) and Steve Cook and John Daniels (1994) use Design by Contract extensively in their books.

You can also get more information from ISE (Bertrand Meyer's company) at <http: //www.eiffel.com>.