Saturday, March 27, 2010

Contract-Oriented Programming with Groovy

Introduction

One of the first programming-related books i've read thoroughly has been "Object Oriented Software Construction" by Bertrand Meyer [0]. Although the second release (!) has been released back in good-old 1997 it still contains a huge amount of object-oriented principles not seen in nowadays main-stream programming languages like Java or .Net C#.

One of the concepts I liked best is contract-oriented programming aka "design by contract". The basic idea is pretty genius: applying the mathematical principle of Hoare triples to classes and objects.

A Hoare triple is always of the following form:

{P} A {Q}

A concrete triple says: before some operation A executes the state P must be true. After execution of operation A, state Q must hold. It's as simple as that.

In an object-oriented context, application of Hoare triples result in so-called assertion types:

Preconditions:  specifies the state the object must hold before a method is executed.

Postconditions: specifies the state the object must hold after a method is executed.

Class-Invariant: specifies the state an object must hold after it has been constructed, before and after each method call on that object.

Unfortunately the principle of contract-oriented programming is not natively supported in most programming languages (except Eiffel). With the advance of aspect-oriented programming there have been some attempts in the Java world to add contract support, but none of them really seemed appropriate to me.

Contracts for Groovy - gcontracts

When I first read Peter Niederwiesers blog-post on closure annotations - contract-oriented programming came immediately to my mind. Through the introduction of AST transformations in Groovy 1.6 it is possible to modify a program's abstract syntax tree and add custom byte-code or modify it - i've already written a short introduction on local and global AST transformations.

With AST transformations and closure annotations in mind a started to implement a first prototype that already supports the previously mentioned assertion types by providing Java annotations (@Invariant, @Requires, @Ensures). Before we take a look at the annotations and on the corresponding AST transformation stuff, I want to show you the outcome and how those contract annotations can be applied on Groovy classes:

e.g. a Person class

@Invariant({ firstName != null && lastName != null })
class Person {
  String firstName
  String lastName
  String name


  public Person() {}


  public Person(final String firstName, final String lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }


  @Override
  @Requires({ firstName.size() > 0 && lastName.size() > 0 })
  def String toString() {
    return "${firstName} und ${lastName}"
  }


  @Requires({ delimiter in ['.', ',', ':'] })
  @Ensures({ name.size() > 1 } )
  def initCompleteName(String delimiter) {
    name = "${firstName}${delimiter}${lastName}"
  }
}

I know, the good-old stack example is a better choice but I needed a quick-and-dirty example on how to apply gcontracts annotations ;-) Let us quickly skip through all annotations and their meanings in that particular context:

1. the @Invariant annotation just says that during the entire life-time of a person it's firstName and lastName property must never be set to null.
2. whenever toString() is executed the object's firstName and lastName size() must be greater than zero
3. whenever initCompleteName is called, parameter delimiter must be in a certain range.
4. whenever initCompleteName is done, the name must be initialized to something bigger than 1.

Note that e.g. the @Requires closure annotation has access to all method parameters and instance variables, so checking the delimiter being in a certain Groovy range just works. In addition the AST transformation process injects the class invariant wherever feasible, e.g. 


def p = new Person('Max', 'Mustermann')
p.setFirstName(null)

causes an AssertionError since after the call to setFirstName the class invariant is not satisfied anymore.

I hope you already see and feel that applying gcontracts can greatly improve readability and maintainability of your source since interface contracts are specified explicitly and are proved (if activated) at runtime. Of course, such mechanisms are ment to be used to determine programming errors and not business validation errors - but i guess i am going to take a look at invariants and their role in the software development process in another blog-post.

Let's take a quick look at the implementation of gcontracts done so far. gcontracts by now provides three annotations:

@Target(ElementType.TYPE)
public @interface Invariant {
    Class value();
}

is used on type-level to specify the class invariant.

@Target(ElementType.METHOD)
public @interface Requires {
    Class value();
}

is used on method-level to specify preconditions.

@Target(ElementType.METHOD)
public @interface Ensures {
    public abstract Class value();
}

is used on method-level to specify postconditions.

As you can image, the real work of gcontracts is done in the AST transformation implementation which is done in the ContractValidationASTTransformation class.

In a nutshell, the transformation visits all code parts which are annotated with the annotations above and adds Java assertions at the appropriate places. Although there is no explicit support for contract-oriented programming, Java provides the assertion statement: whenever assertion-checking is enabled and an assertion is found, e.g.

assert count > 0 : "count must be greater than zero";

that boolean expression is evaluated and if false a ava.lang.AssertionError will be thrown. AST transformation uses that statement to inject pre-, postcondition and class-invariant checks (loop invariants are supposed to be added next) at appropriate code places. That includes that disabling checking of these constraints is as simple as deactivating Java assertions [1].

gcontracts on Github

I pushed the current source code to a github repository: http://github.com/andresteingress/gcontracts

Please keep in mind that the project's code has neither been thoroughly tested nor is being ready to be used in production code - but i am going to work on it, in order to release the first version as soon as possible.

[0] Object-Oriented Software Construction (2nd Edition), Bertrand Meyer, 1997, Prentice Hall
[1] Programming with Assertions, Sun

No comments:

Post a Comment