|
|
|
Advertisement
|
The article is in four parts, of which this is the first, whose contents are defined as follows:
The use of the contract metaphor in software engineering is a growing, but not entirely well understood, phenomenon. A software contract itself is, as in life, merely the agreement (explicit or otherwise) between the parties involved. It "defines a set of expectations between the two parties, that vary in strength, latitude and negotiability, and specified penalties [for contract violation]" [4]. The software contract metaphor encompasses not only functional behaviour—types, interfaces, parameters and return values, and so on - but also operational behaviour—complexity, speed, use of resources, and so on.
The issue of what action is to be taken in response to contract violation is a separate matter, just as in life. In this four-part article I'm focusing on the use of programmatic constructs—enforcements—that police the functional contracts codified in software: known as Contract Enforcement. Other aspects of the software contract metaphor are outside the scope of this discussion.
Just in the same way that human language contains redundancies and error-checking mechanisms, so we must ensure that our code does the same. You tell the compiler what your design is as you go, and it ensures that each time it picks up a crumb, it verifies the design.
Essentially, contract programming is about specifying the design, in terms of the behaviour, of your components (functions and classes), and asserting truths about the design of your code in the form of runtime tests placed within it. These assertions of truth will be tested as the thread of execution passes through the parts of your components, and will �fire� if they don�t hold. (Note: not all parts of contracts are amenable to codification in current languages, and there is some debate as to whether they may ever be [4]. This does not detract from the worth of contract programming, but it does define limits to its active realisation in code. In this article, I will be focusing on the practical benefits of codifying contract programming constructs.)
The behaviour is specified in terms of function/method preconditions, function/method postconditions, and class invariants. (There are some subtle variations on this, such as process invariants, but they all share the same basic concepts with these three elements.) Preconditions state what conditions must be true in order for the function/method to perform according to its design. Satisfying preconditions is the responsibility of the caller. Postconditions say what conditions will exist after the function/method has performed according to its design. Satisfying postconditions is the responsibility of the callee. Class invariants state what conditions hold true for the class to be in a state in which it can perform according to its design; an invariant is a �consistency property that every instance of the class must satisfy whenever it�s observable from the outside" [4]. Class invariants should be verified after construction, before destruction, and before and after the call of every public member function.
Let�s begin with a look at a simple function, strcpy(), which
is implemented along the lines of:
char *strcpy(char *dest, char const *src)
{
char *const r = dest;
for(;; ++dest, ++src)
{
if(�\0� == (*dest = *src))
{
break;
}
}
return r;
}
What are its pre-/post-conditions? Let N be the number of
characters pointed to by src that do not contain the
null-terminating character �\0� (0).
Some preconditions are:
src points to a sequence of N + 1 characters (type
char) each of whose value is accessible by the expression
*(src + n), where n is an integer in the range [0, N + 1)
dest points to a block of memory that is writable for a
length of N + 1 (or more) characters
src
+ n) == *(dest + n) holds true
dest parameter
char *strcpy(char *dest, char const *src)
{
char * r;
/* Precondition checks */
assert(IsValidReadableString(src)); /* Is src valid? */
assert(IsValidWriteableMemory(dest, 1 + strlen(src))); /* Is dest valid? */
for(r = dest;; ++dest, ++src)
{
if(�\0� == (*dest = *src))
{
break;
}
}
return r;
}
where:
assert() is the standard C macro that calls
abort() if its expression evaluates false (0)
IsValidReadableString() is a notional system call that
tests to see if a pointer refers to a null-terminated string whose contents
span read-accessible memory
strlen() is the standard C function that returns the
number of characters in a null-terminated string
IsValidWriteableMemory() is a notional system call that
tests that a pointer refers to a certain size of writeable memory.
|
Sponsored Links
|