as a PDF

advertisement

Extending a method of devising software contracts

Richard Mitchell

University of Brighton, UK richard@inferdata.com

James McKim

Rensselaer at Hartford, CT, USA jcm@rh.edu

Copyright notice

This paper is published in Mingins C and Meyer B (ed),Proc TOOLS 32, IEEE 1999. The material is ©

1999 IEEE. Personal use of this material is permitted. However, permission to reprint/republish this material for advertising or promotional purposes or for creating new collective works for resale or redistribution to servers or lists, or to reuse any copyrighted component of this work in other works must be obtained from the IEEE.

Extending a method of devising software contracts

Richard Mitchell

University of Brighton, UK richard@inferdata.com

James McKim

Rensselaer at Hartford, CT, USA jcm@rh.edu

Abstract

Classes in object-oriented programs can be specified using assertions, specifically, preconditions and postconditions on individual methods and invariants on whole classes. Such assertions can be seen as forming a contract between a class and its client classes and, by extension, between the developers of a class and the developers of client classes. In some programming environments, the contracts can be checked at runtime.

A published method of devising contracts is applied to a small framework based on the observer pattern, raising a number of problems of writing contracts for a set of collaborating classes, rather than for individual classes such as those found in data structure libraries.

As well as providing an example of tackling such problems, the paper identifies desirable extensions to the method of devising contracts, to address aspects of: performance (keeping the run-time cost of evaluating preconditions low); privacy (ensuring that one client of a class cannot discover the identities of other clients); extensibility (allowing subclasses to weaken preconditions without invalidating existing postconditions, and allowing subclasses to adopt different frame rules); and kinds of constraints (distinguishing physical and logical constraints).

1 Introduction

A design pattern is a design idea that has been found useful in a number of concrete designs. A framework is a design idea that has been partly implemented. To use a framework, you must understand the underlying design idea (which might be based on one or more patterns), and extend the existing implementation (for example, by writing subclasses of some of the framework’s classes). A framework based on the observer pattern is described in Section 2.

Design by contract involves specifying classes by adding assertions to capture preconditions and postconditions on individual methods, and invariants on classes. A method of devising contracts is described in Section 3.

As Rösch (1999) has noted, “A component has high quality when it is built according to its description. So, to establish a defined level of quality we must first be able to describe a component and then be able to check whether the finished component resembles its description closely enough.” A framework is not a self-contained component, but, like a component, it does contain code to be reused and so merits being both described and checked, as Rösch advocates.

Sections 4 and 5 explore how to specify the observer framework using contracts, and identify a number of desirable extensions to the method of devising contracts. The observer framework

was chosen partly because it underpins widely-used registration-based event notification mechanisms, which are finding increasing use in component-based development (D’Souza and

Wills 1999), and partly because it involves collaboration between classes, thereby demonstrating that design by contract can be applied to software artefacts that are more complex than individual classes.

Contracts can contain faults. Section 6 briefly discusses what testing contracts involves, and sounds a note of caution. Contracts are one more tool for increasing trust in software, but are not a complete answer. Section 7 summarises the paper.

2 The observer framework

Figure 1 is a UML-style class diagram (Booch, Rumbaugh and Jacobson 1998) showing the principal classes, attributes, operations and associations in the simple version of the observer pattern described in (Gamma, Helm, Johnson and Vlissides 1994). The pattern is useful when one or more objects, known as observers, need to be kept up-to-date with another object, known as a subject. An observer object can attach itself to a subject in order to be told when to update itself.

Each time an operation on a concrete subject changes the subject’s state, it calls notify on itself.

The body of notify calls update on all of the currently attached observers. When an observer is told to update itself, it can ask for its subject’s state, so that it can bring its own state up-to-date with that of its subject.

Subject

{abstract} attach(Observer) detach(Observer) notify()

* observers

Observer

{abstract} for all o in observers

o.update()

ConcreteSubject subjectState setState() getState() subject

ConcreteObserver observerState update() return subjectState observerState =

subject.getState()

Figure 1. A simple version of the observer pattern

Some parts of the observer pattern can be implemented without any particular application in mind, yielding an observer framework. The framework can contain:

a class Subject with fully-implemented operations to attach and detach observers and to notify attached observers, but with no methods that call notify

a class Observer with an abstract operation update to be effected by concrete subclasses of

Observer (in a Java-oriented presentation of the framework, this Observer class would be replaced by an interface (Arnold and Gosling 1997) ).

