2010-05-04 12 views
10

Ich implementiere mein eigenes ArrayList für Schulzwecke, aber um die Dinge etwas aufzupeppen, versuche ich C# 4.0 Code Contracts zu verwenden. Alles war in Ordnung, bis ich Contracts zu den Konstruktoren hinzufügen musste. Soll ich Contract.Ensures() im leeren Parameterkonstruktor hinzufügen?Design von Verträgen und Konstruktoren

public ArrayList(int capacity) { 
     Contract.Requires(capacity > 0); 
     Contract.Ensures(Size == capacity); 

     _array = new T[capacity]; 
    } 

    public ArrayList() : this(32) { 
     Contract.Ensures(Size == 32); 
    } 

Ich würde sagen ja, jede Methode sollte einen gut definierten Vertrag haben. Auf der anderen Seite, warum, wenn es nur Arbeit an den "Haupt" Konstruktor delegiert? Logischerweise würde ich nicht brauchen.

Der einzige Punkt, den ich sehe, wo es nützlich wäre, den Vertrag in beiden Konstruktoren explizit zu definieren, ist, wenn wir in Zukunft Intelisense Unterstützung für Verträge haben. Wäre dies der Fall, dann wäre es sinnvoll, genau anzugeben, welche Verträge jede Methode hat, wie sie in Intelisense erscheinen würde.

Gibt es auch Bücher, die ein wenig tiefer in die Prinzipien und die Verwendung von Design by Contracts einsteigen? Eine Sache ist das Wissen über die Syntax, wie man Contracts in einer Sprache benutzt (C#, in diesem Fall), andere wissen, wie und wann man sie benutzt. Ich habe mehrere Tutorials und Jon Skeets C# in Depth Artikel darüber gelesen, aber ich würde gerne ein bisschen tiefer gehen, wenn möglich.

Dank

+0

bezogen: http://stackoverflow.com/questions/2539497/code-contracts-do-we-have-to-specify-contract-requires-statements-redundant/2626997 – porges

+0

Sie könnten den "Vertrag. Benötigt (Kapazität> 0); " Wenn Sie den c'tor nehmen, nehmen Sie einen UI, der einem int gegenübersteht. Ich versuche Contracts als letzten Ausweg zu verwenden, da die Sprache Ihre Fähigkeit einschränkt, den nächsten Entwickler wissen zu lassen, was Sie bei der Erstellung des Codes gedacht haben. Wenn Sie sich entscheiden, den Vertrag zu behalten, würde ich schreiben "Contract.Requires (Kapazität> = 0);" Denn man sollte immer in der Lage sein, eine leere Datenstruktur zu konstruieren und später Objekte hinzuzufügen. –

+0

"... in Zukunft haben wir die Unterstützung von Intelisense für Verträge." Die Zukunft ist jetzt! http://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970 –

Antwort

5

Ich stimme überhaupt nicht mit Thomas Antwort. Solange Sie in der Implementierung von ArrayList() Entscheidungen treffen, sollten Sie einen Vertrag dafür haben, der diese Auswahl dokumentiert.

Hier haben Sie die Wahl den Hauptkonstruktor mit Argument 32 aufzurufen. Es gibt viele andere Dinge, die Sie hätten tun können (nicht nur in Bezug auf die Wahl der Standardgröße). Einen Vertrag an ArrayList() zu geben, der mit dem von ArrayList(int) fast identisch ist, dokumentiert, dass Sie sich entschieden haben, die meisten dummen Dinge nicht zu tun, die Sie hätten tun können, anstatt sie direkt anzurufen.

Die Antwort "es ruft den Hauptkonstruktor auf, so dass der Vertrag des Hauptkonstrukteurs die Aufgabe erledigt" ignoriert völlig die Tatsache, dass der Vertrag da ist, um Sie davor zu bewahren, sich die Implementierung anzusehen. Für eine Überprüfungsstrategie, die auf Laufzeit-Assertion-Prüfung basiert, besteht der Nachteil des Schreibens von Verträgen selbst für solche kurzen Konstruktoren/Methoden, die fast direkt einen anderen Konstruktor/eine andere Methode aufrufen, darin, dass Sie die Dinge zweimal überprüfen. Ja, es scheint redundant zu sein, aber Laufzeit-Assertion-Checking ist nur eine Überprüfungsstrategie und die DbC-Prinzipien sind davon unabhängig. Das Prinzip lautet: Wenn es aufgerufen werden kann, braucht es einen Vertrag, um zu dokumentieren, was es tut.

+1

Ja; und wenn Sie den statischen Checker verwenden, wird es Ihnen das sagen :) – porges

0

Umh, ich verstehe nicht ganz, warum Sie die ‚Stellt sicher, dass‘ auch in der c'tor Standard setzen. Weil es den Hauptcontainer nennt, der bereits den vollständigen Vertrag implementiert, tut dies auch der Standardcontainer - per definitionem. Das ist also eine logische Redundanz und daher ein großes "Nicht". Vielleicht könnte es pragmatische Konsequenzen haben, wie Sie sagen - nicht wissen-Code Verträge, die gut ...

In Bezug auf Literatur - die besten Quellen sind:

HTH! Thomas

+0

Angenommen, eines Tages unterstützt Intelisense Code Contracts vollständig (oder unter der Annahme, dass wir uns ab heute die XML-Vertragsdokumentation ansehen), die den Vertrag des No-Args-Konstruktors explizit angeben würde Vorteil, dass der Kunde den Vertrag kennt. Wenn du es leer lässt, kannst du nicht. Und deshalb habe ich diesen Beitrag erstellt. –

+1

Ich verstehe die Gründe, und vielleicht wird dies eines Tages wahr werden ... Aber etw. weil es möglicherweise Vorteile in der Zukunft geben könnte, stellte sich als eines der schlimmsten (und teuersten) Dinge in der Softwareentwicklung heraus. Mach immer die einfachste Sache, die den Job macht! –

+0

Ja, Sie haben einen Punkt. Was ist, wenn Intelisense diese Funktion ab heute unterstützt? Wäre es dann sinnvoll, diesen Contract im Konstruktor no args abzulegen? –

1

Client-Code (mit Code Contracts), die ArrayList verwendet wird nicht wissen, dass die leere Konstruktor Ensure s, dass Size == 32 wenn Sie nicht ausdrücklich so sagen, ein Ensure verwenden.

So (zum Beispiel):

var x = new ArrayList(); 
Contract.Assert(x.Size == 32) 

werden Sie die Warnung "assert nicht bewiesen".

Sie müssen alle Verträge explizit angeben; der Code-Verträge rewriter/static checker „look through“ wird kein Verfahren keine Auswirkungen sehen — my answer to the related question "Do we have to specify Contract.Requires(…) statements redundantly in delegating methods?"

0

Entwurf durch Vertrag sehen aus den mathematischen Wurzeln der funktionalen Programmierung kommt: Preconditions und Postconditions.

Sie brauchen nicht wirklich ein Buch über sie, es ist höchstens ein Kapitel eines Grads Informatik (die meisten werden das Konzept lehren). Die Grundvoraussetzung ist, dass Sie die Vorbedingungen schreiben, die die Funktion erwartet, und die Ausgabe, die sie mit den richtigen Parametern erzeugt. Es wird nicht erwartet, dass die Funktion mit falschen Anfangsparametern arbeitet. Das Gleiche gilt für einen Algorithmus: Er ist unfehlbar, dh er liefert garantiert das erwartete Ergebnis.

Das ist, wie ich es in dem Grad gelehrt habe ich zur Zeit studiert, kann es allerdings bessere Definitionen sein um. Der Wikipedia-Artikel über Design by contract wird mit einem OO-Schrägstrich geschrieben, aber Pre/Post-Bedingungen sind sprachunabhängig.

+0

Ja, mein Punkt ist, dass auf Papier OO Design nicht so viel zu lernen hat. Sie lernen etwas über Klassen, Vererbung und Einkapselung und es scheint, als ob es nicht viel mehr zu lernen ist. Aber in der Praxis gibt es Tonnen von Büchern darüber, wie man gute OO-Programme macht. Manchmal reicht es nicht, die Syntax und "Theorie" zu kennen. –

+0

Das sind wahrscheinlich die Wrox und Apress Bücher, die über Praktikabilität sind :) Viel mehr Tiefe wird in einem (guten) Grad gelehrt –

+0

Ich habe keinen Abschluss, also was mache ich? –

1

Ich empfehle Object Oriented Software Construction, 2nd Edition, oder vielleicht Touch of Class, beide von Bertrand Meyer. Alternativ können Sie den Artikel Applying "Design by Contract" von 1992 vom selben Autor lesen.

Fassen wir zusammen:

  • Die Klasseninvariante nach dem Konstruktor halten muss (jeder von ihnen) abgeschlossen ist, und vor und nach jeder öffentlichen Methode der Klasse ausgeführt wird.
  • Methodenvoraussetzungen und Nachbedingungen sind zusätzliche Bedingungen, die beim Eingeben und Beenden jeder öffentlichen Methode zusammen mit der Invariante gelten müssen.

Also in Ihrem Fall Fokus in der Invariante. Erzeuge ein korrektes Objekt (eines, das die Klasseninvariante erfüllt), unabhängig davon, welcher Konstruktor aufgerufen wird.

In dieser related answer diskutierte ich ähnliche Themen, darunter ein Beispiel.