System development
- violated pre-condition -- bug in client
- violated post-condition -- bug in supplier
A pre-condition limits the cases that
a supplier must handle!
slide: System development with contracts
Assertions may be used to decide
who is responsible for any erroneous behavior of the system.
See slide [3-limits].
For example, imagine that you are using a software
library to implement a system for financial transactions
and that your company suffers a number of losses
due to bugs in the system.
How would you find out whether the loss is your own fault
or whether it is caused by some bug in the library?
Perhaps surprisingly, the use of assertions allows
you to determine exactly whether to sue the library
vendor or not.
Assume that the classes in the library are
all annotated with assertions that can be checked
dynamically at runtime.
Now, when you replay the examples that resulted
in a loss for your company with the option
for checking pre- and post-conditions on,
it can easily be decided who is in error.
In the case a pre-condition of a method
signals violation, you, as a client of a library
object, are in error.
However, when no pre-condition violation is signaled,
but instead a post-condition is violated, then the library
object as the supplier of a service is in error;
and you may proceed to go to court,
or do something less dramatic as asking the
software vendor to correct the bug.
Realization
The contract specified in the class interface
as given in slide [3-acc-1] may actually be enforced
in the code as illustrated in slide [3-acc-2].
Realization
account
class account {
public:
account() { _balance = 0; assert(invariant()); }
virtual float balance() { return _balance; }
void deposit(float x) {
require( x >= 0 ); check precondition
hold(); to save the old state
_balance += x;
promise( balance() == old_balance + x );
promise( invariant() );
}
void withdraw(float x) {
require( x <= _balance ); check precondition
hold(); to save the old state
_balance -= x;
promise( balance() == old_balance - x );
promise( invariant() );
}
virtual bool invariant() { return balance() >= 0; }
protected:
float _balance;
float old_balance; additional variable
virtual void hold() { old_balance = _balance; }
};
slide: The realization of the $account$ contract
The additional variable {\em old_balance} is needed
to compare the state preceding an operation with
the state that results afterwards.
The old state must explicitly be copied by calling hold.
In this respect, Eiffel offers better support
than C++.
Whenever proves to be less than zero,
the procedure sketched above can be used to determine whether
the error is caused by an erroneous method invocation,
for example when calling with ,
or whether the implementation code contains a bug.
For the developer of the software, pre-conditions
offer a means to limit the number of cases that a method
must be able to handle.
Often, programmers tend to anticipate all possible uses.
For instance, many programs or systems have options that
may be learned only when inspecting the source code
but are otherwise undocumented. \nop{See for example [UndocDos].}
Rather than providing all possible options,
for now and the future, it is more sensible to delineate
in a precise manner what input will be processed and what
input is considered illegal.
For the developer, this may significantly reduce
the effort of producing the software.
It is important that what is and what is not supported
is in principle negotiable whenever the class
interface explicitly states the requirements
imposed on the user.