A designer building on this framework imports the classes Subject and Observer and designs application-specific subclasses to stand in the places called ConcreteSubject and

ConcreteObserver in the pattern underlying the framework.

A framework embodies a design idea and provides some implementation. Users of a framework need to understand what the framework is providing as well as what they are expected to provide in addition. Frameworks are thus candidates for being designed, specified and checked using the ideas of design by contract. Both Subject and Observer can contain contracts to specify and check the implementation of the framework code, and code developed in the subclasses by future users of the framework.

3 A method for design by contract

Design by contract (Meyer 1992b) involves using assertions to constrain the behaviour of objects. The operations of a class are given postconditions to define what effects they bring about and what results they return. Where appropriate, the operations have preconditions to define when it is valid to call them. Invariants capture unchanging properties of objects of a class. There are many examples in (Meyer 1994) and (Meyer 1997).

The panel entitled “Design by contract guidelines” summarises the advice given in (McKim

1995) on devising contracts. Guideline G4 is the key to the method. By specifying the effect of each command on every basic query, we specify all the visible effects of the commands. In practice, the process of applying the guidelines is iterative. For example, attempting to devise postconditions can reveal that new basic queries are needed. As a result, existing queries can become derived queries, which, in turn, can affect how postconditions are written.

Section 4 shows the guidelines in use, and further examples of their use can be found in

(McKim 1996).

The principles of design by contract can be applied just at design-time. In this case, contracts are used to specify the clients’ view of classes, and can be expressed in, for example, the Object

Constraint Language of the Unified Modeling Language (Warmer and Kleppe 1999). Or, design by contract can be extended into run-time, using a programming language such as Eiffel (Meyer

1992a). In this case, the effort invested in writing the contracts is rewarded twice: contracts help to specify classes and to support run-time checking that the specification and the code are consistent. In this paper, the contracts are presented in Eiffel, and Section 6 explains how the contracts were checked during the testing process. The run-time check that the contracts and the implementation are consistent can increase the trust that can be placed in the code and the specification.

DESIGN BY CONTRACT GUIDELINES

G1 Separate queries from commands

(queries return a result but do not change the visible properties of the object; commands might change the object but do not return a result)

G2 Separate basic queries from derived queries

(derived queries can be specified in terms of basic queries)

G3 For each derived query, write a postcondition that specifies what result will be returned, in terms of one or more basic queries

(now, if we know the values of the basic queries, we also know the values of the derived queries)

G4 For each command, write a postcondition that specifies the value of every basic query

(now we know the total visible effect of each command)

G5 For every function and procedure, decide on a suitable precondition

(to constrain when clients may call the functions and procedures)

G6 Write invariants to define unchanging properties of objects

(concentrate on properties that help the reader build an appropriate conceptual model of the abstraction the class embodies)

4 Initial contracts for the observer framework

This section develops an initial set of contracts for the Subject and Observer classes, using the guidelines presented in Section 3. Section 5 shows how the contracts can be enriched to include frame rules (rules that say “nothing else changes”), privacy control and support for subclassing.

The contracts developed in this section cover two key aspects of the observer framework, attachment and notification:

• attachment is concerned with attaching observers to, and detaching observers from, the set of observers associated with a given subject (Section 4.1).

• notification involves updating the set of attached observers associated with a subject (Section

4.3).

Discussion of contracts for attachment leads to the idea of logical and physical constraints

(Section 4.2). During the discussion of notification, a class of immutable sets is introduced

(Section 4.3.2).

(UML and Eiffel have different lexical conventions. For example, classnames in Eiffel are in all uppercase, and Eiffel uses a single syntax for queries implemented by attributes and queries implemented by functions. In the UML class diagram in Figure 1, we have used traditional UML syntax (although UML does allow users to define their own). In Eiffel source code fragments in the following sections, we use Eiffel conventions. We hope the switch from one to the other does not confuse or annoy. Also, in order to simplify the presentation, we have avoided using some elegant but advanced aspects of Eiffel, notably anchored declarations.)

4.1

Attachment

The issue of attachment involves two key features, attach and detach . We shall look just at attach , since studying detach yields no new guidelines. When a given observer is attached to a subject, the postcondition on attach must assert:

• that the given observer is now one of the subject’s attached observers (desired change)

• that attaching this given observer did not attach or detach any other observers (frame rule).

We deal first with the desired change of attach , and consider frame rules in Section 5.

To be able to formalise the assertion that o is an attached observer, we can add a boolean query attached to class SUBJECT ( Current is the Eiffel term for the receiving object): attached ( o : OBSERVER ) : BOOLEAN

