Warum wird Design by Contract in den meisten modernen Programmiersprachen nur in begrenztem Umfang unterstützt?


40

Ich habe kürzlich Design by Contract (DbC) entdeckt und finde es eine äußerst interessante Art, Code zu schreiben. Unter anderem scheint es folgendes zu bieten:

  • Bessere Dokumentation. Da der Vertrag die Dokumentation ist, ist es unmöglich, dass einer nicht mehr aktuell ist. Da im Vertrag genau angegeben ist, was eine Routine tut, kann die Wiederverwendung unterstützt werden.
  • Einfacheres Debuggen. Da die Programmausführung in dem Moment stoppt, in dem ein Vertrag fehlschlägt, können sich Fehler nicht verbreiten, und die bestimmte verletzte Behauptung wird vermutlich hervorgehoben. Dies bietet Unterstützung bei der Entwicklung und bei der Wartung.
  • Bessere statische Analyse. DbC ist im Grunde genommen nur eine Implementierung der Hoare-Logik, und die gleichen Prinzipien sollten gelten.

Die Kosten scheinen im Vergleich eher gering zu sein:

  • Extra Fingertippen. Da müssen die Verträge präzisiert werden.
  • Benötigt einige Schulungen, um sich mit dem Schreiben von Verträgen vertraut zu machen.

Jetzt, da ich in erster Linie mit Python vertraut bin, ist mir klar, dass es tatsächlich möglich ist, Vorbedingungen aufzuschreiben (nur Ausnahmen für unangemessene Eingaben auszulösen), und es ist sogar möglich, Assertions zu verwenden, um bestimmte Nachbedingungen erneut zu testen. Es ist jedoch nicht möglich, bestimmte Funktionen wie "alt" oder "Ergebnis" ohne zusätzliche Magie zu simulieren, die letztendlich als unpythonisch angesehen werden würde. (Außerdem gibt es einige Bibliotheken, die Unterstützung bieten, aber letztendlich ist es falsch, sie zu verwenden, da die meisten Entwickler dies nicht tun.) Ich gehe davon aus, dass dies ein ähnliches Problem für alle anderen Sprachen ist (außer natürlich) , Eiffel).

Meine Intuition sagt mir, dass der Mangel an Unterstützung auf eine Art Ablehnung der Praxis zurückzuführen sein muss, aber die Online-Suche war nicht fruchtbar. Ich frage mich, ob jemand erklären kann, warum die meisten modernen Sprachen so wenig Unterstützung bieten? Ist DbC fehlerhaft oder zu teuer? Oder ist es aufgrund von Extreme Programming und anderen Methoden einfach veraltet?


Klingt nach einem übermäßig komplizierten Weg, eine testgetriebene Programmierung durchzuführen, ohne den Vorteil zu haben, dass Sie auch Ihr Programm testen müssen.
Dan

3
@Dan, nicht wirklich, ich betrachte es eher als Erweiterung des Typensystems. Zum Beispiel braucht eine Funktion nicht nur ein Integer-Argument, sondern eine Integer-Zahl, die vertraglich verpflichtet ist, größer als Null zu sein
Carson63000

4
@Dan-Codeverträge reduzieren die Anzahl der durchzuführenden Tests erheblich .
Rei Miyasaka

24
@ Dan, ich würde eher sagen, dass TDD die Verträge der Armen sind, nicht umgekehrt.
SK-logic

In dynamischer Sprache können Sie Ihre Objekte mit Verträgen "dekorieren", die auf einer optionalen Flagge basieren. Ich habe eine Beispielimplementierung , die Umgebungsflags verwendet, um vorhandene Objekte mit den Verträgen zu patchen. Ja, die Unterstützung ist nicht nativ, aber einfach hinzuzufügen. Das Gleiche gilt für Testgeschirre. Sie sind nicht ursprünglich, aber einfach hinzuzufügen / zu schreiben.
Raynos

Antworten:


9

Möglicherweise werden sie in praktisch jeder Programmiersprache unterstützt.

