Preconditions: before calling service, client must check everything is ready.
Invariants: something true both before and after the service is called
Postconditions: promises the service makes that should be met after the service finishes
e.g.
public class Stack<T> {
/**
* Precondition: stack not empty
* Postcondition: stack unchanged, last pushed object returned
*/
public <T> peek();
/**
* Precondition: stack not empty
* Postcondition: stack size decreased by 1, last pushed object returned
*/
public <T> pop();
/**
* Precondition: stack not full
* Postcondition: stack sized increased by 1, `peek() == o`
*/
public void push(T o);
}
The contract for a class is the union of the contracts of its methods.
Testing
Contracts inform testing; assertions allow us to check:
- If preconditions hold
- If invariants are invariant
- If postconditions hold
- If there are any side effects
- If there are any exceptions
Preconditions/
Input Values
|
v
|--------------------|
| Software Component |
| |
| Errors/Exceptions | --> Side Effects
| |
|--------------------|
|
v
Postconditions/
Output Values
Inheritance
Subclasses may have different pre/post conditions. Would require checking if the object is an instance of the subclass to determine if preconditions are met - this breaks the LSP.
Contracts are inherited:
- Preconditions can be loosened
- Postconditions and invariants can be tightened
That is, require no more, promise no less.
Hence, instead of saying that ‘Bar is-a Foo’, we can more formally say 'Bar conforms to the contract of Foo.
Guidelines
- No preconditions on queries; it should be safe to ask a question
- No fine print
- Don’t require something that the client can’t determined; preconditions should be supported by public methods
- But the client doesn’t need to verify postconditions
- Use real code where possible
- But use English where you can’t show all the semantics
- No hidden clauses; the preconditions should be sufficient and complete
- No redundant checking; the server shouldn’t verify preconditions are met
Specifying Contracts
Eiffel:
- Keywords in method declaration: require and ensure
- Standardized and multiple projects to add language support
Informally, Java has assert expression: message; statements that are disabled by default (-ea flag required )
Philosophy for Exceptions
Java Exceptions iff a contract violation occurs.
Handling violations:
- Try fix the problem
- Try an alternative approach
- Clean up and throw an exception
- Clean up: release resources, locks, rollback transactions etc.
Hence, exceptions must be caught anywhere where clean up is needed.
Interfaces are contracts
- When a contract can be recognized independently from an implementation, an interface should be considered
- Interfaces can be composed; one class can implement many interfaces
- Interfaces can be extended to specialize contracts
Inheritance: The Dark Side
- Inheritance for implementation; child has no intention of honouring the inherited contract
- ‘Is-a-role-of’: merges two contracts
- ‘Becomes’: switching contracts
- Over-specialization: contract more specific than necessary
- Violating the LSP; breaks the contract