-- Is o attached to Current ?

For now, we shall regard attached as a basic query (in Section 4.4 we shall make it a derived query). We can use the query both in the precondition for attach , introduced by the keyword require , and in the postcondition, introduced by the keyword ensure (in Eiffel, assertions can be labelled so that tools can refer to individual assertions; the identifiers not_already_attached and attached are examples of labels).

attach ( o : OBSERVER )

-- Remember o as one of this subject’s observers require not_already_attached: not attached ( o ) ensure attached: attached ( o )

The precondition on attach could be removed. Discussion of whether that would improve the class SUBJECT is outside the scope of this paper, but Section 5.3 shows how to design

SUBJECT to allow for subclasses that make this change.

4.2

Logical and physical constraints

The precondition on attach addresses what might be termed the logical level, at which the required behaviour of attach is defined. There is also a more physical level to be discussed, arising from the use of references in object-oriented programming languages. The parameter o passed to the attach routine could have the value Void (or nil, or null). We can constrain o not to be Void with what we shall call a physical constraint. Here are the physical and the logical preconditions on attach (‘not equal to’ is written ‘/=’ in Eiffel): attach ( o : OBSERVER )

-- Remember o as one of this subject’s observers require exists: o /= Void not_already_attached: not attached ( o )

(Eiffel has an early-termination and operator, written and then . It is implicitly applied between multiple assertions, which we now have in this example.)

Physical constraints are often appropriate in preconditions, as here. They can also be appropriate in postconditions (for instance, the result of a particular function must not be null) and invariants (some attribute must always refer to an object). We can add a guideline to our list:

G7 Add physical constraints where appropriate

(typically, these will be constraints that variables should not be void)

4.3

Notification

When a subject changes state in a way its observers should know about, the routine that changed the state calls notify on the current object. The job of notify is to call update on each of the subject’s attached observers. In order to simplify the presentation, we shall discuss contracts for notify and update in two parts:

• asserting that notify calls update on one of a subject’s observers (Section 4.3.1)

• asserting that notify calls update on all the attached observers (Section 4.3.2)

4.3.1 Asserting that notify calls update on one observer: There is no facility in Eiffel to check that a feature has been called on some object, so there is no direct way to assert that notify calls update . However, rather than checking that update has been called, we can take a more problemoriented view, and check that update has achieved its purpose.

The point of the observer pattern is to give observers the opportunity to bring themselves upto-date with their subjects when their subjects change. D’Souza and Wills (1999) express this point nicely using an assertion that says, about a subject, that “any action that changes [a subject’s] state also ensures that the observer’s state correctly reflects the new value.” This they capture in an invariant, using a richer assertion language than Eiffel’s, but one that is not executable. Mikkonen (1998) presents a formalisation of the observer pattern based on temporal logic. Again, the requirement that an observer be up-to-date, or consistent, with its subject is expressed using an invariant, but the formalisation is not in an executable form.

We can express the same consistency requirement, but it will take more than a single invariant. In return for this extra effort, we shall get the benefit of runtime checking of our assertions.

To begin with, we add a feature consistent_with_subject to class OBSERVER, and use it in the postcondition of update , as follows: deferred class OBSERVER consistent_with_subject : BOOLEAN

-- Is this observer consistent with its subject?

update

-- Bring this observer up-to-date with its subject ensure consistent_with_subject: consistent_with_subject

. . .

end -- class OBSERVER

Class OBSERVER is abstract (deferred, in Eiffel terms). Each subclass of OBSERVER must provide appropriate implementations of the query consistent_with_subject and the command update . Providing an implementation of update is a normal part of using the observer framework.

Providing an implementation of consistent_with_subject is an additional task, but it is a task that focuses on the reason for using the framework.

4.3.2 Asserting that notify calls update on all the attached observers: Now that we can check that update is called on one observer, we turn to the problem of checking that update is called on every attached observer. To perform this check, we shall need to be able to talk about all the attached observers. Logically, all the attached observers form a set, and we want to refer to this set within assertions, so we introduce a class of immutable sets. Once an immutable set object has been created, it cannot be modified. However, it can be sent messages that result in new set objects (for example, s.plus(10) returns a new set containing the elements in s plus the element

10 ).

The following fragment of Eiffel is part of the client interface to a generic class

IMMUTABLE_SET , devised for use in writing contracts. The interface is presented informally. A version with contracts is given in (Mitchell, Howse and Hamie 1998a), and (Mitchell, Howse and

Hamie 1998b) describes how those contracts were derived from a published Larch specification of the abstract data type Set.

