Dieser Artikel wurde maschinell übersetzt.

Innovation

Geben Sie Ihren Klassen einen Softwarevertrag

Dino Esposito

image: Dino EspositoEine alte aber gute Praxis der Softwareentwicklung empfiehlt, dass Sie am oberen Rand jeder Methode platzieren – bevor alle wichtigen Verhaltensweisen stattfindet – ein Hindernis von bedingten Anweisungen. Jede bedingte Anweisung prüft eine andere Bedingung, die Eingabewerte überprüfen müssen. Wenn die Bedingung überprüft nicht ist, löst der Code eine Ausnahme aus. Dieses Muster wird oft als If-Then-Throw bezeichnet.

Jedoch wird der If-Then-Throw, wir effiziente und ordnungsgemäße Code schreiben müssen? Ist es in allen Fällen ausreichend?

Der Gedanke, dass es möglicherweise nicht werden in allen Fällen ausreichend ist, nicht eine neue. Design by Contract (DbC) ist ein Verfahren eingeführt werden mehrere Jahren von Bertrand Meyer basierend auf der Idee, dass jede Softwarekomponente ein Vertrags hat in der formal beschrieben was es erwartet wird und wie Sie arbeitet. Das If-Then-Throw-Muster deckt fast den ersten Teil des Vertrages; den zweiten Teil fehlt vollständig. DbC ist nicht von allen gängigen Programmiersprachen unterstützt. Allerdings Frameworks vorhanden sein, damit Sie Varianten von DbC in häufig verwendeten Sprachen wie Java, Perl, Ruby, JavaScript und natürlich Microsoft Geschmack.NET Framework-Sprachen. In.NET, führen Sie DbC, über den Code Verträge Bibliothek hinzugefügt, um die.NET Framework 4, befindet sich in der Mscorlib-Assembly. Beachten Sie, dass die Bibliothek verfügbar ist auf 4 Silverlight-Anwendungen jedoch nicht auf Windows-Telefonanwendungen.

Ich glaube, dass nahezu jeder Entwickler grundsätzlich zustimmen sicherlich, dass ein Contract-First-Ansatz zur Entwicklung eine gute Sache ist. Aber ich denke nicht, so dass viele Code Verträge in aktiv verwendet werden.NET-4-Anwendungen, nun, da Microsoft Software-Verträge verfügbar und in Visual Studio integrierten vorgenommen hat. Dieser Artikel konzentriert sich auf die Vorteile der Contract-First-Ansatz für Code Wartbarkeit und Einfachheit der Entwicklung. Hoffentlich können Argumente in diesem Artikel Sie um für Ihr nächstes Projekt Code Verträge an Ihren Chef zu verkaufen. In Zukunft werden Ausgaben dieser Rubrik ich Aspekte wie z. B. Konfiguration, Common Language Runtime-Tools und Features für die Programmierung wie z. B. Vererbung aufgliedern.

Überlegungen zu einer einfachen Rechner-Klasse

Code-Verträge sind ein Status von Mind. Sie sollten nicht platziert beiseite, bis Sie eine große Anwendung entwerfen, die super-Architektur und Beschäftigung viele zukunftsweisende Technologien erfordert aufgerufen werden. Bedenken Sie, dass – wenn schlecht verwaltet – sogar bei extrem leistungsfähige Technologie kann Probleme verursachen. Code Verträge eignen sich für nahezu jede Art von Anwendung, solange Sie einen guten Blick auf diese haben. Wir beginnen mit einer einfachen Klasse – eine klassische Calculator-Klasse, wie diese:

public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        return x + y;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        return x / y;
    }
}

Sie werden wahrscheinlich zustimmen, dass hier der Code nicht realistisch,, ist wie es mangelt es mindestens eine wichtige Information: eine Überprüfung, ob Sie versuchen, Division durch 0 (null). Wie wir es eine bessere Version schreiben, auch angenommen, dass wir ein weiteres Problem für den Umgang mit haben: der Rechner unterstützt keine negativen Werte. Abbildung 1 hat eine aktualisierte Version des Codes, der ein paar If-Then-Throw-Anweisungen hinzugefügt.

