Tuesday, February 5, 2008

Evolving the Object Oriented Paradigm Part IV: Contracts

This piece and the next will deal with limiting the "domain" of your functions as well as your objects. It was going to be one long article but this first part is a normal length by itself. Today, we'll start off with design by contract.

Design by contract is nothing new. It's been around for a long time. And yet, many languages today hesitate about putting contracts into the language and end up never adding them (except D. D handles contracts very well).

"Design by contract" deals primarily with functions (or methods but DBC isn't necessarily an OOP feature). DBC monitors a function's pre-conditions (which typically check the incoming parameters) and post-conditions (which typically checks return values and side effects of the function). For example, let's say we're writing a function called sqrt() which takes in a float and returns its square root as a float. We made our own square root algorithm cause we're really smart. And since we're smart, we don't want a negative number to be processed and we want to verify the return value for our algorithm is correct.

Typical Solution

We could do that like this:


float sqrt(float f)
{
if(f < 0.0)
throw new Exception("sqrt error: negative number");

// insert algorithm here

if(result * result != f)
throw new Exception("sqrt error: algorithm incorrect");
else
return result;
}


We got some clutter in there from if-statements, but it's still fairly easy to understand. Right?

As it turns out we're really, really smart and we want to write two square root algorithms. And we want to have the same interface so we make a class for each algorithm and create an interface for them to share. However we want the same pre and post conditions to apply to both algorithms so we'll start with something like this:

interface SqrtAlgorithm
{
float sqrt(float f);
}

class AlgorithmA implements SqrtAlgorithm
{
public float sqrt(float f)
{
if(f < 0.0)
throw new Exception("sqrt error: negative number");

// insert algorithm A here

if(result * result != f)
throw new Exception("sqrt error: algorithm incorrect");
else
return result;
}
}

class AlgorithmB implements SqrtAlgorithm
{
public float sqrt(float f)
{
if(f < 0.0)
throw new Exception("sqrt error: negative number");

// insert algorithm B here

if(result * result != f)
throw new Exception("sqrt error: algorithm incorrect");
else
return result;
}
}


This is correct. But now I have duplicate code running amok. Duplicating something once isn't too terrible. But if we were really, really, really smart and we wrote 10 square root algorithms, the duplicated code appears too much. And in other situations, your pre- and post-conditions may change over time, causing you to update all the duplicated code for every change.

Surely, there must be an already-existing, easier way. And there is: the Template pattern.

Template Solution

The Template pattern is another design pattern that provides a skeleton of an algorithm and leaves some of the actual concrete stuff to its subclasses. So let's apply the Template pattern to our classes:

abstract class SqrtAlgorithm
{
protected abstract _the_real_sqrt(float f);

public final float sqrt(float f)
{
if(f < 0.0)
throw new Exception("sqrt error: negative number");

float result = _the_real_sqrt(f);

if(result * result != f)
throw new Exception("sqrt error: algorithm incorrect");
else
return result;
}
}

class AlgorithmA extends SqrtAlgorithm
{
protected float _the_real_sqrt(float f)
{
// insert algorithm A here
}
}

class AlgorithmB extends SqrtAlgorithm
{
protected float _the_real_sqrt(float f)
{
// insert algorithm B here
}
}


So we had to change SqrtAlgorithm from an interface to an abstract class. SqrtAlgorithm's sqrt() method must be "final" (or not overridable) to stop subclasses from overriding the template. Now we have to rename sqrt() in AlogirthmA and AlgorithmB to _the_real_sqrt() because that's the method used in the template. Also we had to hide _the_real_sqrt() from the public so its not called directly from the public and skip over our template. If you have a good IDE with good renaming/refactoring abilities, this may not be a big deal. But I've permanently attached all my algorithms to a base class when an interface was fine before which I don't like.

Design by Contract Solution

Typically, contracts are two clauses added to the function definition to state the pre-conditions and post-conditions (although D allows blocks of statements which you would fill with asserts). I'll follow the typical syntax for this model at the moment but the syntax isn't really important at the moment.

In my new model, the version of the above code using contracts would look like this:

abstract concept SqrtAlgorithm
{
interface
{
float sqrt(float f)
requires f >= 0.0
ensures result * result == f;
}
}

concept AlgorithmA : SqrtAlgorithm
{
implementation
{
// contracts inherited
public float sqrt(float f)
{
// insert algorithm A here
}
}
}

concept AlgorithmB : SqrtAlgorithm
{
implementation
{
// contracts inherited
public float sqrt(float f)
{
// insert algorithm B here
}
}
}


In this typical syntax design, the "requires" clause handles pre-conditions and "ensures" handles post-conditions with "result" automatically being the return value inside the ensures clause. The conditions are only stated once by the interface because all inheriting methods inherit the conditions as well. If either clause doesn't pass (meaning its expression evaluates to false), then an exception would automatically be thrown stopping further progress.

Much simpler right?

Contracts are an easy way to validate classes are properly implementing or overriding your methods as well as providing easy validation methods for debugging. Plus, a nice compiler would have a switch to turn on and off including contracts because you may have contracts only used for debugging but other times, you'll want to leave the constraints in.

Next article will be on type domains and constraints.

-Kaja

No comments: