Contracts and Assertions |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
This section is about responsibilities and contracts, and their connection to preconditions and postconditions. Recall from Section 2.2 in the initial lecture that we already touched on responsibilities in the slipstream of the pizza delivery example, see Figure 2.1. At the end of the chapter, in Section 51.8 we briefly discuss Design by Contract, which broadens the scope for applicability of contracts in the development process.
51.1. Division of Responsibilities
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
A class encapsulates some description of state, and some operations. A subset of the operations make up the interface between the class and other classes. All together, the class manages a certain amount of responsibility. Internally, the class is responsible for keeping the state consistent and sound. Externally, the operations of the class are responsible for delimitation of the messages that they handle, and the quality of the work (results) the operations deliver.
It is bad if a class is irresponsible. Class irresponsibility may occur if a pair classes both expect the other class to be responsible.
It is also bad if a class is too responsible. A pair of over-responsible classes redundantly care about the same properties. This is not necessary, and it bloats the amount of program lines in the implementation of the classes.
This leads us to the essence of this and the following sections, namely division of responsibilities. Let us first enumerate the consequences of well-defined and ill-defined division of responsibilities:
|
51.2. The highly responsible program
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Before we proceed to the role of preconditions and postconditions in relation to responsibility, we will study an example of an object-oriented program with two classes that altogether are over-responsible.
We make our points with yet another version of class BankAccount, see Program 51.2, in relation to a client of class BankAccount, see Program 51.1. As you will realize below, the illustration of over-responsibility is slightly exaggerated in relation to a real-life program.
The Main method in Program 51.1 withdraws and deposits money on the bank account referred by the variable ba, which is declared and initialized in line 5. Before withdrawing money in line 8, Main checks the soundness of the account (with AccountOK), and it checks if there are enough money available. After the withdrawal Main checks if the account is still sound. It also deals with the situation where Main withdraws an amount of money, which is greater then the balance of the account. Similar observations apply to Deposit in line 19.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Client{ public static void Main(){ BankAccount ba = new BankAccount("Peter"); if (ba.AccountOK && ba.EnoughMoney(1000)) ba.WithDraw(1000); else WithdrawingProblems("..."); if (!ba.AccountOK) MajorProblem("..."); if (ba.Balance <= 0) BankAccountOverdrawn(ba); ... if (ba.AccountOK) ba.Deposit(1500); if (!ba.AccountOK) MajorProblem("..."); } } | |||
|
In class BankAccount below, the Withdraw method in line 9-16 check the soundness of the bank account, and it deals with insufficient funds, before the actual withdrawal takes place in line 15.
The Deposit method in line 18-24 cares about the situation where clients deposit very large amounts. In such cases the bank account attempts to check if the money comes from illegal or criminal sources.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class BankAccount { private double interestRate; private string owner; private double balance; // ... public void Withdraw (double amount) { if (!AccountOK) ComplainAboutNonValidAccount(); else if (!this.EnoughMoney(amount)) ComplainAboutMissingMoney(); else balance -= amount; } public void Deposit (double amount) { if (amount >= 10000000) CheckIfMoneyHaveBeenStolen(); else if (!AccountOK) ComplainAboutNonValidAccount(); alse balance += amount; } } | |||
|
Seen altogether, the amount of code in Program 51.1 and Program 51.2 is much larger than desired. The checks that happen more than once should be eliminated. In addition, some of the responsibilities should be delegated to third party objects.
51.3. Responsibility division by pre and postconditions
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Preconditions and postconditions can be used to divide the responsibility between classes in an object-oriented program. The idea is to make it the responsibility of particular objects to fulfill the precondition of a method, and to make it the responsibility of other objects to fulfill the postcondition of a method. The rules are as follows:
|
Client and server are roles of objects relative to the message passing in between them. The client and server roles were discussed in Section 2.1. In some books, the server is called a supplier.
Let us recall the precondition and the postcondition of the square root function sqrt, as shown in Program 49.2. A function that calls sqrt is responsible to pass a non-negative number to the function. If a negative number is passed, the square root function should do nothing at all to deal with it. If, on the other hand, a non-negative number is passed to sqrt, it is the responsibility of sqrt to deliver a result which fulfills the postcondition. Thus, the caller of sqrt should do nothing at all to check or rectify the result.
Now we know who to blame if an assertion fails:
Blame the caller if a precondition of an operation fails Blame the called operation if the postcondition of an operation fails |
51.4. Contracts
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In everyday life, a contract is an enforceable agreement between two (or more) parties. Often, contracts are regulated by law. In relation to programming in general, we define a contract in the following way:
|
In object-oriented programming it is natural that the program parts are classes.
The preconditions and the postconditions of the public methods in a class together form a contract between the class and its clients.
It can be a serious matter if a contract is broken. A broken contract is tantamount to an inconsistency between the specification and the program, and it is usually interpreted as an error in the program. The error is usually fatal. A broken contract should raise and throw an exception. Unless the exception is handled, the broken contract will cause the program to stop.
51.5. Everyday Contracts
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Contracts are all around us in our everyday life |
When we do serious business in our everyday life, we are very much aware of contracts. When we accept a new job or when we buy a house, the mutual agreement is formulated in a contract.
Below we list some additional everyday contracts:
|
51.6. Contracts: Obligations and Benefits
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Contracts in object-oriented programs, specified by preconditions and postconditions of certain methods, express obligations and benefits.
In Figure 51.1 we personalize the obligations and benefits of a client and server. In the context of Figure 51.1 the server is called a supplier. This terminology, as well as the syntax used in the illustration, come from the object-oriented programming language Eiffel [Meyer97, Meyer92, Switzer93].
Figure 51.1 A give-and-take situation involving a client and a server (supplier) class. |
The Client, shown to the right in Figure 51.1 must make an effort to arrange, that everything is prepared for calling opSupplier in the class Supplier. These efforts can be enjoyed by the supplier, because he can take for granted that required precondition of opSupplier is fulfilled.
The roles are shifted with respect to the rest of the game. The supplier must make an effort to ensure that the postcondition of opSupplier is fulfilled when the operation terminates. This reflects the fact the operation has done the job, as agreed on in the contract. In return, the client can take for granted that the opposite party (the supplier) delivers an appropriate and correct result.
The obligations and benefits of the contract can be summarized as follows:
Obligation - May involve hard work Benefit - A delight. No work involved |
If you feel that the discussion in this section is too abstract, we will rephrase the essence in the next section relative to the squareroot function.
51.7. Obligations and Benefits in Sqrt
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In Program 49.2 of Section 49.3 we exemplified axiomatic specifications with a squareroot function. Let us, of convenience, rephrase the specification here.
1 2 3 4 5 | sqrt(x: Real) -> Real precondition: x >= 0; postcondition: abs(result * result - x) <= 0.000001 | |||
|
The obligations and benefits of sqrt, relative to its callers, are summarized in the following table:
- | Obligation | Benefit |
Client | Must pass a non-negative number | Receives the squareroot of the input |
Server | Returns a number r for which r * r = x | Take for granted that x is non-negative |
Table 51.1 A tabular presentation of the obligations and benefits of the squareroot function (in a server role) and its callers (in a client role). |
Notice in particular the obligation of the client and the benefit of the server, as emphasized using the red color in the table.
51.8. Design by Contract
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
As presented in Section 51.4, a contract of a class is the sum of the assertions in the class. Thus, a contract is formed by concrete artifacts in the source program.
As part of the Eiffel efforts [Meyer97, Meyer92, Switzer93], the use and benefits of contracts have been broadened such that contracts affects both design, implementation, and testing. The broad application of contract is known as Design by Contract (DBC). Design by Contract is a trademark of the company Eiffel Software, and as such it may be problematic to use the term, at least in commercial contexts.
Design by ContractTM (DBC) represents the idea of designing and specifying programs by means of assertions |
The following summarizes the use of contracts in the different phases of the software development process, and beyond.
|
The use of contracts for design purposes is central. The contract of a planned class serves as the specification of the class. We have discussed program specifications in Section 49.3 of this material.
Interface documentation - as pioneered by JavaDoc - includes signatures of methods and informal explanations found in so-called documentation comments. It is very useful to include both preconditions, postconditions, and class invariants in such documentation.
During program execution - both in the testing phase and in the end use phase - the actual state of the program execution can be compared with the assertions. As such, it is possible to verify the implementation against the specification at program run-time. If an inconsistency is discovered during testing, we have located an error. This is always a pleasure and a success. If an inconsistency is discovered during end use, an exception is thrown. This is clearly less successful. Exceptions have been treated in Chapter 33 - Chapter 36 of this material.
51.9. References
[Switzer93] | Robert Switzer, Eiffel and Introduction. Prentice Hall, 1993. |
[Meyer92] | Bertrand Meyer, Eiffel the Language. Prentice Hall, 1992. |
[Meyer97] | Bertrand Meyer, Object-oriented software construction, second edition. Prentice Hall, 1997. |