Abbildung 1 die Calculator-Klasse, die implementiert des If-Then-Throw-Musters

public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        // Check input values
        if (x <0 || y <0)
            throw new ArgumentException();


        // Perform the operation
        return x + y;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        // Check input values
        if (x < 0 || y < 0)
            throw new ArgumentException();
        if (y == 0)
            throw new ArgumentException();

        // Perform the operation  
        return x / y;
    }
}

Bisher können wir erwähnen, dass unsere Klasse entweder startet die Verarbeitung der eingegebenen Daten oder, im Fall von ungültige Eingabe nur löst vor nichts zu tun. Was ist die von der Klasse generierten Ergebnisse? Welche Fakten wissen wir über sie? Betrachten die Spezifikationen, sollten wir erwarten, dass beide Methoden einen Wert kleiner Null zurückgeben. Wie können wir erzwingen, und schlägt fehl, wenn es nicht passieren? Wir benötigen eine dritte Version des Codes, wie im Abbildung 2.

Abbildung 2 die Calculator-Klasse, die Überprüfung der Vorbedingungen und Postconditions

public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        // Check input values
        if (x <0 || y <0)
            throw new ArgumentException();

        // Perform the operation
        Int32 result = x + y;

        // Check output
        if (result <0)
            throw new ArgumentException();

        return result;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        // Check input values
        if (x < 0 || y < 0)
            throw new ArgumentException();
        if (y == 0)
            throw new ArgumentException();

        // Perform the operation
        Int32 result = x / y;

        // Check output
        if (result < 0)
            throw new ArgumentException();

        return result;
    }
}

Beide Methoden sind in drei Phasen formuliert: Überprüfen der Eingabewerte, die Leistung und Überprüfen der Ausgabe. Kontrolle der ein- und Ausgabe dienen zwei unterschiedlichen Zwecken. Eingaben überprüft die Flag-Fehlern im Code des Aufrufers. Ausgabe-Prüfungen suchen Sie nach Fehlern in Ihrem eigenen Code. Benötigen Sie wirklich Kontrollen bei der Ausgabe? Ich muss zugeben, dass es sich bei Check-Bedingungen über Assertionen in einigen Komponententests überprüft werden können. In diesem Fall müssen Sie nicht unbedingt diese Kontrollen im Laufzeitcode begraben. Allerdings müssen Prüfungen im Code und macht die Klasse selbstbeschreibende macht deutlich, was er kann, und kann nicht ausgeführt werden – ähnlich wie eine Neuzulassung Servicebedingungen.

Wenn Sie den Quellcode der vergleichen Abbildung 2 mit der einfachen Klasse, mit dem wir begonnen, sehen Sie, dass die Quelle von ein paar Zeilen vergrößert wurde – und dies ist eine einfache Klasse mit einige Anforderungen erfüllen. Gehen wir noch einen Schritt weiter.

In Abbildung 2, die drei Schritte, die wir festgestellt (Kontrollkästchen Eingabe, Betrieb und Kontrollkästchen Ausgabe) nacheinander ausführen. Was geschieht, wenn die Leistung des Vorgangs zusätzliche Beendigungspunkte komplex genug ist? Was geschieht, wenn einige dieser Punkte beenden finden Sie unter Fehlersituationen, in denen andere Ergebnisse erwartet werden? Dinge können sehr kompliziert werden. Um den Punkt zu verdeutlichen, jedoch es reicht, dass wir eine der Methoden, ein Kontextmenü Exit hinzufügen wie im Abbildung 3.

Abbildung 3 eine Verknüpfung Exit dupliziert den Code für Postconditions

public Int32 Sum(Int32 x, Int32 y)
{
    // Check input values
    if (x <0 || y <0)
        throw new ArgumentException();
            
    // Shortcut exit
    if (x == y)
    {
        // Perform the operation
        var temp = x <<1; // Optimization for 2*x

        // Check output
        if (temp <0)
            throw new ArgumentException();

        return temp;
    }

    // Perform the operation
    var result = x + y;

    // Check output
    if (result <0)
        throw new ArgumentException();

    return result;
}