Was Sie brauchen, sind "Behauptungen".

Diese können leicht als "if" -Anweisungen codiert werden:

if (!assertion) then AssertionFailure();

Mit dieser Option können Sie Verträge schreiben, indem Sie solche Zusicherungen für Eingabeeinschränkungen oben in Ihren Code einfügen. Diese an den Rückgabepunkten sind Ausgabeeinschränkungen. Sie können sogar Invarianten im gesamten Code hinzufügen (obwohl dies nicht Teil von "Design by Contract" ist).

Ich behaupte, sie sind nicht weit verbreitet, weil Programmierer zu faul sind, um sie zu programmieren, und nicht, weil man das nicht kann.

Sie können diese in den meisten Sprachen ein wenig effizienter gestalten, indem Sie eine boolesche Konstante zur Kompilierungszeit "checking" definieren und die Anweisungen ein wenig überarbeiten:

if (checking & !Assertion) then AssertionFailure();

Wenn Ihnen die Syntax nicht gefällt, können Sie auf verschiedene Sprachabstraktionstechniken wie Makros zurückgreifen.

Einige moderne Sprachen bieten dafür eine gute Syntax, und ich denke, Sie meinen das mit "Unterstützung moderner Sprachen". Das ist Unterstützung, aber es ist ziemlich dünn.