class interface IMMUTABLE_SET [ G ] count : INTEGER

-- The number of elements in the set has ( g : G ) : BOOLEAN

-- Is g an element of the set?

choice : G

-- A consistently-chosen arbitrary element of the set clone (s: IMMUTABLE_SET [ G ]): IMMUTABLE_SET [ G ]

-- A new set object that contains the same objects as s is _ equal ( other : IMMUTABLE_SET [ G ]) : BOOLEAN

-- Does Current contain the same elements as other ?

plus (g : G ): IMMUTABLE_SET [ G ]

-- A new set object equal to Current with element g added minus (g: G ): IMMUTABLE_SET [ G ]

-- A new set object equal to Current with element g removed

. . .

end -- class IMMUTABLE_SET [ G ]

The query count returns the number of elements in the set. The query has is the set membership test. The query choice returns an element of the set. From a client’s point of view, the element is chosen arbitrarily, but consistently: a client cannot know in advance which element choice will return, but the choice will always be made by the same algorithm. Thus, choice is a pure query, and repeated calls to s.choice

will return the same element for as long as s refers to the same instance of IMMUTABLE_SET (there are no commands that can be used to change the contents of such an instance). However, the calls s1.choice

and s2.choice

will not necessarily return the same value when s1 and s2 refer to two equal, but different, immutable set objects (because the consistent algorithm will work from the set’s representation, and two equal sets might not be represented identically).

The query clone(s) returns a new set that shares its elements with s (contrast this with a deep clone, which is a new set that contains copies of the elements of s , and a shallow clone, which contains a copy of whatever data structure object is used to store the elements of a set). The query is_equal is the equality test corresponding to clone . Two sets are equal if, and only if, they contain the same elements.

The query plus is a functional version of the command to add an element to a set. It returns a new set containing all of the receiver’s elements plus the element g.

Similarly, minus is a functional version of the command to delete an element. Both plus and minus can be used in formulating frame rules, and minus , together with choice , can be used recursively to visit every element of a set.

Since every feature is a query, instances of class IMMUTABLE_SET truly are immutable.

Some queries, such as count and has , return information about the current instance. Others, such as plus and minus , return new instances of IMMUTABLE_SET .

Having introduced the class IMMUTABLE_SET , we can now declare the following query on class SUBJECT , to allow us to talk about all the observers attached to a subject: observers : IMMUTABLE _ SET [ OBSERVER ]

-- The observers attached to this subject

Informally, we want a postcondition on notify of this form: notify

-- Update all attached observers ensure

-- forall o in the set of observers , it holds that o.consistent_with_subject

At the time of writing, Eiffel does not provide a “for all” construct, but we can instead recur over the set of observers. An auxiliary function all_observers_up_to_date is introduced to handle the recursion. (The use of recursion rather than a built-in “for all” construct does not impact the proposed extensions to the method of devising contracts.)

all_observers_up_to_date ( observer_set : IMMUTABLE_SET [ OBSERVER ] ) :

BOOLEAN

-- Are all observers in observer_set consistent with their subjects?

ensure

Result =

( ( observer _ set .

count = 0 ) or else

( observer _ set .

choice .

consistent _ with _ subject ) and all_observers_up_to_date ( observer_set.minus

( observer_set.choice

) ) ) )

This function checks that all observers in a given set are up-to-date by checking that

• either the set is empty (the count of the number elements in the set is zero)

• or - one chosen observer is consistent with its subject

- and, by recursion, so are all the other observers.

The consistency property of the query choice ensures that the element that is checked to be consistent_with_subject is the same element that is omitted by minus in the recursive step. (We had two reasons to make consistent_with_subject an auxiliary function with a parameter, rather than a regular query operating directly on observers . Doing so highlights that its role is really to provide an extension to the specification language, by providing a replacement for “for all”. And, at a practical level, it allows us to exploit the consistency property of choice .)

The postcondition of notify can then use this auxiliary function: notify

-- Inform all attached observers that this subject has

-- changed by calling update on each one ensure all_observers_up_to_date: all_observers_up_to_date ( observers )

4.4

A performance issue

The query observers is intended to support specification and checking of the observer framework.

It returns an immutable set of observers as its result. An implementation of the subject side of the framework will almost certainly store its collection of attached observers in a more conventional collection class, such as an array or a linked list. The query observers would then be calculated from this collection, when it is needed.

