Contracts for Groovy

Tutorial - GContracts: A Design by Contract extension for Groovy

Introducing GContracts

Bertrand Meyer, the creator of the Eiffel programming language, introduced the concept of Design by Contract, a technique for improving software robustness and reliability by specifying formal contracts between class implementations.

In a way it is surprising that DbC never found its way into the programming language main stream. At times it has reportedly been under the top 25 requested issues of Sun's issue tracker for the Java programming language.

A contract is an agreement between a class and its clients (classes using the class) and is a first-level citizen of the programming language, standing on the same level as program instructions and expressions.

To see how contracts can help improve software reliability and robustness, we will start with the Rocket class implementation (see Listing 1). Before we jump right into specifying contracts, let’s have a look at a technique that seems similar to DbC at first sight: assertive programming. 

Listing 1

 

class Rocket  {
  int speed
  private boolean started
  
  def start() { started = true }
  boolean isStarted() { started }
  def accelerate()  { speed = speed + 5 }
  def brake() { speed = speed - 5 }
}

 

Assertive Programming

Programming is all about formalizing internal thought models. Even with the finest requirement documents and project methodologies, in the end internal thought models drive the actual doing.

But with internal models come implicit assumptions about the behaviour of certain things or software elements. Assertive programming is about adding assertions to code places where implicit assumptions need to be externalized and proven.

Let’s have a look back at the Rocket class. Once instantiated, a rocket object can be started, accelerated and braked. The class has an internal state, resembling its current speed, and a flag indicating whether the rocket has been started or not. As you will see during the course of this article, DbC especially fits domain classes in a sense of the domain-driven design (DDD) approach, rather than record classes.

The Rocket class author implicitly assumed that Rocket#brake() would never be called before Rocket#accelerate(). But as implicit assumptions should be embedded into the source code, we need to state this assumption. In order to do so, one option is to use Groovy’s power assertion statement: assert.

Listing 2

 

class Rocket  {
  int speed
  private boolean started

  def start() { started = true }
  boolean isStarted() { started }

  def accelerate()  { 
    assert started
    speed = speed + 5
  }

  def brake() { 
    assert started
    speed = speed - 5 
  }
}

 

If Rocket#brake() is now called before Rocket#accelerate(), an AssertionError is thrown and the following message is shown:

 

Assertion failed: 
assert started
       |
       false

 

As can be seen from the code sample, the assertion uses a Boolean expression to state its assumption that a rocket must always be started prior to brake it. In Rocket#brake() and Rocket#accelerate() we can see the first contract element: the precondition.

A precondition defines what must be true in order to let the method succeed. In our case, the rocket instance needs to be started, before Rocket#brake() or Rocket#accelerate() succeeds. It is the Rocket client’s responsibility to start the rocket instance. The stronger the precondition, the more work needs to be done by the client. This precondition is the first part of our Rocket class contract (as a side-note: a contract itself has no representing source code element, it’s simply an overlapping term for all conditions a class defines in terms of its DbC elements, one of them being preconditions). We will refer to a precondition as a contract element.

From assertions to contracts

As can be seen in Listing 2, adding assertions quickly bloats code and makes the actual business logic less readable. There is another not so obvious catch: assertions are not compliant with two important object-oriented principles, inheritance and polymorphism.

Just imagine if someone decided to create a subclass of Rocket overriding the Rocket#accelerate() method by increasing only by 1. As a result, the implementation of Rocket#brake() would break as speed becomes smaller than zero, being an invalid state for rocket objects (see Listing 3). With assertions, there is nothing a class author can do against overriding preconditions in subclasses.

Listing 3

class AnotherRocket extends Rocket {
    def accelerate()  { 
        assert started
        speed = speed + 1
    }
}
    
def rockets = [new AnotherRocket(), new Rocket()]
def result = rockets.collect { Rocket rocket ->
    rocket.start()
    rocket.accelerate()
    rocket.brake()
    rocket.speed
}

println result // output: [-4, 0]

 

Another disadvantage is that a subclass could easily erase the contract element defined by its parent class via prohibiting the assertion statement leading to maybe unexpected consequences.

It too violates the Liskov Substitution Principle, as it would result in subclass instances that cannot be replaced with parent class instances and vice versa, leading to corrupt and unpredictable runtime behaviour. As can be seen in the last code example, the Rocket instance behaves correctly when methods are called in the collect closure, whereas the instance of type AnotherRocket gets into an invalid object state. Just imagine the code in the collect closure would be hidden under various layers and indirections – without actually knowing the concrete type, a client could never deduce an inappropriate object state just by viewing the source code.

As we will see in the next section on GContracts, there are even more advantages by applying DbC instead of working with plain assertions:

  • Contract inheritance
  • Accessing old instance variable values in Boolean expressions
  • Getting the method's return value in Boolean expressions
  • Adding class contracts to the generated documentation
  • Package- or class-level configuration of assertion enabled code

We can’t go into each of the enlisted points in the rest of this article, just remember that DbC is more than simply putting assertions into source code – it is fully integrated in the object-oriented programming paradigm.

Design by Contract for Groovy

GContracts is an extension library for the Groovy programming language. It supports Groovy versions starting from 1.8.0, comes without any external dependencies and is released under a BSD-styled license.

To include GContracts in a Maven or Ivy based environment, you would simply include:

'org.gcontracts:gcontracts-core:1.2.5'

 

Once GContracts jars are available in the classpath, you just need to import the org.gcontracts.annotations.* package to define class contracts. As soon as GContracts is detected by the Groovy compiler, contracts will be automatically added to the generated bytecode during the compilation process.

Contract Annotations

The package org.gcontracts.annotations.* contains three annotations to be used for specifying contract elements on Groovy classes:

  • @Requires for method preconditions
  • @Ensures for method postconditions
  • @Invariant for class invariants

Each annotation will use a language construct that has been introduced in Groovy 1.8: closure annotation parameters. Groovy supports closures as annotation parameters by keeping Java byte-code compatibility. As closures are compiled to inner classes, this is solved by adding a java.lang.Class parameter to the annotation, so don't get confused when, looking at @Requires, @Ensures or @Invariant annotation implementations, you can’t find a Closure attribute – it will be resolved to java.lang.Class by the Groovy compiler.

To see how closure annotation parameters look like, take a look at Listing 4. It shows the imaginary annotation @Since which could be used to execute code only within a specific version range. The actual code checking the version number is specified within a closure annotation parameter.

Listing 4

 

@Since( { version >= 1.0 } )

 

In this case, the closure annotation parameter is the only annotation parameter. GContracts annotations make heavy use of closure annotation parameters to define contracts. So let's start by exploring GContracts contract annotations, starting with the precondition annotation @Requires.

 

Pages

Andre Steingress

What do you think?

JAX Magazine - 2014 - 03 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions