Friday, May 2, 2008

Design by C#ntract

Designing by contract is a way of writing the specification into the class or method itself. It holds the promise of making specification and test code more visible to the users of the unit so as to minimize misunderstandings and catch error conditions early.

It’s just not something we C# developers are used to being able to do. Not in C# anyway. Changing to the Boo language where is can be done safely by the use of macros, or to spec# which (like Boo) is still in development, is something few peoplehave the guts to do in production.

So when I read The Wandering Glitch’s series about doing Design By Contract in C#using the new functional possibilities, I was thrilled. It goes a long way to let us specify pre and post conditions. Andrew Matthews even does post conditions that reference state from before method entry as in: age > old(age).

His code also has the benefit that when you pass in an expression that results in an exception you get a string representing the original expression thrown back at you as the exception’s message.

There was a couple of things that I thought could be improved a bit:

  1. 1. The error message that you get back from the Expression.ToString() is not nice to look at. A typical string representation of an Expression could be: () => (value(DbcTest.Person+<>c__DisplayClass0).age >= 0)
  2. 2. It seems like overkill to do serialization to capture the old state of simple types like int and strings.

The first one is easy. With the help of a simple regular expression, we can throw away the ugly part and leave behind the important stuff, so that the exception gives me this message:

Violation of precondition: age >= 0

There’s not a whole lot of code behind this:

[DebuggerStepThrough]
public static void Require(this T obj,
   Expression> booleanExpression)
{
  var compiledPredicate
                  = booleanExpression.Compile();
  if (!compiledPredicate())
     throw new ContractViolationException(
        “Violation of precondition: ”
        + booleanExpression.ToNiceString());
}

static readonly Regex noiseRemoverRegex1 =
  new Regex(@”value[^)]*).”, RegexOptions.Compiled);
static readonly Regex noiseRemoverRegex2 =
  new Regex(@”.*=>s((.*))”, RegexOptions.Compiled);
private static string ToNiceString(
  this Expression expression)
{
   var output = expression.ToString();
   output = noiseRemoverRegex1.Replace(output, “”);
   output = noiseRemoverRegex2.Replace(output, “$1″);
   return output;
}

Which can then be used thus:

this.Require(() => age >= 0);

So far, all we have, is another way of writing Debug.Assert statements with a little extra oomph.

Comparing a variable to the previous value

How do we store the old value of a variable for later comparison? Closures are capable of freezing the value of local variables. But what if the variable is a reference type and the value that you want to compare to its old value is a member of that object? If the object is immutable (like strings are), no problem. Then you know the value hasn’t changed, because it can’t change.

But if you’re trying to validate old_person => old_person.Age == person.Age you’ll be in trouble because the value of Age will compare against itself and give a false positive in the above case. To overcome that Matthews uses serialization to make a deep clone of Person and all its members and its members’ members and so on. But that has a huge cost. You don’t know how bit the serialized object graph is going to be, but I’ll bet you it will include lots more objects than you can compare in a line of code!

So I opted for a simpler approach that allows me only to compare old values of value types. Value types, unlike reference types, can be captured on block entry:

public class EnsureBlock : IDisposable
  where T : struct
{
  protected const string ViolationTemplate
     = “Violation of contract: {0}”;
  private readonly Func _predicate;
  private readonly T _oldT;
  private readonly string _predicateString;     

  public EnsureBlock(
     Expression> predicate,
   T oldValue)
  {
     _predicateString = predicate.ToNiceString();
     _predicate = predicate.Compile();
     _oldT = oldValue;
  }     

  [DebuggerStepThrough]
  public void Dispose()
  {
     if (!predicate(oldValue))
        throw new ContractViolationException(
   string.Format(ViolationTemplate,
   _predicateString));
  }
}

// Only syntactic sugar:
public static class EnsureBlockExtension
{
  public static EnsureBlock Ensure(
     this object obj,
   Expression> predicate,
   T oldValue)
   where T : struct
  {
     return new EnsureBlock(predicate, oldValue);
  }
}</T,></T,></T,>

The little "where T : struct" after the class declaration restricts the use of the captured variable to simple types and user defined structs. Objects cannot be passed in, so the following is allowed by the compiler:

int age = 12;
using (this.Ensure(old_age => age > old_age, age))
{
  age--;
}

The following, however, is not accepted by the compiler because p is a reference type:

var p = new Person();

using (this.Ensure(old_person => p.Age > old_person.Age, p))
{
  age–;
}

So what we really want to do is pass in the p.Age member as the old value:

var p = new Person();

using (this.Ensure(old_age => p.Age > old_age, p.Age))
{
 age--;
}

It’s definitely not perfect. We still need to compare object references against their old values sometimes. There are two ways to go about that:

  1. 1. Make an overload of Ensure that has a "where T : class" and that uses Matthews code to serialize the object graph.
  2. 2. Walk the Expression tree and manually capture all members that are references within the expression. Obviously that’s not a trivial thing to do.

Maybe I’ll give it a try some day. If you don’t beat me to it!

One Response to 'Design by C#ntract'

Subscribe to comments with RSS or TrackBack to 'Design by C#ntract'.

  1. Søren on DBC « The Wandering Glitch said,

    ON MAY 5TH, 2008 AT 1:27 PM

    […] than comment on the blog, he went away and did something about it. And it’s pretty good! Go take a look, and then pick up the baton from him. Your challenge is to extract the parmeter objects from the […]

No comments:

Post a Comment