Warum sollte ich Codeverträge verwenden?

26

Vor kurzem bin ich auf das Framework von Microsoft für Codeverträge gestoßen.

Ich las ein bisschen Dokumentation und fragte mich ständig: "Warum sollte ich das jemals tun wollen, da es keine statische Analyse durchführt und oft nicht kann."

Jetzt habe ich schon eine Art defensiven Programmierstil, mit Ausnahmen wie dieser:

if(var == null) { throw new NullArgumentException(); }

Ich verwende auch NullObject-Muster viel und habe selten irgendwelche Probleme. Fügen Sie Unit-Tests hinzu, und schon sind Sie fertig.

Ich habe nie Behauptungen benutzt und sie nie verpasst. Ganz im Gegenteil. Ich hasse wirklich Code, der viele bedeutungslose Behauptungen enthält, was für mich nur Rauschen ist und mich von dem ablenkt, was ich wirklich sehen möchte. Codeverträge sind zumindest auf die Art von Microsoft ähnlich - und noch schlimmer. Sie fügen dem Code viel Rauschen und Komplexität hinzu. In 99% wird sowieso eine Ausnahme geworfen - es ist mir also egal, ob es sich um die Behauptung / den Vertrag oder das eigentliche Problem handelt. Es bleiben nur sehr wenige Fälle, in denen die Programmzustände wirklich verfälscht werden.

Was ist der Vorteil der Verwendung von Codeverträgen? Gibt es überhaupt welche? Wenn Sie Unit-Tests und Code bereits defensiv einsetzen, ist die Einführung von Verträgen meines Erachtens die Kosten einfach nicht wert und führt zu Störungen in Ihrem Code, die ein Betreuer beim Aktualisieren dieser Methode verfluchen wird, ähnlich wie ich es tue, wenn ich nicht sehe, was der Code tut aufgrund unbrauchbarer Behauptungen. Ich habe noch keinen guten Grund, diesen Preis zu zahlen.

Falke
quelle
1
Warum sagen Sie "es führt keine statische Analyse durch und kann diese oft nicht durchführen"? Ich dachte, das war einer der Punkte dieses Projekts (im Gegensatz zur Verwendung von Asserts oder defensivem Ausnahmewurf)?
Carson63000
@ Carson63000 Wenn Sie die Dokumentation sorgfältig lesen, werden Sie feststellen, dass der statische Checker nur sehr viele Warnungen ausgibt, die am wenigsten erforderlich sind (die Entwickler geben dies zu), und dass die meisten definierten Verträge nur eine Ausnahme auslösen und nichts anderes tun .
Falcon
@ Falcon: Seltsamerweise ist meine Erfahrung völlig anders. Die statische Analyse funktioniert nicht einwandfrei, aber ziemlich gut, und ich sehe selten unnötige Warnungen, selbst wenn ich mit der Warnstufe 4 (der höchsten) arbeite.
Arseni Mourzenko
Ich denke, ich muss es versuchen, um herauszufinden, wie es sich verhält.
Falcon

Antworten:

42

Sie könnten genauso gut gefragt haben, wann statisches Tippen besser ist als dynamisches Tippen. Eine seit Jahren wütende Debatte, deren Ende nicht abzusehen ist. Schauen Sie sich also Fragen wie Dynamisch vs Statisch getippte Sprachkurse an

Das Hauptargument wäre jedoch das Potenzial, Probleme beim Kompilieren zu entdecken und zu beheben, die andernfalls in die Produktion geraten wären.

Verträge gegen Wachen

Selbst mit Wachen und Ausnahmen haben Sie immer noch das Problem, dass das System in einigen Fällen seine beabsichtigte Aufgabe nicht ausführen kann. Möglicherweise eine Instanz, deren Ausfall sehr kritisch und kostspielig sein wird.

Verträge vs. Unit-Tests

Das übliche Argument in diesem Fall ist, dass Tests das Vorhandensein von Fehlern belegen, während Typen (Verträge) das Fehlen belegen. ZB Wenn Sie einen Typ verwenden, von dem Sie wissen, dass kein Pfad im Programm möglicherweise ungültige Eingaben liefern kann, können Sie bei einem Test nur feststellen, dass der abgedeckte Pfad die richtigen Eingaben liefert.

Verträge vs. Null Objektmuster

Das ist jetzt zumindest im selben Stadion. Sprachen wie Scala und Haskell hatten großen Erfolg mit diesem Ansatz, Nullreferenzen vollständig aus Programmen zu entfernen. (Auch wenn Scala formell Nullen zulässt, ist die Konvention, diese niemals zu verwenden.)

Wenn Sie dieses Muster bereits verwenden, um NREs zu beseitigen, haben Sie im Grunde genommen die größte Ursache für Laufzeitfehler beseitigt, die es im Grunde genommen gibt, wie Verträge dies zulassen.

Der Unterschied besteht möglicherweise darin, dass Verträge die Option haben, Ihren gesamten Code automatisch zu fordern, um null zu vermeiden, und Sie daher zu zwingen, dieses Muster an mehreren Stellen zu verwenden, um die Kompilierung zu bestehen.

Darüber hinaus bieten Verträge Ihnen die Flexibilität, auf Dinge zu zielen, die über null hinausgehen. Wenn Sie also in Ihren Bugs kein NRE mehr finden, sollten Sie Verträge verwenden, um das nächsthäufigste Problem, das Sie haben könnten, zu ersticken. Aus um eins? Index außerhalb des zulässigen Bereichs?

Aber...

Das alles gesagt. Ich bin damit einverstanden, dass die Verträge für syntaktisches Rauschen (und sogar für strukturelles Rauschen) den Code erheblich erweitern und die Auswirkung der Analyse auf Ihre Build-Zeit nicht zu unterschätzen ist. Wenn Sie also Verträge zu Ihrem System hinzufügen möchten, ist es wahrscheinlich ratsam, dies sehr sorgfältig zu tun und sich dabei darauf zu konzentrieren, welche Klasse von Fehlern Sie beheben möchten.

John Nilsson
quelle
6
Das ist eine großartige erste Antwort für Programmierer. Freut mich über deine Teilnahme an der Community.
13

Ich weiß nicht, woher die Behauptung kommt, dass "es keine statische Analyse durchführt und oft nicht kann". Der erste Teil der Behauptung ist eindeutig falsch. Der zweite hängt davon ab, was Sie mit "oft" meinen. Ich würde eher sagen, dass es oft statische Analysen durchführt und selten daran scheitert. In der normalen Geschäftsanwendung wird selten viel näher an nie .

Hier kommt also der erste Vorteil:

Vorteil 1: statische Analyse

Gewöhnliche Behauptungen und Argumentprüfungen haben einen Nachteil: Sie werden verschoben, bis der Code ausgeführt wird. Andererseits manifestieren sich Codeverträge auf einer viel früheren Ebene, entweder beim Codierschritt oder beim Kompilieren der Anwendung. Je früher Sie einen Fehler abfangen, desto günstiger ist es, ihn zu beheben.

Vorteil 2: Sozusagen immer aktuelle Dokumentation

Codeverträge bieten auch eine Art Dokumentation, die immer auf dem neuesten Stand ist. Wenn der XML-Kommentar der Methode SetProductPrice(int newPrice)angibt, dass newPricesie höher oder gleich Null sein sollte, können Sie hoffen, dass die Dokumentation auf dem neuesten Stand ist, Sie können jedoch auch feststellen, dass jemand die Methode so geändert hat, dass newPrice = 0ein ArgumentOutOfRangeException, aber niemals die entsprechende Dokumentation geändert wurde. Angesichts der Korrelation zwischen den Codeverträgen und dem Code selbst liegt kein Problem mit der nicht synchronen Dokumentation vor.

Die Art der Dokumentation, die durch Codeverträge bereitgestellt wird, ist auch in einer Weise wertvoll, dass XML-Kommentare die akzeptablen Werte häufig nicht gut erklären. Wie oft war ich frage mich , ob nulloder string.Emptyoder \r\nein autorisierter Wert für ein Verfahren und XML - Kommentare schweigen auf das!

Fazit: Ohne Codeverträge sind viele Codeteile wie folgt:

Ich akzeptiere einige Werte, aber keine anderen, aber Sie müssen die Dokumentation erraten oder lesen, falls vorhanden. Lesen Sie die Dokumentation eigentlich nicht, sie ist veraltet. Durchlaufen Sie einfach alle Werte und Sie werden diejenigen sehen, die mich dazu bringen, Ausnahmen zu werfen. Sie müssen auch den Wertebereich erraten, der möglicherweise zurückgegeben wird, denn selbst wenn ich Ihnen ein bisschen mehr darüber erzählen würde, könnte dies angesichts der Hunderte von Änderungen, die ich in den letzten Jahren vorgenommen habe, nicht zutreffen.

Mit Code-Verträgen wird es:

Das title-Argument kann eine Nicht-Null-Zeichenfolge mit einer Länge von 0..500 sein. Die folgende Ganzzahl ist ein positiver Wert, der nur dann Null sein kann, wenn die Zeichenfolge leer ist. Schließlich werde ich ein IDefinitionObjekt zurückgeben, niemals null.

Vorteil 3: Verträge von Schnittstellen

Ein dritter Vorteil ist, dass Codeverträge Schnittstellen ermöglichen. Nehmen wir an, Sie haben etwas wie:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

Wie würden Sie mit Asserts und Exceptions garantieren, dass CommitChangesnur aufgerufen werden kann, wenn PendingChangesnicht leer? Wie würden Sie garantieren, dass PendingChangesdas niemals ist null?

Vorteil 4: Erzwingen Sie die Ergebnisse einer Methode

Schließlich ist der vierte Vorteil, um Contract.Ensuredie Ergebnisse zu können. Was ist, wenn ich beim Schreiben einer Methode, die eine Ganzzahl zurückgibt, sicherstellen möchte, dass der Wert niemals schlechter oder gleich Null ist? Einschließlich fünf Jahre später nach vielen Veränderungen durch viele Entwickler? Sobald eine Methode mehrere Rückgabepunkte hat, wird sie Assertzu einem Alptraum für die Instandhaltung.


Betrachten Sie Codeverträge nicht nur als Mittel zur Richtigkeit Ihres Codes, sondern auch als strengere Methode zum Schreiben von Code. In ähnlicher Weise kann eine Person, die ausschließlich dynamische Sprachen verwendet, fragen, warum Sie Typen auf Sprachebene erzwingen sollten, während Sie bei Bedarf dasselbe in Behauptungen tun können. Sie können, aber statische Typisierung ist einfacher zu verwenden, weniger fehleranfällig im Vergleich zu einer Reihe von Behauptungen und selbstdokumentierend.

Der Unterschied zwischen dynamischer und statischer Typisierung kommt dem Unterschied zwischen gewöhnlicher Programmierung und vertraglicher Programmierung sehr nahe.

MainMa
quelle
3

Ich weiß, dass die Frage etwas spät ist, aber Code Contracts bietet noch einen weiteren Vorteil, der erwähnt werden muss

Verträge werden außerhalb der Methode geprüft

Wenn wir Schutzbedingungen innerhalb unserer Methode haben, ist dies der Ort, an dem die Überprüfung stattfindet und Fehler gemeldet werden. Möglicherweise müssen wir dann den Stack-Trace untersuchen, um herauszufinden, wo die eigentliche Fehlerquelle liegt.

Codeverträge befinden sich innerhalb der Methode, aber die Voraussetzungen werden außerhalb der Methode überprüft, wenn versucht wird, diese Methode aufzurufen. Die Verträge werden Teil der Methode und bestimmen, ob sie aufgerufen werden können oder nicht. Das bedeutet, dass wir einen Fehler erhalten, der viel näher an der eigentlichen Ursache des Problems liegt.

Wenn Sie eine vertraglich geschützte Methode haben, die an vielen Stellen aufgerufen wird, kann dies ein echter Vorteil sein.

Alex White
quelle
2

Wirklich zwei Dinge:

  1. Durch die Unterstützung von Tools kann VS Vertragsdaten von der Intelligenz abheben, sodass sie Teil der automatischen Vervollständigungshilfe sind.
  2. Wenn eine Unterklasse eine Methode überschreibt, verlieren Sie alle Ihre Schecks (die immer noch gültig sein sollten, wenn Sie dem LSP folgen) mit Verträgen, die automatisch ihren untergeordneten Klassen folgen.
Steinmetalle
quelle