Was die meisten modernen Sprachen nicht geben, sind "zeitliche" Aussagen (über beliebige vorherige oder folgende Zustände [zeitlicher Operator "eventuell"], die Sie benötigen, wenn Sie wirklich interessante Verträge schreiben möchten. IF-Anweisungen helfen nicht du hier.


Das Problem, das ich sehe, wenn ich nur auf Asserts zugreifen kann, ist, dass es keine effektive Möglichkeit gibt, Nachbedingungen für Befehle zu überprüfen, da Sie häufig den Nachbedingungsstatus mit dem Vorbedingungsstatus vergleichen müssen (Eiffel nennt dies "alt" und leitet ihn automatisch an die Nachbedingungsroutine weiter .) In Python kann diese Funktionalität mit Hilfe von Dekoratoren problemlos neu erstellt werden, wenn jedoch die Zeit zum Deaktivieren von Behauptungen knapp wird.
Ceasar Bautista

Wie viel des vorherigen Zustands spart Eiffel tatsächlich? Da es vernünftigerweise nicht wissen kann, auf welchen Teil Sie zugreifen / ihn ändern können, ohne das Halteproblem zu lösen (indem Sie Ihre Funktion analysieren), muss es entweder den vollständigen Maschinenzustand oder als Hueristik nur einen sehr flachen Teil davon speichern. Ich vermute letzteres; und diese können durch einfache skalare Zuordnungen vor der Vorbedingung "simuliert" werden. Ich würde gerne erfahren, dass Eiffel es anders macht.
Ira Baxter

7
... habe gerade nachgesehen, wie Eiffel funktioniert. "old <exp>" ist der Wert von <exp> beim Eintritt in die Funktion, daher werden beim Eintritt in die Funktion wie erwartet flache Kopien erstellt. Sie können sie auch tun. Ich bin damit einverstanden, dass der Compiler die Syntax für pre / post / old implementiert, ist praktischer als dies alles von Hand zu tun, aber der Punkt ist, dass man dies von Hand tun kann und es wirklich nicht schwer ist. Wir sind zurück zu faulen Programmierern.
Ira Baxter

@IraBaxter No. Code wird einfacher, wenn Sie den Vertrag von der eigentlichen Logik trennen können. Wenn der Compiler Vertrag und Code unterscheiden kann, kann dies die Duplizierung um ein Vielfaches reduzieren . In D können Sie beispielsweise einen Vertrag für eine Schnittstelle oder eine Superklasse deklarieren, und die Zusicherungen werden auf alle implementierenden / erweiterenden Klassen angewendet, unabhängig vom Code in ihren Funktionen. ZB mit Python oder Java müssen Sie die gesamte superMethode aufrufen und möglicherweise die Ergebnisse wegwerfen, wenn Sie nur die Verträge ohne Duplikation überprüfen möchten. Dies hilft wirklich bei der Implementierung von sauberem LSP-konformen Code.
Marstato

@marstato: Ich habe bereits zugestimmt, dass die Unterstützung in der Sprache eine gute Sache ist.
Ira Baxter

15

Wie Sie sagen, Design by Contract ist ein Merkmal in Eiffel, die lange eine dieser Programmiersprachen gewesen ist , die gut in der Gemeinde respektiert wird , aber die auf nie gefangen hat.

DbC ist in keiner der beliebtesten Sprachen verfügbar, da die Mainstream-Programmierer erst seit relativ kurzer Zeit akzeptieren, dass das Hinzufügen von Einschränkungen / Erwartungen zu ihrem Code eine "vernünftige" Sache für Programmierer ist. Heutzutage ist es für Programmierer üblich zu verstehen, wie wertvoll Unit-Tests sind, und dies führte dazu, dass Programmierer eher akzeptieren, Code einzugeben, um ihre Argumente zu validieren und Vorteile zu erkennen. Aber vor einem Jahrzehnt haben wahrscheinlich die meisten Programmierer gesagt: "Das ist nur zusätzliche Arbeit für Dinge, von denen Sie wissen, dass sie immer in Ordnung sind."

Ich denke, wenn Sie heute zum durchschnittlichen Entwickler gehen und über Nachbedingungen sprechen, nicken sie begeistert und sagen: "OK, das ist wie Unit-Testing." Und wenn Sie über Vorbedingungen sprechen, sagen sie "OK, das ist wie eine Parametervalidierung, was wir nicht immer tun, aber, weißt du, ich denke, es ist in Ordnung ..." Und wenn Sie dann über Invarianten sprechen Sie fingen an zu sagen: "Gee, wie viel Overhead ist das? Wie viele weitere Bugs werden wir fangen?" usw.

Ich denke, es ist noch ein langer Weg, bis DbC sehr weit verbreitet ist.


OTOH, die Mainstream-Programmierer waren es gewohnt, die Behauptungen eine ganze Weile zu schreiben. Das Fehlen eines brauchbaren Präprozessors in den modernsten Hauptsprachen machte diese nette Praxis ineffizient, aber es ist immer noch üblich für C und C ++. Es gibt ein Comeback mit den Microsoft Code Contracts (basierend auf AFAIK, einem Umschreiben des Bytecodes für die Release-Builds).
SK-logic

8

Meine Intuition sagt mir, dass der Mangel an Unterstützung das Ergebnis einer Art Ablehnung der Praxis sein muss, ...

Falsch.

Es ist eine Designpraxis . Es kann explizit im Code (Eiffel-Stil) oder implizit im Code (in den meisten Sprachen) oder in Komponententests enthalten sein. Die Designpraxis existiert und funktioniert gut. Die Sprachunterstützung ist überall auf der Karte. Es ist jedoch in vielen Sprachen im Unit-Test-Framework vorhanden.

Ich frage mich, ob jemand erklären kann, warum die meisten modernen Sprachen so wenig Unterstützung bieten? Ist DbC fehlerhaft oder zu teuer?

Es ist teuer. Und. Noch wichtiger ist, dass es einige Dinge gibt, die in einer bestimmten Sprache nicht bewiesen werden können. Die Schleifenbeendigung kann beispielsweise in einer Programmiersprache nicht bewiesen werden, sondern erfordert eine Beweisfähigkeit "höherer Ordnung". Einige Arten von Verträgen sind technisch unbeschreiblich.

Oder ist es aufgrund von Extreme Programming und anderen Methoden einfach veraltet?

Nein.

Wir verwenden meist Unit-Tests, um nachzuweisen, dass DbC erfüllt ist.

Für Python gibt es, wie Sie bereits bemerkt haben, mehrere Möglichkeiten für die DbC.

  1. Die Docstring- und die Docstring-Testergebnisse.

  2. Aussagen zur Validierung von Ein- und Ausgängen.

  3. Unit-Tests.

Des Weiteren.

Sie können gebildete Tools im Programmierstil verwenden, um ein Dokument zu schreiben, das Ihre DbC-Informationen enthält und das saubere Python plus-Komponententestskripte generiert. Der Ansatz der Lese- und Schreibprogrammierung ermöglicht es Ihnen, ein schönes Stück Literatur zu schreiben, das die Verträge und die vollständige Quelle enthält.


Sie können triviale Fälle von Schleifenbeendigung nachweisen, z. B. Iteration über eine feste endliche Sequenz. Es ist eine verallgemeinerte Schleife, von der nicht gezeigt werden kann, dass sie trivial endet (da sie nach Lösungen für „interessante“ mathematische Vermutungen suchen könnte). Das ist die ganze Essenz des Halteproblems.
Donal Fellows

+1. Ich denke, Sie sind der einzige, der den kritischsten Punkt behandelt hat - there are some things which cannot be proven. Die formale Verifizierung mag großartig sein, aber nicht alles ist verifizierbar! Diese Funktion schränkt also tatsächlich ein, was die Programmiersprache tatsächlich kann!
Dipan Mehta

@DonalFellows: Da der allgemeine Fall nicht bewiesen werden kann, ist es schwierig, eine Reihe von Funktionen zu integrieren, die (a) teuer und (b) als unvollständig bekannt sind. Mein Punkt in dieser Antwort ist, dass es einfacher ist, all diese Funktionen zu meiden und zu vermeiden, dass falsche Erwartungen an formale Korrektheitsnachweise im Allgemeinen gestellt werden, wenn es Einschränkungen gibt. Als Entwurfsübung (außerhalb der Sprache) können (und sollten) viele Beweistechniken verwendet werden.
S.Lott,

Es ist in einer Sprache wie C ++, in der die Vertragsüberprüfung im Release-Build kompiliert wird, überhaupt nicht teuer. Und die Verwendung von DBC führt in der Regel zu einer leichteren Veröffentlichung von Build-Code, da Sie weniger Laufzeitprüfungen durchführen müssen, um sicherzustellen, dass sich das Programm in einem legalen Zustand befindet. Ich habe die Anzahl der furchtbaren Codebasen verloren, die ich gesehen habe, bei denen zahlreiche Funktionen auf einen ungültigen Status prüfen und false zurückgeben, wenn es in einem ordnungsgemäß getesteten Release-Build NIEMALS in diesem Status sein sollte.
Kaitain

6

Einfach raten. Vielleicht ist ein Teil des Grundes, warum es nicht so beliebt ist, weil "Design by Contract" von Eiffel als Warenzeichen eingetragen ist.


3

Eine Hypothese ist, dass für ein ausreichend großes komplexes Programm, insbesondere für ein Programm mit sich bewegenden Zielen, die Masse der Verträge selbst so fehlerhaft und schwer zu debuggen sein kann, oder mehr als der Programmcode allein. Wie bei jedem Muster kann es durchaus zu einer Nutzung kommen, die über sinkende Renditen hinausgeht, sowie zu deutlichen Vorteilen, wenn sie gezielter eingesetzt werden.

Eine andere mögliche Schlussfolgerung ist, dass die Popularität von "verwalteten Sprachen" der aktuelle Beweis für die vertragliche Unterstützung dieser ausgewählten verwalteten Funktionen ist (Array-Grenzen nach Vertrag usw.).


> Die Masse der Verträge selbst kann so fehlerhaft und schwer zu debuggen sein, oder mehr als der Programmcode allein, den ich noch nie gesehen habe.
Kaitain

2

Der Grund dafür, dass die meisten gängigen Sprachen keine DbC-Funktionen in der Sprache haben, ist das Kosten-Nutzen-Verhältnis der Implementierung, das für den Sprachimplementierer zu hoch ist.

Eine Seite davon wurde bereits in den anderen Antworten betrachtet. Unit-Tests und andere Laufzeitmechanismen (oder sogar einige Kompilierungszeitmechanismen mit Template-Metaprogrammierung) können Ihnen bereits einen Großteil der DbC-Güte verleihen. Daher wird es, obwohl es einen Vorteil gibt, wahrscheinlich als recht bescheiden angesehen.

Die andere Seite ist der Preis, die nachträgliche Anpassung von DbC an eine vorhandene Sprache ist wahrscheinlich eine zu große Änderung und sehr komplex. Es ist schwierig, eine neue Syntax in einer Sprache einzuführen, ohne den alten Code zu beschädigen. Das Aktualisieren Ihrer vorhandenen Standardbibliothek zur Verwendung einer so weitreichenden Änderung wäre teuer. Daher können wir den Schluss ziehen, dass die Implementierung von DbC-Funktionen in einer vorhandenen Sprache hohe Kosten verursacht.

Ich würde auch bemerken, dass Konzepte, die so ziemlich Verträge für Vorlagen sind und daher etwas mit DbC zu tun haben, aus dem neuesten C ++ - Standard gestrichen wurden, da selbst nach jahrelanger Arbeit davon ausgegangen wurde, dass sie noch jahrelange Arbeit benötigten. Diese großen, umfassenden und umfassenden Änderungen an den Sprachen sind einfach zu schwer umzusetzen.


2

DbC würde umfassender genutzt, wenn die Verträge zum Zeitpunkt der Kompilierung überprüft werden könnten, sodass es nicht möglich wäre, ein Programm auszuführen, das gegen einen Vertrag verstößt.

Ohne Compiler-Unterstützung ist "DbC" nur ein anderer Name für "Invarianten / Annahmen überprüfen und bei Verletzung eine Ausnahme auslösen".


Stößt das nicht auf das Problem des Stillstands?
Ceasar Bautista

@ Caesar Kommt drauf an. Einige Annahmen können überprüft werden, andere nicht. Beispielsweise gibt es Typsysteme, mit denen vermieden werden kann, dass eine leere Liste als Argument übergeben oder zurückgegeben wird.
Ingo

Netter Punkt (+1), obwohl Bertrand Meyer in seinem Teil von "Masterminds of Programming" auch das System der zufälligen Klassenerstellung und Überprüfung auf Vertragsverletzung erwähnte. Es handelt sich also um einen gemischten Kompilierungs- / Laufzeitansatz, aber ich bezweifle, dass diese Technik in jeder Situation
funktioniert

Das stimmt in gewissem Maße, obwohl es eher ein katastrophaler Fehler als eine Ausnahme sein sollte (siehe unten). Der Hauptvorteil von DBC ist die Methodik, die tatsächlich zu besser gestalteten Programmen führt, und die Gewissheit, dass sich jede Methode bei der Eingabe in einem legalen Zustand befinden MUSS, was einen Großteil der internen Logik vereinfacht. Im Allgemeinen sollten Sie keine Ausnahmen auslösen, wenn ein Vertrag verletzt wird. Ausnahmen sollten verwendet werden, wenn sich das Programm LEGAL in Zustand ~ X befinden kann und der Client-Code dies verarbeiten muss. Ein Vertrag besagt, dass ~ X einfach illegal ist.
Kaitain,

1

Ich habe eine einfache Erklärung, die meisten Leute (einschließlich Programmierer) wollen keine zusätzliche Arbeit, es sei denn, sie sehen es als notwendig an. Avionik-Programmierung, bei der Sicherheit für sehr wichtig gehalten wird Ich habe die meisten Projekte ohne sie nicht gesehen.

Wenn Sie jedoch die Programmierung von Websites, Desktops oder Mobilgeräten in Betracht ziehen - Abstürze und unerwartetes Verhalten werden manchmal nicht als so schlimm angesehen, und Programmierer vermeiden nur zusätzliche Arbeit, wenn Fehler gemeldet und später behoben werden.

Dies ist wahrscheinlich der Grund, warum Ada außerhalb der Luftfahrtprogrammierungsbranche meiner Meinung nach nie aufgehört hat, weil es mehr Programmierarbeit erfordert, obwohl Ada eine großartige Sprache ist und wenn Sie ein zuverlässiges System erstellen möchten, ist es die beste Sprache für den Job (mit Ausnahme von SPARK, das proprietär ist) Sprache basierend auf Ada).