The query observers can be used to define the query attached , as follows: attached ( o : ABSTRACT_OBSERVER ) : BOOLEAN

-- Is o one of this subject's attached observers ?

ensure consistent_with_observers: Result = observers .

has ( o )

The query observers is a basic query, and attached is now a derived query. That means it is strictly redundant. Any use of attached(o) can be replaced by the expression observers.has(o) .

However, we shall retain attached for the following reason.

When using Eiffel, it is common practice to develop classes with full assertion checking turned on (to help to test that the implementation is correct) but to develop clients with only precondition checking turned on in the supplier classes (to check that the clients fulfil their side of the contract). This practice extends to the development of libraries and frameworks, such as the one under discussion.

When full assertion checking is turned on in the observer framework, each notify cycle entails re-calculating the immutable set observers from the underlying collection that SUBJECT uses to store its attached observers, since intervening calls to attach and detach might change this collection of attached observers. In return for the benefit of extra verification, we accept the runtime penalty of using observers when full assertion checking is turned on.

However, to keep the run-time penalties low when only precondition checking is on, we introduce additional, derived queries ( attached is a good example) that are cheap to calculate.

The contract for attached ties its result to that of the query observers , and this connection will thus be verified during testing, but the body of attached does not refer to observers (it calculates its result directly from the data structure SUBJECT uses to store its attached observers), and is thus cheap to execute.

We therefore add another guideline to our list:

G8 Make sure that queries used in preconditions are cheap to calculate

(if necessary, add cheap-to-calculate derived queries whose postconditions verify them against more expensive queries)

There is, of course, a trade-off at work here. A user of the framework must understand each additional derived query before he or she can understand the feature it helps to specify. Adding derived queries can reduce run-time overheads, but can increase the intellectual load on users

(although, of course, a user of the framework might find attached(o) easier to remember and use than observers.has(o) ).

4.5

Re-entrancy

There are further aspects of even this simple version of the observer pattern that we do not have space to cover. In particular, we have not discussed the need to decide whether re-entrant calls to attach and detach are allowed (such calls could, for instance, detach an existing observer just before a subject calls update on that observer), and the need to make the decision visible in the contracts. Devising contracts to allow and control re-entrant calls is an interesting exercise, but does not lead to any new guidelines.

5 Enriching the contracts

This section adds frame rules to the existing contracts (rules that say “nothing else changes”), addresses a privacy issue (making sure that one observer of a subject cannot find out about other

observers), and modifies the form of all postconditions to allow for subclassing (so that a new postcondition, appropriate for a weaker precondition, does not conflict with inherited postconditions).

5.1

Frame rules

Frame rules are rules of the kind “... and nothing else changes.” Currently, the postcondition on attach(o) asserts that observer o is attached. An example of a frame rule would be that attaching observer o does not add any other observers to the set of observers, or remove any existing observers from it. Borgida, Mylopoulos and Reiter (1995) show why frame rules can overconstrain developers of subclasses. For example, suppose that a subclass designer wants to extend attach so that, when some limit on the number of observers is reached, the oldest observer is detached to make room for the new observer. The frame rule given above, that attaching observer o does not add or remove any other observers, would preclude the subclass designer from extending our version of attach.

<<specification>>

SUBJECT_TYPE

Signatures and constraints

(preconditions, postconditions, invariant)

<<constrains>>

<<specification>>

SUBJECT_TYPE_WITH_

FRAMING

Additional constraints of the

"no other changes" variety

<<restricts>>

<<specification>>

SUBJECT_TYPE_WITH_

FRAMING_AND_

VISIBILITY_CONTROL

<<implements>>

The set of observers used in constraints is made private

ABSTRACT_SUBJECT

{abstract}

Implementation of registration and notification (but no methods that call

'notify')

<<extends>>

CONCRETE_SUBJECT

Applicationspecific methods that change the state and call

'notify'

Figure 2 . Layers of specification and implementation.

The solution presented in (Borgida, Mylopoulos and Reiter 1995) is to write frame rules on whole classes, of the form “if property x changed (in accordance with some given formula) there must have been a call to method m.

” Such a rule can be extended in a subclass to include new allowed property changes, and additional calls that may bring about existing changes. Eiffel does not provide a means to assert that a given method was called, so we place frame rules in the postconditions of individual methods. For example, the following addition to the postcondition on attach asserts that, after o has been attached, the set observers minus this newly attached observer is the same as the set of observers before the attach , i.e., attaching a new observer changes the set of observers in no other way (the clone is necessary because we want to talk about the elements of the set, not the identity of the set).

