Dieser Artikel wurde maschinell übersetzt.

Schneide

Codeverträge: Vererbung und das Liskovsche Prinzip

Dino Esposito

Dino EspositoWie Real-Life-Verträge Software-Verträge binden Sie an zusätzlichen Einschränkungen und Sie etwas an Zeit Kosten. Wenn ein Vertrag steht, sollten Sie sicherstellen, dass Sie nicht ihre Bedingungen zu brechen. Wenn es um die Software-Verträge — einschließlich Codeverträge in Microsoft.NET Framework — fast jeder Entwickler wird schließlich Zweifel über die rechnerische Kosten haben Verträge um Klassen manifestieren. Sind Verträge für Ihre Software, unabhängig von der Art des Build geeignet? Oder sind Verträge stattdessen meist ein Debuggen Beihilfen, die von Retail-Code entfernt werden sollte?

Eiffel, die erste Sprache einzuführen Software-Verträge, hat native Sprachschlüsselwörter Vorbedingungen, Postconditions und invarianten definieren. In Eiffel daher sind Verträge Teil der Sprache. Wenn in den Quellcode einer Klasse verwendet wird, werden Verträge ein integraler Teil des Codes.

In.NET, obwohl Verträge in den Rahmen sind und nicht unterstützten Sprachen gehören. Dies bedeutet, dass die Laufzeitüberprüfung kann aktiviert oder deaktiviert werden. Vor allem bei.NET, die Sie berechtigt sind, über Verträge auf einer pro-Build-Konfiguration-Basis zu entscheiden. In Java sind Dinge fast identisch. Sie verwenden einen externen Rahmen und entweder die Quellen zu kompilierenden Vertrag Code hinzufügen oder Fragen Sie Tools rund um das Framework den Bytecode entsprechend ändern.

In diesem Artikel werde ich einige Szenarien diskutieren, wo Codeverträge in fahren Sie in Richtung eine höhere Qualität der Gesamtentwurf Software besonders hilfreich erweisen.

Was sind Codeverträge

Eine immergrüne beste Praxis der Software-Entwickler ist Methoden schreiben, die alle Eingabeparameter sorgfältig zu überprüfen, die sie erhalten. Wenn der input-Parameter nicht die Erwartungen der Methode übereinstimmt, wird eine Ausnahme ausgelöst. Dies ist bekannt als die Wenn-dann-Throw Muster. Voraussetzung Verträge sieht dieser gleichen Code schöner und kompakter. Interessanter, liest es auch besser, weil eine Voraussetzung können Sie nur erforderlich sind, anstelle von Baumusterprüfungen, was wünschenswert ist nicht klar. Auf den ersten Blick sehen Software-Verträge, einfach wie ein schöner schreiben-Ansatz für Ausnahmen in Methoden der Klasse zu verhindern. Nun, gibt es es viel mehr als nur das.

Die einfache Tatsache, die Sie von Verträgen für jede Methode denken gibt an, dass Sie jetzt mehr über die Rolle dieser Methoden denken. Schließlich bekommt Design knappere und knappere. Und Verträge repräsentieren auch eine wertvolle Form der Dokumentation, insbesondere für die Umgestaltung Zwecke.

Code-Verträge sind nicht jedoch auf Vorbedingungen, beschränkt, obwohl Voraussetzungen das einfachste Teil des Software-Verträge zum Abholen sind. Die Kombination von Vorbedingungen, Postconditions und invarianten — und die umfassende Anwendung von ihnen in Ihrem Code — gibt Ihnen einen entscheidenden Vorteil und wirklich führt zu höherer Qualität Code.

Assertionen Vs. Code-Verträge Vs. Prüfungen

Code-Verträge sind nicht ganz wie Assertionen und andere Debug-Instrumente. Während Verträge Sie Fehler aufzuspüren helfen können, ersetzen sie keinen guten Debugger oder eine gut gemachte Gruppe von Komponententests. Wie Assertionen zeigen Codeverträge eine Bedingung, die während der Ausführung eines Programms zu einem bestimmten Zeitpunkt überprüft werden muss.

