• No results found

Hazardous Software Failure Mode Classification

2.3 The object oriented paradigm

2.3.3 Software contracts

Helm et al [21] introduced contracts as a way of explicitly specifying interactions amongst groups of objects. The idea of “design by contract” was introduced by Bertrand Meyer as a way of making OO software more reliable. In [46] he argues that reliability is even more important for OO systems. This is because reuse (for example through inheritance) is the cornerstone of OO and the potential consequences of incorrect behaviour are therefore even more serious as reusable

components may be in many different applications. It is argued that software elements should be considered as implementations meant to satisfy well-understood specifications, this can be achieved through contracts. Whenever a task in one software unit relies on a call to another unit to carry out a sub-task a contract exists between the two units. As in the real world, there are two major properties that characterise any contract. Firstly each party expects some benefits from the contract and is prepared to incur some obligations to obtain them. Secondly, these benefits and obligations are documented in some form of contract document. So if the execution of a certain task relies on a routine call to handle one of its subtasks, it is necessary to specify the relationship between the client (the caller) and the supplier (the called routine) as precisely as possible. This is done using assertions. Some assertions, called pre- and postconditions apply to individual routines, others, called invariants, constrain all routines of a given class. Meyer uses the Eiffel language [47] to represent pre- and post conditions as shown below.

Routine name (argument declaration) is Header comment

require

Precondition do

Routine body, i.e. instructions ensure

Postcondition end

Preconditions express requirements that any call must satisfy if the operation is to execute correctly. The postcondition expresses properties that are ensured in return by the execution of the call. A precondition violation indicates a ‘bug’ in the client, the caller did not observe the conditions imposed on correct calls. A postcondition violation is a ‘bug’ in the supplier, the routine failed to deliver on its promises. Meyer explains that the presence of a precondition in a routine simply means that the client must guarantee that condition. It does not necessarily mean that the condition must be tested for before each call to that routine from a client.

Another type of assertion that can be used is a class invariant. This is a property that applies to all instances of the class, transcending particular routines. In effect the invariant is added to the pre- and postcondition of every existing exported routine of the class and any subsequently added routines of the class.

In [25] Jezequel and Meyer look at the what they consider to be the reasons for the Ariane disas-ter discussed in chapdisas-ter 2.1. As the report of the inquiry found, this was a reuse error, however the authors of this paper feel the real cause was the lack of any kind of precise specification associated with the reusable module. They propose that the convert function of the Internal Reference System horizontal bias module should have contained a require clause stating that the horizontal bias must be less than the maximum bias. They conclude that had the mission used a language and method supporting built-in assertions and design by contract then the crash would probably have been avoided. They do acknowledge however that it is always risky to draw such after-the-fact conclusions. They go on to state that the lesson to be learned from the event is that reuse without a contract is sheer folly.

Mitchell and McKim in [48] present benefits of using a design by contract approach gathered by talking to people who have used contracts. The first benefit they suggest is better designs.

By this they mean that designs are more systematic, due to developers having to clearly and simply express obligations of client and supplier. There is also better control over the use of inheritance, which shall be looked at in more detail later. Contracts also provide a consistent meaning for exceptions and ensure they are used systematically. The second benefit is improved reliability. This comes from the developer having a better understanding of the code, and hence spotting faults more easily. Contracts can also help testing, which again increases reliability.

Another benefit of contracts is better documentation, as contracts are part of the public view of a class’s features and allow programmers to produce more precise specifications. Contracts provide easier debugging because bugs are easily pinpointed. Finally, contracts also provide support for reuse, again this will be examined later.

Mitchell and McKim also identify some potential costs and limitations of the use of contracts.

Firstly they acknowledge that it takes time to write contracts, and although there is a down-stream cost saving in terms of testing, documenting, reusing etc. it is often difficult to commit the extra time to upstream activities. Writing good contracts is also a skill that takes time and practice to acquire. There is also a danger that contracts may lead to a false sense of secu-rity, as contracts cannot express all the desirable properties of programs (“Does the presence of contracts ensure the safety of the system?” for example). Another potential limitation of a contracts approach suggested by the authors is that for some projects, an early release, even one with bugs, rather than the quality of the product is the most important goal. This should not, however, ever be the case for safety critical software!

Using contracts with OO systems

Contracts are ideally suited to use with OO systems. In [46] Meyer discusses how contracts can be used to provide a better understanding and control of inheritance. The system in figure 2.25 is taken as an example.

X

+r()

A

+r()

B

Figure 2.25: Redefinition of a routine under contract

In this example class A has defined a method r, which has a contract associated with it. Class B inherits r from class A but has redeclared it. Polymorphism allows A to become attached to instances of B. If X now makes a call to r then dynamic binding ensures that the redeclared version of r in B is called rather than A’s original version. In effect A has subcontracted the task r to B. The problem here is that the author of X can only look at the contract for r in A. B could now violate its prime contractor’s (A’s) promises. B could do this in one of two ways. B could make the precondition stronger, risking some calls which are correct from X’s view point not being handled properly. Or B could make the postcondition weaker, returning a result which is less favourable than what has been promised to X. This can therefore not be allowed.

The reverse of these changes is permitted however. B can weaken the original precondition or strengthen the postcondition as both of these result in B exceeding the performance of the original contractor. The conclusion drawn by Meyer is that when using redeclaration in inheritance, the new version must remain compatible with the original specification, although it may improve on it. This can also be applied to class invariants, which must always be stronger than or equal to the invariants of each of its parents.

Several problems that could occur from making changes to reusable artifacts in a system are identified by Steyaert et al in [73]. The first problem arises when a parent class is changed to introduce a new method while one of the inheritors of that class had previously introduced

a method with the same name. Although this will not cause problems for the execution of the software as the parent class would be overridden by the inheritor’s, the intention of the adaptation to the parent class is lost. Secondly, if a new abstract method is introduced to the parent class which is invoked by other methods of the parent class, then already existing inheritor classes which don’t provide an implementation for this abstract class will be incomplete. If the new method were concrete, this could include extra method invocations of methods implemented by the inheritor that weren’t invoked before. This is referred to as method capture and can result in erroneous behaviour as the inheritor did not take into account that its methods would be invoked by the parent. The inverse of this situation can arise when method invocations are omitted by a change to a parent class. The method is said to have become inconsistent with the method it used to invoke.

Reuse contracts are introduced by Steyaert et al as a way of managing such changes. These contracts are a set of either abstract or concrete method descriptions. These description also include a specialisation clause which list self sends (calls to other methods within the same class).

There are also three operators introduced for reuse contracts, concretisation, refinement and extension along with their inverse operations abstraction, coarsening and cancelation. These operators allow the derivation of new reuse contracts on inheritors from the reuse contracts of the parent class. The derived reuse contract is labeled with how it has been derived based on the basic operators. When the base class changes it is then sufficient to check that the same operator can still apply to the new parent class, if it does then it is safe to conclude that no assumptions made by the reuser have been violated. This technique provides a useful way of documenting and managing change and reuse in a system and could potentially be applied to ensure safety properties are still assured in the presence of change.

Beugnard et al [5] describe how recent reports have found mixed results when components have been used and reused in mission-critical systems. They conclude that the solution is to be able to determine beforehand whether a given component can be used within a certain context. This should take the form of expressing what the component does without entering into the details of how. Contracts are proposed as a way of achieving this and they discuss how components can be made contract aware. It is proposed that contracts for components can be divided into four levels of increasingly negotiable properties. The first and most basic level is used to specify the component’s interface and ensures that the client and server components can communicate.

An example of this level of contract would be the IDL (Interface Definition Language) speci-fication used in an object bus such as CORBA. This level of contract doesn’t however define

the behaviour of the component, there is no indication of what the result of the execution of an operation might be. The design by contract that has been examined earlier in this chapter fits into the next level of contract as it defines an object’s behaviour using assertions. These con-tracts assume that the services are atomic (either performed in their entirety or not performed at all) or executed as transactions (a defined sequence of atomic services). The higher layer of contracts deals with the global behaviour of components in terms of synchronisation between method calls. This is particularly important were one server component has many clients. The contract will guarantee to a client that the service will be executed correctly, whatever other clients request. Beugnard et al propose achieving this through the addition of synchronisation policies to a contract. The fourth level of contract is the quality-of-service contract which can quantify the expected behaviour or offer the means to negotiate these values. The difficulty with using contracts such as these is that they rely on third parties.

Review of contracts

It has been seen how contracts can provide a very powerful and effective way of constrain-ing the behaviour of software. This is particularly true for OO software. As Meyer noted, whenever a task in one software unit relies on a call to another unit to carry out a sub-task, a contract exists between the two units. This is exactly the situation that exists with OO systems where it is object collaborations which realise the functionality. It is important to note that the presence of contracts alone is not enough. The constraints defined in the contract must be correct, and must be correctly implemented by the software. It has also been seen how contracts can provide support for reuse and inheritance. It is important that any constraints on the system design support such features, as they are beneficial features of OO.