Nathan Burgener Design by Contract ‐ Modul SWE Nathan Burgener Inhaltsverzeichnis 1 Was ist Design by Contract......................................................................................................3 1.1 Überblick ................................................................................................................................3 1.2 Design by Contract mit Methoden ...............................................................................4 1.3 Design by Contract mit Klassen ....................................................................................5 1.4 Vererbung ..............................................................................................................................6 Design by Contract in Java...............................................................................................................7 1.5 Überblick ................................................................................................................................7 1.6 Assertions...............................................................................................................................7 1.6.1 Funktionsweise ...........................................................................................................7 1.6.2 Assertions und Design by Contract ....................................................................8 1.7 iContract .................................................................................................................................8 1.7.1 Einführung ....................................................................................................................8 1.7.2 Preconditions...............................................................................................................8 1.7.3 Postconditions .............................................................................................................9 1.7.4 Invarianten................................................................................................................. 10 1.7.5 Vererbung................................................................................................................... 11 1.7.6 Ausnahmebehandlung .......................................................................................... 11 1.8 Beispiele für bereits bestehende Methoden......................................................... 11 Zertifizierungsstelle für Komponenten .................................................................................. 13 Nathan Burgener 1 Was ist Design by Contract 1.1 Überblick Design by Contract ist ein Konzept aus der Softwaretechnik von Bertrand Meyer. Dabei soll die Qualität von Programmmodulen verbessert werden, indem formale Verträge generiert werden. Bei Design by Contract soll für jede Methode eine Art Vertrag erstellt werden, welcher dann vom Aufrufer aber auch von der Methode selber eingehalten werden muss. Die Verträge existieren in zwei Arten. Als Klasseninvarianten und als Vor‐ und Nachbedingungen für Funktionen dieser Klassen. Da das Verfassen eines Vertrages für ”grosse“ Funktionen schwierig wird, ist davon auszugehen, dass Funktionen von den meisten Programmierern auf ein Minimum herabgebrochen werden. Das dürfte ein Plus an Sicherheit und Lesbarkeit des gesamten Codes mit sich bringen. Der eigentliche Sinn von Design by Contract besteht nun darin, genau diese Zusicherungen zur Laufzeit auszuwerten. Das heisst, dass das Programm vor und nach jedem Methodenaufruf kontrolliert ob der Systemzustand immer noch der Spezifikation entspricht. Durch das Hinzufügen von solchen Integritätsbedingungen ist der Code austauschbarer, besser lesbar, einfacher zusammenführbar und somit langfristig stabiler, zuverlässiger und preiswerter. Die Vorteile beim Verwenden von Design by Contract liegen auf der Hand. ‐ Hilfe beim Schreiben korrekter Software Um die Reliability der Software zu verbessern sollte man Schnittstellen sehr genau definieren. Design by Contract zwingt den Software Engineer dies schon früh im Design Prozess zu tun. Fehler werden so früher erkennt. Der Programmierer wird angeregt sehr genau über seinen Code nachzudenken. So bemerkt er auch früher die Fehler im Programm und kann reagieren und erhöht so auch die Wahrscheinlichkeit, dass das Programm den Anforderungen später auch gerecht wird. ‐ Hilfe bei der Dokumentation Durch die Angabe der Pre‐ und Postconditions ist schon ein Teil der Dokumentation erledigt. Die Methode ist so sauber dokumentiert und es ist schön ersichtlich, was für Vorbedingungen gelten müssen und Nathan Burgener was die Methode zurückgibt. ‐ Hilfe beim Testen, Debuggen und bei der Qualitätssicherung Durch das generieren der Verträge werden bereits auch Ansätze für das Testen erstellt. Für jeden Vertrag kann auch ein Test geschrieben werden. ‐ Hilfe beim Erstellen von effektiven Ausnahmebehandlungen Basierend auf dem Vertrag können die Ausnahmebehandlungen direkt definiert werden. 1.2 Design by Contract mit Methoden Für eine Methode wird bei Design by Contract ein Vertrag erstellt, welcher beschreibt, auf welche Art und Weise auf die Methode zugegriffen wird. Ebenfalls werden die Auswirkungen der Methode auf den Programmzustand festgelegt. Um die Software zu spezifizieren und Aussagen über ihre Korrektheit zu machen wird das Hoare‐Triple verwendet. Das Hoare‐Triple besteht aus 3 Elementen: Vorbedingung P, Nachbedingung Q und Codesegment S. Das Codesegment entspricht dabei genau einer Methode des Programms. Das Hoare‐Tripel sagt aus, dass wenn die Methode S aufgerufen wird, während P gilt, so wird nach Ausführung der Methode Q gelten. Dies kann folgendermassen geschrieben werden. {P} S {Q} Als Beispiel dafür soll eine Divisionsmethode doDivision angeschaut werden. Diese Methode erhält als Übergabeparameter den Divisor d. doDivision(int d) { globalResult = globalDivident / d; } Es ist klar ersichtlich, dass der Divisor nicht 0 sein darf. Weiter wird erwartet, dass der Divisor nicht negativ ist. Als Programmierer der Methode kann man jetzt mit if‐then Anweisungen mühsam diese Überprüfungen machen. Doch man weiss nicht, wie dem Benutzer klar zu machen, dass seine Eingabe nicht korrekt war. In Java geschieht dies normalerweise mit boolschen Rückgabeparametern oder es wird eine Exception ausgelöst. Um dieses Problem mit Hilfe von Design by Contract zu lösen, wird nun ein Vertrag formuliert, der den Aufrufer verpflichtet die Methode korrekt zu verwenden. Als Belohung erhält er dafür die Garantie, dass das Resultat Nathan Burgener korrekt berechnet wird. Wie bereits genannt wird dieser Vertrag mit Hilfe des Hoare‐Tripels formuliert: {d > 0} doDivision {globalResult = globalDivident / d} Was geschieht aber jetzt, wenn ein Vertrag verletzt wurde? Die meisten Programmiersprachen werfen dann eine Exception. So ist schnell ersichtlich, wo im Programm sich der Fehler befindet. Wird eine Vorbedingung verletzt, so ist der Aufrufer seine Pflicht einen gültigen Programmzustand herzustellen nicht nachgekommen und das Programm kann dem Benutzer dies mitteilen, damit er die Eingabe korrigiert. Ist hingegen eine Nachbedingung verletzt worden, scheint der Programmierer der jeweiligen Methode seine Arbeit nicht sorgfältig erledigt zu haben. 1.3 Design by Contract mit Klassen Die Vor‐ und Nachbedingungen geben an, was bei den Aufrufparameter und dem Rückgabewert der Methode erwartet wird. Zusätzlich kann man jetzt noch Integritätsbedingungen festlegen. Die sogenannten Klasseninvarianten. Klasseninvarianten sind Bedingungen, die für die gesamte Klasse und nicht nur für einzelne Methoden gelten sollen. Sie werden bei jedem Methodenaufruf zu Beginn und an seinem Ende geprüft. Die Klasseninvariante beschreibt den gültigen Zustand eines Objektes dieser Klasse. Als gültigen Zustand meint man hier, dass das Objekt von aussen sinnvoll verwendet werden darf. Klasseninvarianten dürfen während der Nutzung einer Klasse nicht verletzt werden. Einzige Ausnahme davon ist der Zeitraum der Ausführung einer Methode einer Klasse. Hier darf eine Klasseninvariante verletzt werden, wenn die Funktion sicherstellt, dass diese nach ihrer Abarbeitung wieder gilt. Als Beispiel gibt es hier eine WG‐Küche. Jeder darf die Küche verwenden und auch verschmutzen. Er muss aber sicherstellen, dass die Küche beim Verlassen wieder tiptop aufgeräumt ist. Als Beispiel in Java wird eine Klasse Stack genommen. Die Invariante „items“ darf hier nicht kleiner als 0 sind und auch nicht grösser als die Varaible max. public class Stack { private int items; // Anzahl der Elemente /** invariant items>=0; items<=max **/ public void removeItem(){ } Nathan Burgener //Element löschen 1.4 Vererbung Wird eine Unterklasse erzeugt, welche von der Elternklasse erbt, so werden auch die Zusicherungen vererbt. Dabei gelten die folgenden Regeln: Für die Invariante gilt das gleiche wie für die Nachbedingung ‐ Die in der Elternklasse geltende Invariante bleibt bestehen und wird mit der Invariante der Unterklasse weiter verschärft. Für die Vorbedingung einer Methode ‐ Die Vorbedingung kann schwächer werden, da ein „oder“ mit Vorbedingung der Elternklasse und der neuen Vorbedingung gemacht wird. Für die Nachbedingung einer Methode ‐ Die Vorbedingung kann stärker werden, da ein „und“ mit Vorbedingung der Elternklasse und der neuen Vorbedingung gemacht wird. Nathan Burgener Design by Contract in Java 1.5 Überblick In Java wurden während den Jahren diverse Tools für die Implementation von Design by Contract entwickelt. So gibt es Beispielsweise iContract, ContractJ, Jass und weitere. Einige Tools arbeiten auch mit den Assertions von Java. Viele Tools wurden jedoch in den letzten Jahren nicht mehr weiter entwickelt und es gibt auch für keines der Tolls ein Plugin für Netbeans. Ersichtlich ist auch noch, dass die neueren Tools fast alle mit Aspektorientierter Programmierung arbeiten. 1.6 Assertions 1.6.1 Funktionsweise In Java 1.4 wurden Assertions als neues Konzept zur Java‐ Sprachunterstütung eingeführt. Das Assertions Statement überprüft gewisse Bedingungen und wird entweder true oder false. Wird beispielsweise die Prüfung einer Inputvariable vergessen, so kann es unter Umständen passieren, dass das Programm vorerst weiterläuft, dann aber an einem späteren Punkt zur Runtime abstürzt. Um dies zu verhindern kann man mit einem assert Statement früh eine gewünschte Bedingung prüfen. Hier ein Beispiel für eine solche Überprüfung: public void doDivision(int d) { assert(d > 0); //Weiterführender Code } Resultiert ein false aus der Assertion, so bricht das Programm mit einem java.lang.AssertionError ab. Assertions werden in erster Linie zur frühen Fehlererkennung verwendet. Der Vorteil gegenüber den if‐Anweisungen ist die Möglichkeit, Assertions zu deaktivieren. Bei deaktivierten Assertions werden die assert Anweisungen nicht ausgewertet und es entsteht keine Effizienzeinbusse. AssertionErrors haben etwa die gleiche Bedeutung wie RuntimeExceptions. Es ist wichtig zu unterscheiden zwischen den unchecked AssertionErrors bzw. RuntimeExceptions und den checked Nathan Burgener Exceptions, die deklariert und behandelt werden müssen. Ein Vorteil von Exceptions gegenüber Assertions ist die Möglichkeit, ein Exception Handling zu definieren für abnormale Fälle, von denen man weiss, dass sie eintreten und behandelt werden können. 1.6.2 Assertions und Design by Contract Wie in der Beschreibung der Assertions ersichtlich ist, werden Vor‐ und Nachbedingungen sowie Klasseninvarianten nicht unterstützt. Assertions kann also nicht wirklich für Design by Contract verwendet werden. Assertions haben immer die gleiche Semantik. Die Information ob es sich um pre‐ oder postconditions handelt, lässt sich nur anhand des zugehörigen Kommentars erkennen und sind deshalb weniger formell. Weiter sind Assertions nicht frei von Seiteneffekten. Dadurch können sich Fehler einschleichen, die ohne sie nicht möglich gewesen wären und die schwer aufzufinden sind. Assertions lassen sich nicht vererben. Somit muss in der Unterklasse auch alles noch einmal implementiert werden, was eine Duplizierung von Code bedeutet. 1.7 iContract 1.7.1 Einführung Die Implementierung der Zusicherungen in Java erfolgt in den Interfaces der Klassen. Sie stehen jeweils als Kommentare vor und nach der Methode, so dass sie nicht mit dem Rest des Quellcodes vermischt werden. Es gibt also ein weiteres File, in dem alle Verträge definiert sind. Die Zusicherungen wir mittels folgenden Abkürzungen realisiert: ‐ Precondition: @pre ‐ Postcondition: @post ‐ Invarianten: @invariant iContract ist ein Präprozessor für Java. Um dieses Tool zu verwenden wird der Code zuerst durch den iContract gelassen. Dieser erzeugt aus den Kommentaranweisungen Assertion‐Checks und fügt diese in den Source Code ein. 1.7.2 Preconditions In iContract werden die Preconditions im Header der Methode mit der Direktive @pre beschrieben. Hier ein Beispiel: Nathan Burgener /** * @pre f >= 0.0 */ public float wurzel(float f) { ... } In diesem Beispiel muss der Parameter f der Funktion wurzel() grösser oder gleich Null sein. 1.7.3 Postconditions Postconditions werden auch die die Preconditions im Header der jeweiligen Methode definiert. Dafür wird die Direktive @post verwendet. Hier ein Beiepiel dafür /** * @pre f >= 0.0 * @post Math.abs((return * return) - f) < 0.001 */ public float wurzel(float f) { ... } Diese Postcondition besagt, dass die Methode die Wurzel der Zahl f berechnet und dies mit einer maximalen Abweichung von 0.001. iContract verwendet einige spezielle Notationen für die Postconditions. Beispielsweise steht das return für den Rückgabewert der Methode. Es wird also zur Laufzeit durch den Rückgabewert der Methode ersetzt. In einer Postcondition muss oftmals auf einen Wert zugegriffen, der vor und nach der Ausführung der Methode nicht gleich ist. Um dies zu unterscheiden, kann mit @pre in der Postcondition auf den Wert vor dem Ausführen der Methode zugegriffen werden. Hier ein Beispiel dazu: Nathan Burgener /** * Fügt ein Element zu einer Collection hinzu * * @post c.size() = [email protected]() + 1 * @post c.contains(o) */ public void hinzu(Collection c, Object o) { ... } Hier wird mit [email protected]() auf die Grösse der Collection vor dem Ausführen der Methode zugegriffen. 1.7.4 Invarianten Die Invarianten werden mit iContract im Header der Klasse definiert. Hier ein Beispiel dafür: /** * A PositiveInteger ist ein Integer, der garantiert positiv ist. * * @inv intValue() > 0 */ class PositiveInteger extends Integer { //Code der Klasse } Die Invariante garantiert hier, dass der PositivsIntegers’s Wert immer grösser als Null. Dieses Assertions wird immer vor und nach der Ausführung der Methoden dieser Klasse geprüft. Nathan Burgener 1.7.5 Vererbung iContract unterstützt auch die Vererbung. Das heisst alle Invarianten, Preconditions und Postconditions die in der Super-Klasse definiert werden, müssen auch von der Unterklasse eingehalten werden. In den Unterklassen können natürlich noch weitere hinzugefüt werden. Das Konzept funktioniert auch mit Interfaces. Wenn es 2 Interfaces gibt, in welchen Invarianten, Preconditions und Postconditions definiert werden und eine Klasse diese beide implementiert, sind diese auch auch für die Klasse gültig. 1.7.6 Ausnahmebehandlung Das Tool iContract ermöglicht bei einer Verletzung der Zusicherung, die eine Ausnahme(Exception) wirft, diese gleich abzufangen. So muss das Programm nicht abgebrochen werden, wenn eine Ausnahme voraussehbar ist. Hier ein Beispiel dafür: /* *@pre i>= 0 #ArrayIndexOutOfBoundsException */ 1.8 Beispiele für bereits bestehende Methoden /** * Creates a new user for the application * @param vorname String, which represents the users first name * @param nachname String, which represents the users last name * @param benutzername String, which represents the user name * @param passwort String, which represents the password */ public void newReg(String vorname, String nachname, String benutzername, String passwort) { EntityManager em = emf.createEntityManager(); EntityTransaction utx = em.getTransaction(); NewUser nUser = new NewUser(); nUser.setVorname(vorname); nUser.setNachname(nachname); nUser.setBenutzername(benutzername); Nathan Burgener nUser.setPasswort(passwort); try { utx.begin(); em.persist(nUser); utx.commit(); } catch(Exception ex){ try { utx.rollback(); } catch (Exception e) {} throw new RuntimeException("Error creating entity", ex); } finally { em.close(); } } Preconditions für diese Methode: • vorname darf nicht Null sein • nachname darf nicht Null sein • benutzername darf nicht Null sein • passwort darf nicht Null sein • passwort muss mehr als 10 Zeichen beinhalten • emf.createEntityManager() darf nicht Null sein • em.getTransaction() darf nicht Null sein Postconditions für diese Methode: • DB erhält neues User Object • em.isOpen() ist false Invariante für die Klasse: • Der EntityManagerFactory emf darf nie null sein. Nathan Burgener Zertifizierungsstelle für Komponenten An der ETH wurde 1998 mit einem Projekt mit dem Namen Trusted Components gestartet. Das ganze verwendet das Konzept von Design by Contract. Dafür muss eine detaillierte Beschreibung gemacht werden und die Qualität der Software garantiert werden. Zur Qualität gehören Punkte wie Sicherheit, Robustheit, Korrektheit, Performance usw. Der grosse Vorteil von Trusted Components liegt dabei in der Wiederverwendung. Die zertifizierten Komponenten können jetzt einfach wiederverwendet werden. So wird viel Zeit beim Implementieren von neuen Programmen gespart. Das Projekt wurde aber nie richtig fertiggestellt. Der Grund dafür ist nicht bekannt. Ich denke aber, dass so etwas nur sehr schwer umsetzbar ist. Zuerst müsste ein allgemeines Framework definiert und erstellt werden, damit alle mit dem gleichen arbeiten und dann müsste dieses Framework auch von den Entwicklungsumgebungen unterstützt werden. Das schwierige ist jetzt wohl die Punkte Sicherheit, Robustheit, Korrektheit, Performance usw. auch zu garantieren. Nathan Burgener Quellen http://www.wikiservice.at/dse/wiki.cgi?DesignByContract http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html http://www.javaworld.com/javaworld/jw-02-2001/jw-0216cooltools.html?page=4 http://archive.eiffel.com/doc/manuals/technology/contract/ www.gruntz.ch/courses/sem/ws06/DBC.pdf http://swt.cs.tuberlin.de/lehre/mwsp/ws0607/ausarbeitungen/Ausar beitung-6.pdf www.metafinanz.de/fileadmin/Dokumente/Leistungen/1_Download/2 007_hsr_eclipse.pdf http://seal.ifi.uzh.ch/fileadmin/User_Filemount/Vorlesungs_Folien/Se minar_SE/SS06/kandrical_design_contract.pdf http://www2.hsaugsburg.de/informatik/projekte/testen/ss2006/builtI nTest.pdf