Programming with Contracts (PwC) is a method of developing software using contracts to explicitly state and test design requirements. The contract is used to define the obligations and benefits of program elements such as subroutines and classes. In this article, I explain contracts and present a technique for defining contracts in separate classes safely hidden away from implementation details.
Design by Contract (DbC) is often confused with PwC. Conceived by Bertrand Meyer, DbC is a formal software-design methodology, whereas PwC is the programming technique of using a contractual representation in code to verify that preconditions, postconditions, and invariants are satisfied. The differences can be equated to the relationship between object-oriented programming and object-oriented design. (For more information, http://www.artima.com/intv/contracts.html.)
Contracts
Contracts are made up of three major elements, referred to as "clauses": preconditions, postconditions, and class invariants. Preconditions and postconditions are clauses that are evaluated at the beginning and end of specific routines, respectively. From a design standpoint, a precondition represents the obligations on the context invoking the routine. An example can be found using the C file reading function fscanf
:
int fscanf(FILE* stream,const char * format [ , argument , ...]);<br>
The function fscanf
operates with the precondition that stream must be a pointer to an open file. There is also another precondition that there must be the same number of type specifiers in the format string as the number of arguments passed. Postconditions, however, represent both the obligations and benefits of a given subroutine. In the case of fscanf
, the implicit postconditions are:
- The return value is
EOF
if an error occurs; otherwise, it is the number of items successfully read not including any ignored fields. - The FILE* argument is updated.
From a theoretical standpoint a pre/ postcondition should be evaluated at compile time when appropriate. This can be done using BOOST_STATIC_ASSERT
found in the Boost C++ library (http://boost/static_assert.hpp). Pre/postconditions conceptually are also language independent. In fact, some pre/postconditions are difficult and even impossible to express in a formal language. In these cases, the pre/postcondition is best expressed as a natural language comment.
A class invariant is a property of each instance of a class that is required to evaluate to True before and after every external call to a public function. One way to think of a class invariant is as a clause that is ANDed with the pre- and postcondition of each public method. Checking class invariants in C++ is beyond the scope of this article, but the same effect can be achieved more verbosely with pre/ postconditions.
Peppering Code with Assertions: Good, but Good Enough?
Most modern C++ programmers have adopted the good habit of (usually) checking their preconditions and postconditions in their code by placing assertions throughout their routines. This is fine for simple single-expression assertions, as the code generated can be completely removed by turning off assertion compilation.
Simply using assertions is somewhat unsatisfying as the set of preconditions and postconditions is buried deep in the code and is hard to extract. Automated tools can extract preconditions and postconditions, assuming they follow a consistent naming convention, but for a human perusing the code, the contracts of nontrivial functions and classes are not easily parsed.
The other drawback of assertions is that they only apply to codeif you have nontrivial clauses, then this approach starts to spill into noncontract code. Consider this example:
template<typename T><br> class Stack {<br> void Push(T x) {<br> int nOldCount = Count()<br> // implementation here<br> assert(Count() == nOldCount + 1);<br> }<br> ...<br> }<br>
Notice that the code for allocating space and initializing nOldCount
will possibly remain in your executable, regardless of whether assertions are turned off.
Perhaps in this case, a compiler might optimize it away, but for nontrivial examples, it is virtually impossible for optimizers to remove all unreachable code. In this case, the simplest thing to do would be to wrap the nOldCount
declaration in a #ifdef/#endif
pair.
template<typename T><br> class Stack {<br> void Push(T x) {<br> #ifdef CONTRACT_CHECKING_ON<br> int nOldCount = Count()<br> #endif // CONTRACT_CHECKING_ON<br> // implementation here<br> assert(Count() == nOldCount + 1);<br> }<br> ...<br> }<br>
This may appear verbose, but the advantage that this code is effectively embedded with a test case that disappears entirely during release builds should be quite clear.
Writing code in this manner assures that the code is continually tested every time the code is executed. Retrofitting comparable test cases after code is already written is harder and almost invariably not as effective.
However, I have good news if you find this overly complexthere is a better way.
Using Contract Classes
The contract clearly needs to be separated from the implementation details. To separate it, you can define it in its own class. This makes the contract more easily parsed, and makes it reusable. The way I implement PwC in Heronand which applies equally well to C++is to define the contract as a template class that inherits from its primary type parameter, which you then use to wrap an implementation class. Consider this implementation class:
struct SortableIntArray_impl {<br> bool SortRange(int i, int j);<br> int GetAt(int i);<br> void SetAt(int i, int x);<br> };<br>
You can define the contract of the three public functions in code as in Listing One. Despite being conceptually simple, this contract required a significant amount of code to express. If this had been embedded directly in the implementation itself, it would become messy and confusing.
Listing One
template <typename T> struct SortableIntArray_contract : public T { bool SortRange(int i, int j) { // preconditions assert(i >= 0); assert(i < size()); assert(j >= i); assert(j < size()); // implementation T::SortRange(i, j); // postconditions // in essence, is this array sorted for (int n=i; n < j; n++) { assert(get_at(n) <= get_at(n=1)); } } int GetAt(int i) { // preconditions assert(i >= 0); assert(i <= size()); return T::GetAt(i); } void SetAt(int i, int x); // preconditions assert(i >= 0); assert(i <= size()); T::SetAt(i, x); // postcondition assert(T::GetAt(i) == x); } };
The contract can now be applied easily to the object conditionally as in Listing Two. The same effect can also be achieved using metatemplate programming techniques involving type selectors such as the STLSoft library compile-time function stlsoft::select_first_type
or the Boost library compile-time function mpl::if_c
.
Listing Two
#ifdef CONTRACT_CHECKING_ON typedef SortableIntArray_contract<SortableIntArray_impl> SortableIntArray; #else typedef SortableIntArray_impl SortableIntArray; #endif
Conclusion
Using assertions does allow the implementation of PwC, but using contract classes is better. Contract classes express and validate significantly complex contracts without obfuscating the implementation. Contract classes make design intentions explicit and improve the readability of code. Contract classes can also often be reused for classes with different implementations but with similar interfaces.
Acknowledgment
Thanks to Matthew Wilson for reviewing the article and pointing out that writing contracts that involve system conditions is a bad idea.
Download the source code that accompanies this article.
Christopher is a freelance computer programmer and developer of Heron, a modern, general-purpose, open-source language inspired by C++, Pascal, and Java. He can be contacted at http://www.heron-language.com/.