Im Beispielcode (und es ist nur ein Beispiel), Sum-Methode versucht eine Verknüpfung, wenn die zwei Werte gleich sind – anstelle von Aufsummieren multiplizieren. Code zum Überprüfen der Ausgabewerte, muss jedoch für jedes frühen Wegfahrweg im Code repliziert werden.

Die Quintessenz ist, dass niemand nach vernünftigem Ermessen ein Contract-First-Ansatz für die Softwareentwicklung ohne einige schwerwiegende Werkzeugbereitstellung oder mindestens einen bestimmten Helper-Framework vorstellen kann. Vorläufige Prüfbedingungen ist relativ leicht und preiswert zu führen; Umgang mit Bedingungen nach der Ausführung manuell können Sie die gesamte Codebasis unhandlich und Fehler anfällig. Um ein paar andere Nebenkosten Aspekte von Verträgen, die den Quellcode der Klassen machen würde, ganz zu schweigen von Real Leg dich für Entwickler, wie z. B. Bedingungen werden überprüft, wenn Eingabeparameter sind Sammlungen und sicherstellen, die die Klasse wird immer in einem bekannten gültigen Zustand, wenn eine Methode oder eine Eigenschaft aufgerufen wird.

Geben Sie die Code-Verträge

In der.NET Framework 4, Code-Verträge ist ein Framework, das eine viel bequemere Syntax in einem Klassenvertrag express bietet. Insbesondere Code Verträge unterstützt drei Arten von Verträgen: Vorbedingungen, Postconditions und invarianten. Vorbedingungen kennzeichnen die vorläufigen Bedingungen, die für eine Methode zur sicheren Ausführung überprüft werden sollten. Postconditions express die Bedingungen, die überprüft werden soll, sobald die Methode ordnungsgemäß oder aufgrund von einer ausgelösten Ausnahme ausgeführt wurde. Schließlich beschreibt eine invariante eine Bedingung, die während der Lebensdauer von allen Klasseninstanzen immer true ist. Genauer gesagt, eine invariante gibt eine Bedingung, die nach jeder mögliche Interaktion zwischen der Klasse und einem Client bereithalten muss an – d. h. nach dem Ausführen der öffentlichen Member, einschließlich Konstruktoren. Ausgedrückt als invarianten Bedingungen sind nicht aktiviert und anschließend vorübergehend verletzt werden nach dem Aufruf eines privaten Members.

Die Code-Verträge-API besteht aus einer Liste von statischen Methoden der Klasse Vertrag definiert. Verwenden Sie die Methode, um die Vorbedingungen und sorgt für Postconditions Ausdrücken express erfordert. Abbildung 4 zeigt, wie Sie die Calculator-Klasse, die mithilfe von Code Verträge neu zu schreiben.

Abbildung 4 die Calculator-Klasse, die mithilfe von Code Verträge geschrieben

using System.Diagnostics.Contracts;
public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
        Contract.Ensures(Contract.Result<Int32>() >= 0);

        if (x == y)
            return 2 * x;

        return x + y;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
        Contract.Requires<ArgumentOutOfRangeException>(y > 0);

        Contract.Ensures(Contract.Result<Int32>() >= 0);

        return x / y;
    }
}

Einen schnellen Vergleich der Abbildung 3 und Abbildung 4 zeigt die Leistungsfähigkeit einer effektiven API für die Implementierung von DbC. Methodencode ist wieder stark lesbarer Form in dem Sie nur zwei Ebenen unterscheiden: Vertrag Informationen einschließlich Vorbedingungen und Postconditions sowie tatsächlichen Verhalten. Sie müssen keine Kombination von Bedingungen Verhalten, wie in Abbildung 3. Dies hat zur Folge, Lesbarkeit wird erheblich verbessert und für das Team viel einfacher ruft diesen Code zu verwalten. Beispielsweise können schnell und sicher hinzufügen eine neue Vorbedingung oder Postconditions nach Belieben bearbeiten – Sie an einer Stelle eingreifen und Ihre Änderungen klar verfolgt werden können.

Vertragsinformationen wird über den einfachen C#- oder Visual Basic-Code angegeben. Vertrag-Anweisungen werden nicht wie klassische deklarative Attribute, aber dabei immer noch eine starke deklarative Geschmacksrichtung verwaltet. Mithilfe von einfachen Code anstelle von Attributen erhöht die Programmiermöglichkeiten von Entwicklern, macht jedoch erwarten, dass die Bedingungen express, die Sie Bedenken haben. Zur gleichen Zeit Ihnen Code Verträge mit weitere Unterstützung bei der der Code umgestaltet. Code-Verträge Hinweisen in der Tat das Verhalten, die von der Methode erwartet werden sollte. Auf diese Weise Codierung Disziplin beibehalten werden, wenn Sie Schreibmethoden und help keep your Code lesbar, auch wenn zahlreiche Vorbedingungen und Postconditions zu erhalten. Obwohl Sie Verträge mit einer allgemeine Syntax wie die in Ausdrücken können Abbildung 4, wenn tatsächlich Code kompiliert, resultierende Flow kann nicht werden stark von den Code im beschriebenen Abbildung 3. Wo ist dann der Trick?

Ein zusätzliches Tool im Buildprozess von Visual Studio integriert – Code Verträge-Rekorder – dies geschieht mithilfe der Umformung des Codes, den beabsichtigten Zweck ausdrückliche Vorbedingungen und Postconditions verstehen und erweitern sie in der richtigen Codeblöcke platziert, wo sie logisch gehören. Als Entwickler Sie gerade Gedanken soll ein Postcondition angelegt und wo diese duplizieren, wenn an einem bestimmten Punkt Sie den Code zum Hinzufügen einer anderen Einladehafen bearbeiten.

Ausdrücken von Bedingungen

Sie können herausfinden, bis die genaue Syntax Vorbedingungen und Postconditions-Dokumentation, die Code-Verträge; eine aktuelle PDF-Datei erhalten Sie von der DevLabs-Website unter Bit.LY/f4LxHi. Ich werde kurz zusammenfassen. Verwenden Sie die folgende Methode, um eine erforderliche Bedingung anzugeben, und andernfalls wird die angegebene Ausnahme:

Contract.Requires<TException> (Boolean condition)

Die Methode hat einige Überladungen, die Sie berücksichtigen sollten. Die Methode stellt ausdrückt ein Postcondition:

Contract.Ensures(Boolean condition)

Wenn es darum geht, eine Vorbedingung schreiben, wird der Ausdruck in der Regel nur Eingabeparameter und vielleicht eine andere Methode oder Eigenschaft in der gleichen Klasse enthalten.Wenn dies der Fall ist, müssen Sie ergänzen diese Methode mit dem reinen-Attribut Beachten Sie, dass die Ausführung der Methode den Zustand des Objekts Änderungen vorgenommen werden.Beachten Sie, dass Code Vertrag Tools gehen davon aus, dass Eigenschaftengettern rein sind.

Wenn Sie ein Postcondition schreiben, müssen Sie möglicherweise Zugriff auf andere Informationen, wie z. B. der Wert zurückgegeben wird oder der anfängliche Wert einer lokalen Variablen.Dies erfolgt über ad-hoc-Methoden, z. B. Contract.Result <T> beim Abrufen des Wertes (vom Typ T) zurückgegeben wird, von der Methode und die Contract.OldValue <T> um den Wert in der angegebenen lokalen Variablen am Anfang der Ausführung der Methode zu erhalten.Schließlich haben Sie Gelegenheit, eine Bedingung zu überprüfen, wenn während der Ausführung der Methode eine Ausnahme ausgelöst wird.In diesem Fall verwenden Sie die Methode Contract.EnsuresOnThrow <TException>.

Abbreviators

Die Syntax die Vertrag ist sicherlich kompakter als die Verwendung von einfachen Code, aber es kann stark anwachsen ebenfalls.In diesem Fall ist die Lesbarkeit erneut gefährdet.Eine natürliche Abhilfe ist mehrere Vertrag-Anweisungen in eine Unterroutine gruppieren, wie im Abbildung 5.

Abbildung 5 ContractAbbreviators verwenden

public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        // Check input values
        ValidateOperands(x, y);
        ValidateResult();

        // Perform the operation
        if (x == y)
            return x<<1; 
        return x + y;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        // Check input values   
        ValidateOperandsForDivision(x, y);
        ValidateResult();

        // Perform the operation
        return x / y;
    }

    [ContractAbbreviator]

    private void ValidateOperands(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
    }

    [ContractAbbreviator]
    private void ValidateOperandsForDivision(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
        Contract.Requires<ArgumentOutOfRangeException>(y > 0);
    }

    [ContractAbbreviator]

    private void ValidateResult()
    {
        Contract.Ensures(Contract.Result<Int32>() >= 0);
    }
}

Das ContractAbbreviator-Attribut weist den Rekorder auf wie die ergänzten Methoden richtig zu interpretieren. Ohne das Attribut, wenn Sie als eine Art von Makro, um die ValidateResult-Methode in der Tat erweitern (und andere Methoden ValidateXxx in Abbildung 5) würde vielmehr inextricable Code enthalten. Was würde beispielsweise Contract.Result <T> finden Sie, wie er in einer void-Methode verwendet wird? Derzeit muss das ContractAbbreviator-Attribut explizit vom Entwickler in das Projekt definiert werden, wie es in der Mscorlib-Assembly enthalten ist. Die Klasse ist recht einfach:

public class Calculator
{
    public Int32 Sum(Int32 x, Int32 y)
    {
        // Check input values
        ValidateOperands(x, y);
        ValidateResult();

        // Perform the operation
        if (x == y)
            return x<<1; 
        return x + y;
    }

    public Int32 Divide(Int32 x, Int32 y)
    {
        // Check input values   
        ValidateOperandsForDivision(x, y);
        ValidateResult();

        // Perform the operation
        return x / y;
    }

    [ContractAbbreviator]

    private void ValidateOperands(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
    }

    [ContractAbbreviator]
    private void ValidateOperandsForDivision(Int32 x, Int32 y)
    {
        Contract.Requires<ArgumentOutOfRangeException>(x >= 0 && y >= 0);
        Contract.Requires<ArgumentOutOfRangeException>(y > 0);
    }

    [ContractAbbreviator]

    private void ValidateResult()
    {
        Contract.Ensures(Contract.Result<Int32>() >= 0);
    }
}

Sauber, verbesserte Code

Addieren, die Code-Verträge-API – im Wesentlichen die Vertragsbedingungen – ist ein systemeigener Bestandteil der.NET Framework-4, wie Sie zu der Mscorlib-Assembly gehört. Visual Studio 2010 verfügt über eine Konfigurationsseite in den Projekteigenschaften, die bestimmte an der Konfiguration des Code-Verträge. Für jedes Projekt müssen Sie dorthin wechseln und Überprüfung von Verträgen Runtime explizit aktivieren. Außerdem müssen Sie die Common Language Runtime-Tools von der DevLabs-Website downloaden. Einmal abholen auf der Website Sie das richtige Installationsprogramm für die Version von Visual Studio stehen Ihnen. Common Language Runtime-Tools enthalten Code Verträge-Rekorder und Interface-Generator sowie die statische Prüfung.

Code-Verträge können Sie die sauberen Code zu schreiben, indem Sie zwingt Sie erwartete Verhalten und die Ergebnisse für jede Methode anzugeben. Zumindest enthält diese Anleitung beim Wechseln Sie zum Umgestalten und verbessern Ihren Code. Es gibt viel mehr über Code Verträge erörtert werden. Insbesondere in diesem Artikel ich einfach schnell invarianten erwähnt und die leistungsstarke Funktion Vertragsvererbung überhaupt nicht erwähnen Sie. In zukünftigen Artikeln, ich möchte all dies und vieles mehr abdecken. Bleiben Sie am Ball!

Dino Esposito ist Autor des Titels „Programming Microsoft ASP.NET MVC“ (Microsoft Press 2010) und Mitverfasser von „Microsoft .NET:(Microsoft Press, 2010) and coauthor of “Microsoft .NET: Architecting Applicationsfor the Enterprise” (Microsoft Press, 2008). Esposito lebt in Italien und ist ein weltweit gefragter Referent für Branchenveranstaltungen. Folgen Sie ihm auf Twitter am Twitter.com/despos.

Dank an den folgenden technischen Experten für die Überprüfung dieses Artikels: Brian Grunkemeyer