Eine Behauptung, die fehlschlägt ist ein Symptom, dass etwas falsch, irgendwo ist. Eine Assertion kann nicht Ihnen jedoch sagen, warum es nicht und wo das Problem entstanden ist. Ein Code-Vertrag, der fehlschlägt, auf der anderen Seite erzählt Sie viel mehr. Es teilt Informationen über die Art des Fehlers. Also, erfahren Sie z.B., ob die Ausnahme ausgelöst wurde, weil eine bestimmte Methode einen unzulässige Wert empfangen, konnte nicht in die Berechnung des erwarteten Rückgabewerts oder einen ungültigen Status enthält. Während eine Assertion nur über ein erkannten schlechten Symptom erzählt, können einen Code-Vertrag wertvolle Informationen zeigen, wie die Methode eingesetzt werden sollte. Diese Informationen kann letztlich können Sie nachvollziehen, was zu behoben werden, um aufzuhören verletzt eine bestimmte Assertion hat.

Wie betreffen Software-Verträge Komponententests? Offensichtlich nicht einer der anderen ausschließen und die zwei Eigenschaften sind Art von orthogonal. Eine Testumgebung ist ein externes Programm, das funktioniert durch Anwenden einer festen Eingabe für ausgewählte Klassen und Methoden zu sehen, wie sie sich Verhalten. Verträge sind eine Möglichkeit für die Klassen zu schreien heraus, wenn etwas nicht stimmt. Um zu testen, Verträge, müssen jedoch, Sie den Code ausgeführt werden.

Unit-Tests sind ein großes Werkzeug, Regression nach einer tiefen Umgestaltung Prozess zu fangen. Verträge sind vielleicht mehr informativ als Tests, das erwartete Verhalten der Methoden zu dokumentieren. Designwert von Tests zu erhalten, müssen Sie Test-driven Development (TDD) üben. Verträge sind wahrscheinlich ein einfacher Tool als TDD, Dokument und Design-Methoden.

Verträge Code zusätzlichen Informationen hinzufügen und lassen Sie es an Ihnen zu entscheiden, ob es diese Informationen an den bereitgestellten Binärdateien machen soll. Unit-Test beinhaltet ein externes Projekt, das schätzen kann, wie der Code macht. Ob Sie Vertragsinformationen oder nicht, kompilieren müssen im Voraus klare Vertragsinformationen hilft ungemein als Hilfe-Dokumentation und Design.

Code-Verträge und Input-Daten

Verträge finden Sie unter Bedingungen, die immer in der normalen Ausführungsablauf eines Programms wahr halten. Dies scheint darauf hinzudeuten, dass ist der ideale Ort, wo Sie Verträge verwenden möchten, in interne Bibliotheken, die vom Entwickler nur vorbehaltlich Eingabe streng gesteuert werden. Klassen, die direkt auf Benutzereingaben ausgesetzt sind nicht unbedingt ein guter Ort für Verträge. Wenn Sie die Voraussetzungen für ungefilterten input-Daten festlegen, kann der Vertrag fehl und eine Ausnahme. Aber ist dies wirklich, was Sie wollen? Die meiste Zeit, möchten Sie anmutig beeinträchtigt werden oder eine höfliche Nachricht an den Benutzer zurückzugeben. Sie wollen keine Ausnahme, und Sie wollen nicht zu werfen und fangen dann eine Ausnahme nur, ordnungsgemäß wiederherzustellen.

In.NET, Code-Verträge gehören Bibliotheken und kann eine gute Ergänzung zu (und in einigen Fällen, ein Ersatz für) von Datenanmerkungen werden. Datenanmerkungen, eine großartige Arbeit in Bezug auf die Benutzeroberfläche, weil in Silverlight und ASP.NET haben Sie Komponenten, die verstehen, diese Anmerkungen und passen Sie den Code oder die HTML-Ausgabe. Auf der Ebene der Domäne obwohl Sie oft benötigen mehr als nur Attribute, und Code-Verträge sind ein idealer Ersatz. Ich bin nicht unbedingt, dass Sie nicht die gleichen Fähigkeiten mit Attributen, die Sie mit Code-Verträge können. Ich finde, jedoch sind, die an Lesbarkeit und Expressivität, die Ergebnisse besser mit Code-Verträge als Attribute. (By the way, ist dies genau warum Codeverträge-Team bevorzugt einfachem Code über Attribute.)

Geerbte Verträge