observers .

minus ( o ).

is _ equal ( old ( clone ( observers ) ) )

Here is how we allow parent class designers to include such frame rules without unduly constraining subclass designers.

Taking the subject side of the observer pattern as an example, we write non-frame contracts in one class, which contains no implementation code (it is called SUBJECT_TYPE in Figure 2), and we write the frame rules in a subclass, still with no implementation. This subclass further constrains the contracts it inherits from its superclass. Finally we write the implementation code in class ABSTRACT_SUBJECT , which implements the contracts it inherits (the need for visibility control is discussed later, in Section 5.2). Regular users of the framework write their version of what is called CONCRETE_SUBJECT in Figure 2 (they extend the implementation provided in

ABSTRACT_SUBJECT ). A developer wishing to step outside the constraints imposed by the frame rules can replace SUBJECT_TYPE_WITH_FRAMING and make appropriate changes to the code in ABSTRACT_SUBJECT .

The approach is summarised in this guideline:

G9 Place constraints on desired changes and frame rules in separate classes

(this allows developers more freedom to extend your classes)

5.2

Privacy

The query observers is needed in a number of assertions. There can be applications of the observer framework in which one observer of a subject should not be allowed to learn the identities of other observers, yet the query observers lets any client of a subject do just that. If privacy is needed, i.e., if one observer must not be able to learn the identities of other observers, there is a straightforward solution. Provide deferred classes in which observers is visible, and available at run-time to support checking, but hide the query before reaching the level of concrete classes. The set of layers needed on the subject side of the framework is extended by one (see

Figure 2). In Eiffel, the relevant class looks like this:

deferred class SUBJECT_TYPE_WITH_FRAMING_AND_VISIBILITY_CONTROL inherit

SUBJECT_TYPE_WITH_FRAMING export { NONE } observers end end -- class SUBJECT_TYPE_WITH_FRAMING_AND_VISIBILITY_CONTROL

The class inherits from SUBJECT_TYPE_WITH_FRAMING and makes the query observers visible to none of the possible client classes. Generalising, we get

G10 If there are privacy requirements, queries that compromise privacy can be used in contracts, and then made private

(“then made private” means they are made private in layers below the one in which they are used in contracts, but above the layers to which clients have full access)

Privacy control of this kind is not without a price. The class

SUBJECT_TYPE_WITH_FRAMING_AND_VISIBILITY_CONTROL is a subclass of

SUBJECT_TYPE_WITH_FRAMING but it is not a subtype, since it cancels the query observers . A subclass that is not also a subtype needs introducing and using with care. There are meant to be two kinds of users of the observer framework, whom we shall call regular users and reusers.

Regular users will import into their projects all the classes in the layers shown in Figure 2, except the class CONCRETE_SUBJECT , which is where they place the class that is to be a subject in their application. The imported framework code declares variables of class

ABSTRACT_SUBJECT , and regular users will declare variables of their own concrete subject and observer classes. There is no need to declare variables of, for instance, SUBJECT_TYPE , so there is no source of problems arising from the use of dynamic binding in the presence of cancelling.

Some users will do more than use the framework. They will take it apart and reuse some of the parts. They might, for example, change some of the frame rules and adapt the implementation code accordingly, to offer different functionality to regular users. Reusers who adapt the framework will have to understand where and why the framework has non-subtype inheritance relationships, if they are to reuse parts of the framework safely.

5.3

Allowing redefinition of contracts

As well as modifying the code, a subclass developer might wish to redefine the contracts on existing routines. Here we examine an example problem arising from redefining preconditions, and identify a general solution. Suppose that the contract on the command attach looked like this:

attach ( o : OBSERVER ) require not_attached: not attached ( o ) ensure attached: attached ( o ) one_more_observer: observers .

count = old observers .

count + 1

Imagine you are developing a subclass of SUBJECT , called RELAXED_SUBJECT (in terms of the layers in Figure 2, you would begin by writing RELAXED_SUBJECT_TYPE ). You wish to relax the precondition on attach and allow it to be called with an observer that is already attached, in which case attach is to do nothing. The inherited postcondition labelled “attached” will be vacuously satisfied. But the inherited postcondition labelled “one_more_observer” cannot be satisfied when attach is called with an already-attached observer, so the designer of the original version of SUBJECT has precluded the redefinition you wish to make.

A general solution to the problem is to consider carefully what a postcondition means. It means “if the precondition was true in the ‘before’ state then this is what will be true in the ‘after’ state.” If we write this meaning in full, the contract on attach becomes: attach ( o : OBSERVER ) require not_attached: not attached ( o ) ensure attached: old ( not attached ( o ) ) implies attached ( o ) one_more_observer: old ( not attached ( o ) ) implies observers .

count = old observers .

count + 1

Each of the original postconditions has been guarded by the precondition. Now the redefinition in the subclass is possible. The new precondition is the original precondition or -ed with attached (o).

The new postcondition is the original postcondition and -ed with still_attached: old ( attached ( o ) ) implies attached ( o ) if_was_attached_then_no_more_observers: old ( attached ( o ) ) implies observers .

count = old observers .

count

(Since these are now both frame rules then, by guideline G9, they will go in a separate class.)

Generalising, we get

G11 To support redefinition of features, guard each postcondition clause with its corresponding precondition

(this allows unforeseen redefinitions by those developing subclasses)

A practice we have found works is to develop contracts without guarding the postconditions, and to add the guards late in the development cycle, when changes are happening less frequently, and

the contracts have been tested (see Section 6). We repeat the tests on the contracts after adding the guards.

Guarding postconditions with preconditions allows redefinitions in subclasses. It does not protect methods against faulty clients, and so the original preconditions must remain in place.

Thus, the precondition is a constraint on when a method can be called, and the combined assertion “precondition implies postcondition” specifies what the method does. In the terms introduced in (Rumbaugh, Blaha, Premerlani, Eddy and Lorensen 1991), preconditions are concerned with dynamic properties, and precondition-postcondition pairs are concerned with functional properties.

6 Testing the contracts

Design by contract leads to programs containing assertions that have two roles:

• the assertions specify the client’s view of a class, and

• those assertions that are executable can be used to check the code (we have used only executable assertions in the examples included in this paper).

There are two aspects of assertions that need to be tested. First, the correctness of the assertions must be tested. When an assertion evaluates to false at run-time, it might be the assertion that is faulty, rather than the code. The second bullet above states the intention of including assertions

(to check the code), but the reality is that, at testing-time, we can only check that the assertions and code are consistent. Inspection will reveal where the fault lies.

There is a second aspect of contracts that can be tested. We can try to answer the question: what bugs in the code would be caught by our assertions, and what would not? In trying to answer this question, we are testing the quality of the contracts, rather than the correctness of the code they specify. Such testing is important to those who promote design by contract, in order to understand its strengths and limitations. And it is important to those who use software that is equipped with contracts; developers need some indication of how thorough the contracts are.

Testing how thorough the contracts are involves a particular kind of testing. It involves devising bugs in the implementation that go undetected by the contracts. It is too early to offer guidance on how to devise tests that uncover weaknesses in contracts, but it is clear that it requires the same creative approach to being destructive that makes conventional testing successful. Le Traon, Deveaux and Jézéquel (1999) have devised tools to support the seeding of bugs to check contracts.

Here is an example of a bug that is not caught by the version of the contracts described here.

Imagine a faulty subject that attaches a given observer twice (i.e., puts the reference to a given observer twice into the internal data structure that holds the collection of attached observers). The consequence of this bug would be that the observer would have its update method called twice each time a concrete subject called notify . The contracts described here will not detect this bug, partly because the contracts use a set to give an abstract view of the collection of attached observers, and partly because the test that notify calls update is based on boolean properties, and calling update twice still leaves the appropriate boolean properties true. (The modifications to the contracts needed to fix this particular problem do not add new insights into the method of developing contracts, and are not presented here.)

A list of undetectable bugs might form part of the detailed documentation of the framework. Such a list can be seen as a list of areas of incompleteness in the formal expression of the specification of the framework.

If you did not trust the framework developer, how would you know that the framework did not somewhere contain an if-statement like this one?

if and

-- it’s the third Thursday in June and the year is later than 2001

-- I’m still being used in the banking application named “safe-e-commerce” then

-- steal a fraction of a cent from large numbers of bank accounts and put them into

-- account 12345678910

Would you notice if there were no tests involving resetting the system clock to the third Thursday in June in some future year? Of course not.

How might rogue statements such as the one above be detected? The code might be executed in an environment that limits its access to resources (sandboxing), or the code might be inspected to make sure that it does what its contracts claim and no more (this is equivalent to treating frame rules as implicit, to be checked by inspection). To avoid making source code public, we can imagine making use of a third party, trained in certifying software against contracts. and trusted by us and by you. We deliver the full source code (interfaces, contracts and implementation) of a component to this third party, who inspects the code, and reveals the interfaces and contracts to you, together with a certificate of compliance, so that you can trust our components. Without the contracts, which provide precise specifications and run-time checking, the level of trust would not be so high.