Design by Contract Libraries für C # wurden von Microsoft experimentell getestet und sind sehr nützlich für die Erstellung zuverlässiger Software. Sie haben jedoch noch nie eine Dynamik auf dem Markt aufgenommen, da Sie sie sonst als Teil der Kernsprache von C # gesehen hätten.

Behauptungen sind nicht dasselbe wie eine voll funktionsfähige Unterstützung für Vor- / Nachbedingungen und Invarianten. Obwohl es versuchen kann, sie zu emulieren, führt ein Language / Compiler mit entsprechender Unterstützung eine Analyse des abstrakten Syntaxbaums durch und prüft auf logische Fehler, die einfach nicht behauptet werden können.

Bearbeiten: Ich habe eine Suche durchgeführt und folgende Diskussion ist hilfreich: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala


-2

Meistens sind die Gründe wie folgt:

  1. Es ist nur in Sprachen verfügbar, die nicht populär sind
  2. Dies ist unnötig, da die gleichen Aufgaben in vorhandenen Programmiersprachen von jedem, der sie tatsächlich ausführen möchte, auf unterschiedliche Weise ausgeführt werden können
  3. Es ist schwer zu verstehen und anzuwenden - es erfordert spezielle Kenntnisse, um es richtig zu machen, so dass es nur wenige Leute gibt, die es tun
  4. Dazu ist viel Code erforderlich - Programmierer ziehen es vor, die Anzahl der von ihnen geschriebenen Zeichen auf ein Mindestmaß zu beschränken -, wenn es sich um einen langen Code handelt, muss etwas daran falsch sein
  5. Die Vorteile sind nicht da - es kann einfach nicht genug Bugs finden, um es sich zu lohnen

