GContracts: Eine Bibliothek zur Erweiterung der Groovy-Sprache

Design by Contract mit Groovy

Andre Steingress

Groovy ist neben Scala und JRuby eine der populärsten Programmiersprachen für die virtuelle Java Machine. Auf Basis der in Groovy 1.6 eingeführten AST- (Abstract-Syntax-Tree-)Transformationen bietet GContracts eine in Java implementierte Bibliothek für die Erweiterung der Groovy-Programmiersprache um die Möglichkeit von „Design by Contract“.

Design by Contract

Eine große Aufmerksamkeit erhält derzeit das neue Google-Projekt Contracts for Java, mit dem sich Java-Code mit Design-by-Contract-typischen Annotationen versehen lassen.

Was hat es mit dem Design-by-Contract-Ansatz auf sich?

Passend zum Thema präsentieren wir den Top-Artikel „Design by Contract mit Groovy“ aus dem Java Magazin, in dem Andre Steingress in das Design-by-Contract-Muster einführt und die Groovy-basierte Alternative GContracts vorstellt.

gcontracts ist eine unter BSD lizensierte Bibliothek für die Erweiterung von Groovy um Design by Contract [1]. Design by Contract (DbC) ist ein von Bertrand Meyer eingeführter Begriff für eine Technik die in heutigen Mainstream Programmiersprachen nur selten Unterstützung findet. DbC ist eine Technik, um die Spezifikation von Softwarekomponenten (i. d. R. wird in diesem Artikel der Begriff der Komponente mit jener der Klasse gleichgesetzt) direkt im Quellcode der jeweiligen Komponente vorzunehmen. Grundlegende Idee dabei ist, durch die direkte Verzahnung von Quellcode und Spezifikation die Korrektheit von Komponenten (im Sinne der Übereinstimmung mit der Spezifikation) auch zur Laufzeit durch zusätzlich generierten Programmcode zu überprüfen. Befindet sich beispielsweise eine Komponente durch den Aufruf einer Methode in einem ungültigen Zustand, so wird dies als Programmierfehler interpretiert und der Programmfluss abgebrochen.

Back in History

Design by Contract wurde von Bertrand Meyer in den 80er-Jahren eingeführt [3] und lässt sich in den Sprachfeatures von „Mainstreamprogrammiersprachen“ nicht finden. Die grundlegende Idee dieser Programmiertechnik basiert auf dem Hoare-Kalkül. Das Hoare-Kalkül ist ein formales System des Mathematikers C.A.R. Hoare, das benutzt wird, um mathematische Aussagen über die Korrektheit von Programmen zu treffen [4]. Das zentrale Element dieses formalen Systems ist das Hoare-Tripel.

Ein Hoare-Tripel besteht aus einer Vorbedingung P, einer Nachbedingung Q und einem Programm S. Ein konkretes Tripel {P} S {Q} drückt also aus, dass für die Ausführung von S die Vorbedingung P erfüllt sein muss. Im Gegenzug wird die Bedingung Q durch die Programmlogik S erfüllt. Mit Design by Contract wurde die Idee des Hoare-Kalküls in die objektorientierte Programmierung übertragen.

GContracts in Action

Zentrales Element bei der Anwendung von DbC sind Verträge (Contracts). In diesem Zusammenhang ist die Unterscheidung zwischen Klienten und Anbietern in der Objektorientierung von Bedeutung: eine Klasse A ist Klient (Client) einer Klasse B, wenn A zur Laufzeit Objektinstanzen von B verwendet. Klasse B wird in diesem Sinne auch Anbieter (Supplier) genannt.

Abb. 1: Unterscheidung zwischen Klient und Anbieter

Verträge werden zwischen Klienten und Anbietern vereinbart und bei der Methodendefinition einer Klasse im Quellcode angegeben. Ein Vertrag besteht aus zwei Teilen: einer Vorbedingung (Precondition) und einer Nachbedingung (Postcondition). Vor- und Nachbedingungen stellen Assertionen dar, die durch boolesche Ausdrücke im Quellcode angegeben werden. Ist der boolesche Ausdruck falsch, so wird der Programmfluss unterbrochen und eine Ausnahme geworfen.

Abb. 2: Vertragsteile

Als Beispiel für die Anwendung von Assertionen sehen wir uns die Klasse ItemList in Listing 1 an.

Listing 1
class ItemList {  
    // ...
    @Requires({ item != null && !is_borrowed(item) })
    @Ensures({ is_borrowed(item) })
    def void borrow_item(Item item, Date from, Date to)  {
      itemRecords.add(ItemRecord.create(item, from, to))
    }
    // ...
}

Die Methode borrow_item der Klasse ItemList wird immer dann von Klienten dieser Klasse verwendet, wenn ein Item für eine bestimmte Zeit ausgeliehen wird.

Der Vertrag zwischen Klienten und der Klasse ItemList wird mit GContracts über die beiden Java-Annotationen @Requires und @Ensures ausgedrückt, wobei mit @Requires eine Vorbedingung und mit @Ensures eine Nachbedingung angegeben wird. Generell wird bei der Verwendung der von GContracts angebotenen Annotationen der eigentliche boolesche Ausdruck der jeweiligen Assertion innerhalb einer Groovy Closure definiert. Eine Closure stellt vereinfacht dargestellt einen Codeblock dar, der zur Laufzeit in einem bestimmten Kontext ausgeführt wird. Die explizite Angabe des return Statements ist in Groovy Closures nicht notwendig und wird in den in GContracts verwendeten Annotationen üblicherweise nicht angegeben. @Requires({ }). Die in diesem Beispiel angedeutete Notation ist in Groovy syntaktisch für Annotationen erlaubt und wurde in einem Blog-Post von Peter Niederwieser (Spock Testframework, Groovy Comitter) vorgestellt [2].

Wieder zurück zu unserem ersten Beispiel. Die Definition der Vor- und Nachbedingung der Methode borrow_item hat nicht nur dokumentarischen Wert, sondern wird zur Laufzeit bei jedem Aufruf der Methode überprüft. Wird ein Fehler in der Implementierung von borrow_item eingeführt, der eine Verletzung des Vertrags zur Folge hat, wird bei der Programmausführung beim Aufruf der Methode borrow_item ein java.lang.AssertionError ausgelöst.

java.lang.AssertionError: [postcondition] In method  violated: { is_borrowed(item) }.

Verträge werden üblicherweise nicht für die Validierung von Benutzereingaben verwendet, sondern dienen der expliziten Definition der Korrektheit von Methoden und Klassen. Die Definition von Vor- und Nachbedingungen wird in der Regel nur bei öffentlichen Methoden vorgenommen. Ein starker boolescher Ausdruck einer Vorbedingung drückt größere Verantwortung für den Klienten, ein schwächerer Ausdruck größere Verantwortung für den Anbieter aus. Die schwächste Form der Vorbedingung wäre demnach @Requires({ true }). Dieser Ausdruck stellt im übrigen den Standardfall ohne Verwendung von DbC dar und führt zu einem defensiven Programmierstil und impliziten Annahmen über die Verwendung von Komponenten. Im Folgenden wird auf weitere Details der in GContracts angebotenen Assertionen eingegangen und deren Verwendung mit Beispielen dargelegt.

Geschrieben von
Andre Steingress
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.