7 Summary

A number of existing guidelines for writing contracts have been applied to a small reusable framework based on the observer pattern, demonstrating that contracts can apply to a framework of collaborating classes as well as isolated classes. The contracts check not only the ‘out of the box’ framework code, but also the code that extends the framework, which a user of the framework must write.

The observer pattern has been specified by others. As mentioned in Section 4.3.1, D’Souza and Wills (1999) present a formalisation based on UML and OCL, and Mikkonen (1998) presents a formalisation based on temporal logic. These formalisations are not executable, and so can be more abstract than the one presented here. However, there is then no possibility to check the implementation against the specification except by proof. That possibility exists for the formalisation presented here, but we also have the possibility to check the implementation by runtime checking of contracts.

A number of new guidelines have been introduced to cover some detailed issues: the need to care about the run-time cost of evaluating assertions (by making sure that preconditions are cheap to evaluate), the need to allow for subclassing (by keeping frame rules separate, and by

guarding postconditions), the need to support privacy (by controlling whether one object in a collaboration can discover the identities of others), and the need to consider constraints concerned directly with properties of object-oriented programming languages (by distinguishing logical and physical constraints).

When all the guidelines on writing contracts have been followed, and the contracts have been thoroughly tested, it is reasonable to place more trust in the implemented code than if no contracts had been written. However, we feel it right to sound a note of caution. It is difficult to write complete contracts, and sometimes it is inappropriate to expect contracts to cover everything. An honest framework developer could publish a list of the known loopholes in the contracts, and demonstrate by conventional testing that those bugs the loopholes would let through are not manifested in the implementation. Third-party inspectors could verify that a framework does what it claims, and no more.

Acknowledgements

Support for this work from the following bodies is gratefully acknowledged: the UK’s

Engineering and Physical Sciences Research Council under grant number GR/K67304; InferData

Corporation, Austin, Texas; and the Distributed Systems and Software Engineering Group at

Monash University, Melbourne, Australia.

References

Arnold K and Gosling (1997). The Java Programming Language.

Addison Wesley.

Booch G, Jacobson I and Rumbaugh R (1998). The Unified Modeling Language User Guide.

Addison Wesley.

Borgida A, Mylopoulos J and Reiter R (1995).

On the frame problem in procedure specifications.

IEEE Transactions on Software Engineering, Vol 21, No 10, pp 785-798.

D’Souza D and Wills A (1999). Objects, components and frameworks with UML. The Catalysis approach . Addison-Wesley.

Gamma E, Helm R, Johnson R and Vlissides J (1994 ). Design patterns. Elements of reusable object-oriented software . Addison-Wesley.

Le Traon Y, Deveaux D and Jézéquel J-M (1999). Self-testable components: from pragmatic tests to design-for-testability methodology. In Proceedings of TOOLS29, IEEE, pp96-107.

McKim J (1995). Class Interface Design and Programming by Contract . In Proceedings of

TOOLS 18, Prentice Hall, pp 433-470.

McKim J (1996). Programming By Contract: Designing for Correctness . Journal of Object

Oriented Programming, May 1996.

Meyer, B (1997). Object-oriented software construction . Prentice-Hall (2 nd

edition).

Meyer B (1992a). Eiffel: the language . Prentice-Hall.

Meyer B (1992b). Applying “Design by Contract” . IEEE Computer, 25(10), pp 40-51.

Meyer B (1994). Reusable software: the base object-oriented libraries. Prentice-Hall.

Mikkonen T (1998). Formalizing Design Patterns . Proceedings of ICSE'98. IEEE, pp 115-124.

Mitchell R, Howse J and Hamie A (1998a). A contract-oriented specification for a SET class .

University of Brighton Technical Report CMS-98-01.

Mitchell R, Howse J and Hamie A (1998b). Contract-oriented specifications . In Chen J, Li M,

Mingins C and Meyer B (editors). Proceedings of TOOLS 24.

IEEE, pp 131-140.

Rösch M (1999). Building a software components industry.

Component Strategies 1(9) pp29-33.

Rumbaugh, J., Blaha M, Premerlani W, Eddy F, and Lorensen W (1991). Object-oriented modeling and design.

Prentice-Hall.

Warmer J and Kleppe A (1999). The object constraint language. Precise modeling with UML .

Addison-Wesley.

Download