Heim

Design by contract

Design by contract (englisch Entwurf gemäß Vertrag) oder kurz DBC ist ein Konzept aus dem Bereich der Softwareentwicklung. Ziel ist das reibungslose Zusammenspiel einzelner Programmmodule durch die Definition formaler "Verträge" zur Verwendung von Schnittstellen, die über deren statische Definition hinausgehen. Entwickelt und eingeführt wurde es von Bertrand Meyer mit der Entwicklung der Programmiersprache Eiffel.

Das reibungslose Zusammenspiel der Programmmodule wird durch einen "Vertrag" erreicht, der beispielsweise bei der Verwendung einer Methode einzuhalten ist. Dieser besteht zum einen aus den Vorbedingungen (engl. precondition), also den Zusicherungen, die der Aufrufer einzuhalten hat; zum anderen aus den Nachbedingungen (engl. postcondition), also den Zusicherungen, die der Aufgerufene einhalten wird.

In einem konkreten Vertrag für eine Methode werden dann die Wertebereiche der Eingangsparameter genau klassifiziert, sowie deren Randbedingungen und Anforderungen festgehalten. Dementsprechend werden auch die Ausgangsparameter beschrieben. Es wird also exakt definiert, in welcher Situation eine Methode welche Werte verarbeiten kann. Sofern sich der Aufrufende daran hält, können keine Fehler auftreten und die Methode liefert garantiert keine unerwarteten Ergebnisse.

Somit wird zusätzlich zu den von der Programmiersprache selbst verwendeten Sicherungsverfahren, wie zum Beispiel der Typenkontrolle, auch eine semantische Sicherung ermöglicht. Das bedeutet, dass auch solche Bedingungen sicher eingehalten werden, die nicht mit den Mitteln der Programmiersprache selbst beschrieben werden können.

Durch die Definition von Vor- und Nachbedingung kann ein Modul durch ein beliebiges ausgetauscht werden, wenn dieses denselben "Vertrag" erfüllt.

Inhaltsverzeichnis

Vor- und Nachbedingungen

Jedem Unterprogramm werden Vorbedingungen (preconditions) und Nachbedingungen (postconditions) zugeordnet. Die Vorbedingungen legen fest, unter welchen Umständen das Unterprogramm aufrufbar sein soll. Beispielsweise darf ein Unterprogramm zum Lesen aus einer Datei nur dann aufgerufen werden, wenn die Datei vorher erfolgreich geöffnet wurde. Die Nachbedingungen legen die Bedingungen fest, die nach Abschluss des Unterprogrammaufrufs gegeben sein müssen.

Vor- und Nachbedingungen werden als boolesche Ausdrücke formuliert. Ist eine Vorbedingung nicht erfüllt (d.h. ihre Auswertung ergibt false, also "nicht zutreffend"), liegt ein Fehler im aufrufenden Code vor: Dort hätte dafür gesorgt werden müssen, dass die Vorbedingung erfüllt ist. Ist eine Nachbedingung nicht erfüllt, liegt ein Fehler im Unterprogramm selbst vor: Das Unterprogramm hätte dafür sorgen müssen, dass die Nachbedingung erfüllt ist.

Vor- und Nachbedingung bilden daher eine Art Vertrag (englisch contract): wenn der aufrufende Code die Vorbedingung erfüllt, dann ist das Unterprogramm verpflichtet, die Nachbedingung zu erfüllen.

Liskov'sches Substitutionsprinzip

Wendet man das liskovsche Substitutionsprinzip (nach Barbara Liskov, LSP) auf Vor- und Nachbedingungen an, erhält man die folgende Aussage:

Sind vor dem Aufruf des Unterprogramms der Unterklasse die Vorbedingungen der Oberklasse erfüllt, so muss das Unterprogramm die Nachbedingungen der Oberklasse erfüllen.

Dies bedeutet, dass ein Unterprogramm einer Unterklasse bei der Gestaltung seiner Vor- und Nachbedingungen nicht frei ist: es muss mindestens den durch die Vor- und Nachbedingungen formulierten "Vertrag" erfüllen. Das heißt, es darf die Vorbedingungen nicht verschärfen (es darf vom aufrufenden Code nicht mehr verlangen als in der Oberklasse verlangt), und es darf die Nachbedingungen nicht aufweichen (es muss mindestens so viel garantieren wie die Oberklasse).

Unterklassen

Unterklassen müssen bei Design by Contract folgende Regeln bezüglich der Oberklassen befolgen:

Grenzen des Verfahrens

Design By Contract kann nur auf Softwareeigenschaften angewandt werden, die sich auch als Vor- und Nachbedingung formulieren lassen. Bedingungen wie "vor Routine A muss Routine B aufgerufen worden sein" lassen sich (einigermaßen mühsam) über Hilfsvariablen abbilden. Ähnlich können Bedingungen wie "Routine A ruft in ihrem Verlauf immer auch Routine B auf" (gerade im objektorientierten Bereich wichtig) über Nachbedingungen und Modulinvariante gefasst werden.

Das Formulieren der Vor- und Nachbedingungen kostet den Programmierer Zeit, die er häufig lieber für die (vermeintlich) eigentliche Programmentwicklung nutzen würde.

Wird die Semantik eines Unterprogramms vollständig in Vorbedingungen, Nachbedingungen, und Modulinvarianten gefasst, erhält man eine funktionale Spezifikation des Unterprogramms. Compiler für funktionale Programmiersprachen können solche Nachbedingungen in einigen Fällen direkt in ausführbaren Code umsetzen; insofern zeigt ein bis zur Perfektion getriebenes Vorgehen nach Design By Contract einen Schritt zur nächst abstrakteren Programmiermethodik an.