COMP2511
🎨 2.5 - Design by Contract
Thinking Contractually
- In small-scale problems, ADT requirements have a relatively finite scope
- As software becomes more complex, we can no longer account for every single possible scenario
- We restrict scope by defining the boundaries of the specification clearly
- The client of the ADT and the ADT each have their own obligations and benefits
- In business, when two parties interact with one another, they often write contracts to clarify obligations and expectations.
Defensive Programming
- Consider every possible input that the ADT could be given
- Map each possible input to a defined output
- Addresses unforeseen circumstances, so that the software works no matter what is thrown at it
-
Useful in some scenarios, but too expensive to adopt everywhere, all the time:
- Results in redundant checks (client and ADT perform checks)
- Locating errors can be difficult to lack of clear demarcation of responsibilities
- We may end up defending against errors that will never be encountered
Design by Contract
- The software specification is clearly defined and documented
- Results in simpler code and easier maintenance
- "The spec, the whole spec, and nothing but the spec"
- Correctness: Whether the implementation meets the specification (contract)
- Correctness is enforced through unit testing
- Benefits
- Prevents redundant validation tasks
- Given the preconditions are satisfied, the client can expect the postconditions
- Makes development cleaner and faster
- Responsibilities are clearly assigned between software components
Design by Contract
- Three components to a software contract:
-
Preconditions: what does the contract expect?
- If the precondition is true, cases outside of the precondition do not have to be handled
- E.g. expected argument value mark >= 0 and marks <= 100
-
Postconditions: what does the contract guarantee?
- Return value is guaranteed, provided the precondition is true
- E.g. Correct return value representing a grade
-
Invariant: what does the contract maintain? (keep the same)
- Some values must satisfy constraints, before and after execution (e.g. of a method)
- E.g. A value of mark remains between 0 and 100
-
Preconditions: what does the contract expect?
Contractual Correctness
- All testing is determining contractual correctness; whether the implementation meets the specification
- We have seen this in the form of dynamic verification, but it is also possible to have static verification as well (compilation, linting, type checking)
- It is possible to mathematically prove correctness to a contract statically;
- Some programming languages (Eiffel, Danny) offer native support for DbC and allow for static verification
- Java does not have native support for DbC (there are various libraries available to support it)
- Contracts must be:
- Declarative (not imperative) and not include implementation details;
- As much as possible: precise, formal and verifiable
Design by Contract: Example using Eiffel
Design by Contract: Examples in Java
Preconditions
- A precondition is a condition or predicate that must always be true just prior to the execution of some section of code
- If a precondition is violated, the behaviour of the section of code becomes undefined and thus may or may not carry out its intended work.
- Often, preconditions are included in the documentation of the affected section of code.
- Preconditions are sometimes tested using guards or assertions within the code itself, and some languages have specific syntactic constructions for testing .
- In Design by Contract, a software element can assume that preconditions are satisfied, resulting in removal of redundant error checking code.
Preconditions: Examples
Postconditions
- A post-condition is a condition or predicate that must always be true just after the execution of some section of code
- The post-condition for any routine is a declaration of the properties which are guaranteed upon completion of the routine's execution[1] .
- Often, preconditions are included in the documentation of the affected section of code.
-
Post-conditions are sometimes tested using guards or assertions within the code itself, and some languages have specific syntactic constructions for testing
-
In Design by Contract, the properties declared by the post-condition(s) are assured, provided the software element is called in a state in which its pre-condition(s) were true.
Invariants
- The class invariant constrains the state (i.e. values of certain variables) stored in the object.
- Class invariants are established during construction and constantly maintained between calls to public methods. Methods of the class must make sure that the class invariants are satisfied / preserved.
- Within a method: code within a method may break invariants as long as the invariants are restored before a public method ends.
- Class invariants help programmers to rely on a valid state, avoiding risk of inaccurate / invalid data. Also helps in locating errors during testing.
Preconditions in Inheritance
- When we create subclasses, we must ensure that the inherited contract is still complied to
- Liskov Substitution Principle: All subtypes must be substitutable for their base types - preserving correctess between super/subclasses
-
Preconditions
- An implementation or redefinition (method overriding) of an inherited method must comply with the inherited contract for the method.
- Preconditions may be weakened (relaxed) in a subclass, but it must comply with the inherited contract.
- An implementation or redefinition may lessen the obligation of the client, but not increase it.
Preconditions in Inheritance
- An implementation or redefinition (method overriding) of an inherited method must comply with the inherited contract for the method.
- Preconditions may be weakened (relaxed) in a subclass, but it must comply with the inherited contract.
- An implementation or redefinition may lessen the obligation of the client, but not increase it.
Postconditions in Inheritance
- An implementation or redefinition (method overriding) of an inherited method must comply with the inherited contract for the method.
- Post-conditions may be strengthened (more restricted) in a subclass, but it must comply with the inherited contract.
- An implementation or redefinition (overridden method) may increase the benefits it provides to the client, but not decrease it.
- For example:
-
the redefinition (overridden method) returns sorted set, offering more benefit to a client.
-
the original contract requires returning a set.
-
Class Invariants in Inheritance
-
Class invariants are inherited, that means,
"the invariants of all the parents of a class apply to the class itself.”
-
A subclass can access implementation data of the parents, however, must always satisfy the invariants of all the parents – preventing invalid states!
Safety & Security Considerations
- Do we have to program defensively all the time to ensure our software is safe and secure?
- We must think about the architecture - interactions between different software components
- A question to ask: Who will be using your ADT (what client)?
- A login form on a website: assume no preconditions;
- A backend web server on an internal network: can assume some preconditions;
- A public-facing API: assume no preconditions
- Machine code executor: assumes that the code is valid and won't cause buffer overflows, etc. (lower-level contracts between the executor and OS ensure safety here)
- A Java library used by programmers?
- Checks are still done, just determine at what point they are done
- Can't make any assumptions about user input
Exceptions
- What if a client doesn't meet the preconditions?
- In software, we can define different "flows" of interaction between the client and ADT
- Three main types of flows:
- Optimal - main success flow/use case
- Optional - alternative success flow/use case
- Exceptional - error flow/use case (e.g. invalid input was provided)
- The exceptional behaviour is still defined, but doesn't meet the preconditions for an optimal flow
- Exceptions are a way of informing the client that they haven't met the preconditions
- Exceptions are part of the contract
- In Java, whether an exception is checked or unchecked will affect compilation
COMP2511 22T3 - 2.5 - Design by Contract
By npatrikeos
COMP2511 22T3 - 2.5 - Design by Contract
- 248