1
Ihre Antwort ist nicht gut argumentiert. Sie geben lediglich Ihre Meinung wieder, dass es keine Vorteile gibt, dass viel Code benötigt wird und dass dies unnötig ist, da es mit vorhandenen Sprachen durchgeführt werden kann (das OP hat sich speziell mit diesem Problem befasst!).
Andres F.

Ich denke nicht über Behauptungen usw. als Ersatz dafür nach. Das ist nicht die richtige Vorgehensweise in vorhandenen Sprachen (dh es wurde noch nicht angesprochen)
1.

@tp1, wenn Programmierer die Eingabe wirklich minimieren wollten, würden sie niemals in etwas so Ausführliches und Beredsames wie Java verfallen. Und ja, das Programmieren selbst erfordert "Fachwissen", "um es richtig zu machen". Diejenigen, die nicht im Besitz eines solchen Wissens sind, sollten einfach nicht kodieren dürfen.
SK-logic

@ Sk-logic: Nun, es scheint, dass die halbe Welt OO falsch macht, nur weil sie keine Weiterleitungsfunktionen von Elementfunktionen in die Elementfunktionen der Datenelemente schreiben möchten. Meiner Erfahrung nach ist das ein großes Problem. Dies wird direkt dadurch verursacht, dass die Anzahl der zu schreibenden Zeichen minimiert wird.
tp1

@tp1, wenn die Leute das Tippen wirklich minimieren wollten, würden sie niemals die OO berühren. OOP ist von Natur aus beredt, selbst in seinen besten Implementierungen wie Smalltalk. Ich würde nicht sagen, dass es eine schlechte Eigenschaft ist, Beredsamkeit hilft manchmal.
SK-logic am
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.