Thursday, January 15, 2009

Preconditions, Postconditions: Design by Contract for C#

Bertrand Meyer has described a design technique called "Design by Contract" (DBC) in his book Object-Oriented Software Construction. He also added support for this technique in his programming language Eiffel. Also other languages do not have a native support for this valuable technique, we nevertheless can apply this technique in all other languages. What we need is the possibility to define and execute assertions at runtime and we have to document the pre-, and postconditions in the header of our methods. For C# we only need a small helper class (comments are omitted):
public static class Assertion
{
    public static void Require(bool precondition, string conditionDescription)
    {
        if (!precondition)
        {
            throw new AssertException(ExceptionDescription("Precondition", conditionDescription));
        }
    }

    public static void Require(bool precondition, string descriptionFormat, params object[] descriptionParameters)
    {
        if (!precondition)
        {
            throw new AssertException(ExceptionDescription("Precondition", string.Format(CultureInfo.InvariantCulture, descriptionFormat, descriptionParameters)));
        }
    }

    public static void RequireIsNotNull(object toBeTested,string objectName)
    {
        if (toBeTested==null)
        {
            throw new AssertException(ExceptionDescription("Precondition", objectName + " is not null"));
        }
    }

    public static void Ensure( bool postcondition, string conditionDescription )
    {
        if ( ! postcondition )
        {
            throw new AssertException(ExceptionDescription("Postcondition",conditionDescription));
        }
    }

    public static void Ensure( bool postcondition, string descriptionFormat, params object[] descriptionParameters )
    {
        if ( ! postcondition )
        {
            throw new AssertException(ExceptionDescription("Postcondition",string.Format( CultureInfo.InvariantCulture, descriptionFormat, descriptionParameters ) ) );
        }
    }


    public static void Check( bool condition, string conditionDescription )
    {
        if ( ! condition )
        {
            throw new AssertException(ExceptionDescription("Condition",conditionDescription));
        }
    }

    public static void Check( bool condition, string descriptionFormat, params object[] descriptionParameters )
    {
        if ( ! condition )
        {
            throw new AssertException(ExceptionDescription("Condition",string.Format( CultureInfo.InvariantCulture, descriptionFormat, descriptionParameters ) ) );
        }
    }


    //
    // Private methods
    //

    private static string ExceptionDescription(string assertionType, string description)
    {
        return string.Format(CultureInfo.InvariantCulture, "{0} failed. The expectation was '{1}', but this is false.", assertionType, description);
    }

}
As important as checking the assertions at runtime is to allow a client of our class to read the preconditions and postconditions without inspecting the implementation of our methods. The simplest solution is to copy the assertions into then method comments:
public class PrinterDescription
{
    private XmlDocument printerXml;

    /// <summary>
    /// Load the descripton from a xml file
    /// Assertion.RequireIsNotNull(printerDescriptionPath, "printerDescriptionPath");
    /// Assertion.Require(File.Exists(printerDescriptionPath), "File printerDescriptionPath exists");
    /// Assertion.Ensure(IsLoaded, "IsLoaded");
    /// </summary>
    public void Load(string printerDescriptionPath)
    {
        Assertion.RequireIsNotNull(printerDescriptionPath, 
            "printerDescriptionPath");
        Assertion.Require(File.Exists(printerDescriptionPath), 
            "File printerDescriptionPath exists");

        printerXml = new XmlDocument();
        printerXml.Load(printerDescriptionPath);

        Assertion.Ensure(IsLoaded, "IsLoaded");
    }

    /// <summary>
    /// Is the description loaded?
    /// </summary>
    public bool IsLoaded
    {
        get
        {
            return printerXml != null;
        }
    }

    /// <summary>
    /// Name of the printer
    /// Assertion.Require(IsLoaded, "IsLoaded");
    /// </summary>
    public string Name
    {
        get
        {
            Assertion.Require(IsLoaded, "IsLoaded");
            XPathNavigator nameNode = printerXml.CreateNavigator().SelectSingleNode("//PrinterName");
            return nameNode.Value;
        }
    }

}

2 comments:

Nate K. said...

Great to see such a simple implementation for pre and post conditions. I like it! Hopefully we can get this support in the language at some point in time.

Sven said...

If you do not want to wait for .NET 4.0 with "Code Contracts" you can go to CodePlex http://lightcontracts.codeplex.com/