Software-Verträge sind vererbbar in nahezu jeder Plattform, die ihnen, unterstützt und die.NET Framework ist keine Ausnahme. Wenn Sie aus einer vorhandenen eine neue Klasse ableiten, greift die abgeleitete Klasse das Verhalten, Kontext und Verträge des übergeordneten Elements. Das scheint den natürlichen Verlauf der Dinge zu sein. Vererbung von Verträgen darstellen keines Problem für Invarianten und Postconditions. Es ist zwar ein bisschen für Vorbedingungen, problematisch. Lassen Sie uns invarianten anzugehen und betrachten Sie den Code in Abbildung 1.

Abbildung 1 Erben invarianten

public class Rectangle
{
  public virtual Int32 Width { get; set; }
  public virtual Int32 Height { get; set; }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width > 0);
    Contract.Invariant(Height > 0);
  }
}
public class Square : Rectangle
{
  public Square()
  {
  }

  public Square(Int32 size)
  {
    Width = size;
    Height = size;
  }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(Width == Height);
  }
  ...
}

Die Basisklasse Rechteck hat zwei invarianten: Breite und Höhe sind größer als 0 (null). Die abgeleitete Klasse Square fügt eine weitere unveränderliche Bedingung: Breite und Höhe müssen übereinstimmen. Sogar aus logischer Sicht macht das Sinn. Ein Quadrat ist wie ein Rechteck, außer dass es eine zusätzliche Einschränkung aufweist: Breite und Höhe müssen immer gleich sein.

Für Postconditions arbeiten Dinge meist auf die gleiche Weise. Eine abgeleitete Klasse, die überschreibt eine Methode und fügt weitere Postconditions nur erweitert die Fähigkeiten der Basisklasse und wirkt wie ein Sonderfall der übergeordneten Klasse, die alle tut, das übergeordnete Element ist, und vieles mehr.

Was über Vorbedingungen, dann? Das ist genau das Warum zusammenfassend Verträge über einer Klassenhierarchie eine zarten Operation ist. Eine Klassenmethode ist logisch gesehen, eine mathematische Funktion identisch. Beide erhalten einige Eingabewerte und produzieren einige Ausgabe. In der Mathematik ist der Wertebereich produziert durch eine Funktion als die Zielmenge bekannt; die Domäne ist die Palette der möglichen Eingabewerte. Indem Methode einer abgeleiteten Klasse Invarianten und Postconditions hinzufügen, vergrößern Sie nur die Methode Zielmenge. Aber durch das Hinzufügen von Vorbedingungen, beschränken Sie die Methode Domäne. Ist das etwas, was Sie wirklich besorgt sein sollte? Lesen Sie weiter.

Liskov-Prinzip

SOLID ist ein beliebtes Akronym, die sich aus den Anfangsbuchstaben der fünf wichtigsten Grundsätze der Software-Design, einschließlich der einzigen Verantwortung, Open/Closed, Schnittstelle Trennung und Abhängigkeitsumkehr. L in SOLID steht für das Liskovsche Substitutionsprinzip. Finden Sie viel mehr über das Liskov-Prinzip bei bit.ly/lKXCxF.

Kurz gesagt, besagt das Liskov-Prinzip, dass immer sollte es sicher, eine Unterklasse an jedem Ort zu verwenden, wenn die übergeordnete Klasse erwartet wird. So emphatische, wie es klingen mag, das ist nicht etwas, das wir out of the Box mit plain Objektorientierung zu erhalten. Kein Compiler einer Objekt-orientierten Sprache kann die Magie zu gewährleisten, die das Prinzip immer enthält.

Es ist ein präzise Entwickler Verantwortung sicherzustellen, dass es ist sicher, alle abgeleiteten Klassen an Orten verwenden, wenn die übergeordnete Klasse erwartet wird. Beachten Sie, sagte ich "sicher." Plain Objektorientierung ermöglicht es allen abgeleiteten Klassen an Orten verwenden, wenn die übergeordnete Klasse erwartet wird. "Möglich" ist nicht das gleiche als "sicher". Um das Liskov-Prinzip zu erfüllen, müssen Sie eine einfache Regel einhalten: die Domäne einer Methode kann nicht in einer Unterklasse verkleinert werden.

Code-Verträge und das Liskov-Prinzip

Neben der formalen und abstrakte Definition Liskov-Prinzip hat viel zu tun mit Software-Verträge und kann an eine bestimmte Technologie wie leicht neu formuliert werden.NET-Code-Verträge. Der entscheidende Punkt ist, dass die Voraussetzungen von eine abgeleitete Klasse nicht nur hinzugefügt werden kann. Dabei wird es den möglichen Werte abgenommen, für eine Methode, die common language Runtime Fehler möglicherweise erstellen einschränken.

Es ist wichtig zu beachten, dass Verstöße gegen das Prinzip unbedingt eine Laufzeitausnahme oder Fehlverhalten führen nicht. Jedoch ist es ein Zeichen, dass eine mögliche Gegenbeispiel Code bricht. Mit anderen Worten, können Auswirkungen der Verletzung welligkeit über die gesamte Codebasis und schändlichen Symptome in scheinbar unabhängige Bereiche anzeigen. Es macht die gesamte Codebasis härter zu pflegen und entwickeln – eine Todsünde in diesen Tagen. Stellen Sie sich vor, Sie haben den Code in Abbildung 2.

Abbildung 2 zur Veranschaulichung der Liskov-Prinzip

public class Rectangle
{
  public Int32 Width { get; private set; }
  public Int32 Height { get; private set; }

  public virtual void SetSize(Int32 width, Int32 height)
  {
    Width = width;
    Height = height;
  }
}
public class Square : Rectangle
{
  public override void SetSize(Int32 width, Int32 height)
  {
    Contract.Requires<ArgumentException>(width == height);
    base.SetSize(width, width);
  }
}

Das Quadrat Klasse erbt von Rechteck und fügt nur eine Voraussetzung. An diesem Punkt schlägt fehl, der folgende Code (die ein möglich Gegenbeispiel darstellt):

private static void Transform(Rectangle rect)
  {
    // Height becomes twice the width
    rect.SetSize(rect.Width, 2*rect.Width);
  }

Die Methode Transformation wurde ursprünglich zur Bewältigung von Instanzen der Rectangle-Klasse geschrieben, und es macht seinen Job sehr gut. Genommen Sie an, dass eines Tages Sie das System erweitern und starten Sie Instanzen des Platzes an den gleichen (unberührte) Code übergeben, wie hier gezeigt:

var square = new Square();
square.SetSize(20, 20);
Transform(square);

Abhängig von der Beziehung zwischen Quadrat und Rechteck kann die Transform-Methode fehlschlagen ohne eine offensichtliche Erklärung.

Schlimmer noch, Sie können leicht beschmutzen wie, das Problem zu beheben, aber wegen der Hierarchie von Klassen, kann nicht es etwas, das Sie leicht nehmen möchten. So beenden Sie bis zur Festsetzung des Fehlers mit umgehen das Problem, wie hier gezeigt:

private static void Transform(Rectangle rect)
{
  // Height becomes twice the width
  if (rect is Square)
  {
    // ...
return;
  }
  rect.SetSize(rect.Width, 2*rect.Width);
}

Aber unabhängig davon Ihre Bemühung, der berüchtigte Ball Schlamm hat gerade erst begonnen größer wachsen. Die nette Sache über.NET und c#-Compiler ist, dass wenn Sie Codeverträge Vorbedingungen Ausdrücken verwenden, können Sie den eine Warnung von der Compiler so erhalten, wenn Sie das Prinzip Liskov verletzt sind (siehe Abbildung 3).

The Warning You Get When You’re Violating the Liskov Principle

Abbildung 3 die Warnung erhalten Sie, wenn Sie das Liskov-Prinzip verletzt sind

Der Grundsatz der am wenigsten verstanden und am wenigsten angewendet solide

Gelehrt hat ein.NET-Design-Klasse für ein paar Jahren, ich glaube, ich kann sicher sagen, dass der festen Prinzipien, das Liskov-Prinzip bei weitem die wenigsten verstanden und angewendet ist. Sehr oft kann ein sonderbares Verhalten in einem Softwaresystem erkannt eine Verletzung des Prinzips Liskov aufgespürt werden. Erfreulicherweise können Codeverträge deutlich in diesem Bereich, wenn nur Sie Compilerwarnungen sorgfältige ansehen.

Dino Esposito ist der Verfasser von „Programming Microsoft ASP.NET 4“ (Microsoft Press, 2011) und Mitverfasser von „Microsoft .NET: Architecting Applications for the Enterprise“ (Microsoft Press 2008). Mit Sitz in Italien, ist Esposito ein gefragter Referent bei Branchenveranstaltungen weltweit. Sie können folgen ihm auf Twitter bei twitter.com/despos.

Dank der folgenden technischen Experten für die Überprüfung dieses Artikels: Manuel Fahndrich