Code mit möglicherweise nutzloser Ausnahmebehandlung stärken

12

Ist es empfehlenswert, eine unnötige Ausnahmebehandlung zu implementieren, falls ein anderer Teil des Codes nicht korrekt codiert ist?

Einfaches Beispiel

Eine einfache, damit ich nicht alle verliere :).

Angenommen, ich schreibe eine App, in der die Informationen einer Person (Name, Adresse usw.) angezeigt werden und die Daten aus einer Datenbank extrahiert werden. Angenommen, ich bin derjenige, der den UI-Teil codiert, und ein anderer schreibt den DB-Abfragecode.

Stellen Sie sich nun vor, dass die Spezifikationen Ihrer App besagen, dass die Person, die die Abfrage codiert, dies erledigen sollte, indem sie für das fehlende Feld "NA" zurückgibt, wenn die Informationen der Person unvollständig sind (sagen wir, der Name fehlt in der Datenbank).

Was ist, wenn die Abfrage schlecht codiert ist und diesen Fall nicht behandelt? Was passiert, wenn der Typ, der die Abfrage geschrieben hat, ein unvollständiges Ergebnis verarbeitet und wenn Sie versuchen, die Informationen anzuzeigen, stürzt alles ab, weil Ihr Code nicht für die Anzeige leerer Inhalte vorbereitet ist?

Dieses Beispiel ist sehr einfach. Ich glaube, die meisten von Ihnen werden sagen: "Es ist nicht Ihr Problem, Sie sind nicht für diesen Absturz verantwortlich." Aber es ist immer noch Ihr Teil des Codes, der abstürzt.

Ein anderes Beispiel

Nehmen wir an, ich bin derjenige, der die Anfrage schreibt. In den Spezifikationen wird nicht dasselbe wie oben angegeben, aber der Typ, der die Abfrage "Einfügen" schreibt, sollte sicherstellen, dass alle Felder ausgefüllt sind, wenn eine Person zur Datenbank hinzugefügt wird, um das Einfügen unvollständiger Informationen zu vermeiden. Sollte ich meine "Auswahl" -Abfrage schützen, um sicherzustellen, dass ich dem Benutzer vollständige Informationen gebe?

Die Fragen

Was ist, wenn in den Spezifikationen nicht ausdrücklich steht, dass "dieser Typ für die Bewältigung dieser Situation verantwortlich ist"? Was ist, wenn eine dritte Person eine andere Abfrage implementiert (ähnlich der ersten, jedoch in einer anderen Datenbank) und Ihren UI-Code verwendet, um sie anzuzeigen, diesen Fall jedoch in seinem Code nicht behandelt?

Sollte ich das Notwendige tun, um einen möglichen Absturz zu verhindern, auch wenn ich nicht derjenige bin, der den schlechten Fall behandeln soll?

Ich suche keine Antwort wie "(s) er ist derjenige, der für den Absturz verantwortlich ist", da ich hier keinen Konflikt löse, würde ich gerne wissen, ob ich meinen Code vor Situationen schützen soll, für die ich nicht verantwortlich bin zu handhaben? Hier würde ein einfaches "wenn leer, etwas tun" ausreichen.

Diese Frage befasst sich im Allgemeinen mit der redundanten Ausnahmebehandlung. Ich frage es, weil ich, wenn ich alleine an einem Projekt arbeite, 2-3 mal eine ähnliche Ausnahmebehandlung in aufeinanderfolgenden Funktionen programmieren kann, "nur für den Fall", dass ich etwas falsch gemacht habe und einen schlechten Fall durchkommen lasse.

rdurand
quelle
4
Sie sprechen von "Tests", aber soweit ich Ihr Problem verstehe, meinen Sie "Tests, die in der Produktion angewendet werden". Dies wird besser als "Validierung" oder "Ausnahmebehandlung" bezeichnet.
Doc Brown
1
Ja, das passende Wort ist "Ausnahmebehandlung".
Durand
änderte das falsche Tag dann
Doc Brown
Ich verweise Sie auf The DailyWTF - sind Sie sicher , dass Sie diese Art von Tests durchführen möchten?
gbjbaanb
@gbjbaanb: Wenn ich Ihren Link richtig verstehe, ist das überhaupt nicht das, worüber ich spreche. Ich spreche nicht von "dummen Tests", sondern vom Duplizieren der Ausnahmebehandlung.
Rdurand

Antworten:

14

Worüber Sie hier sprechen, sind Vertrauensgrenzen . Vertrauen Sie der Grenze zwischen Ihrer Anwendung und der Datenbank? Vertraut die Datenbank darauf, dass die Daten aus der Anwendung immer vorab validiert werden?

Diese Entscheidung muss in jeder Bewerbung getroffen werden, und es gibt keine richtigen und falschen Antworten. Ich neige dazu, zu viele Grenzen als Vertrauensgrenze zu bezeichnen. Andere Entwickler vertrauen gerne darauf, dass APIs von Drittanbietern immer das tun, was Sie von ihnen erwarten.

pdr
quelle
5

Das Robustheitsprinzip "Sei konservativ in dem, was du sendest, sei liberal in dem, was du akzeptierst" ist das, was du suchst. Es ist ein gutes Prinzip - BEARBEITEN: Solange seine Anwendung keine schwerwiegenden Fehler verbirgt -, stimme ich @pdr zu, dass es immer von der Situation abhängt, ob Sie es anwenden sollten oder nicht.

Doc Brown
quelle
Einige Leute denken, dass das "Robustheitsprinzip" Mist ist. Der Artikel gibt ein Beispiel.
@MattFenwick: danke für den Hinweis, es ist ein gültiger Punkt, ich habe meine Antwort ein wenig geändert.
Doc Brown
2
Dies ist ein noch besserer Artikel, der auf die Probleme mit dem "Robustheitsprinzip" hinweist
hakoja
1
@hakoja: ehrlich, ich kenne diesen Artikel gut, es geht um Probleme, die auftreten, wenn Sie anfangen , dem Robustheitsprinzip nicht zu folgen (wie es einige MS-Leute mit neueren IE-Versionen versucht haben). Trotzdem ist dies ein bisschen weit von der ursprünglichen Frage entfernt.
Doc Brown
1
@DocBrown: Genau deswegen hättest du niemals liberal in dem sein sollen, was du akzeptierst. Robustheit bedeutet nicht, dass Sie alles akzeptieren müssen, was auf Sie geworfen wird, ohne sich zu beschweren, sondern dass Sie alles akzeptieren müssen, was auf Sie geworfen wird, ohne zusammenzustoßen.
Marjan Venema
1

Es hängt davon ab, was Sie testen. Nehmen wir jedoch an, dass der Umfang Ihres Tests nur Ihr eigener Code ist. In diesem Fall sollten Sie Folgendes testen:

  • Der "glückliche Fall": Geben Sie Ihrer Anwendung eine gültige Eingabe und stellen Sie sicher, dass die Ausgabe korrekt ist.
  • Die Fehlerfälle: Geben Sie ungültige Eingaben für Ihre Anwendung ein und stellen Sie sicher, dass diese korrekt verarbeitet werden.

Zu diesem Zweck können Sie nicht die Komponente Ihres Kollegen verwenden. Verwenden Sie stattdessen die Option "Verspotten" , ersetzen Sie den Rest der Anwendung durch "gefälschte" Module, die Sie über das Testframework steuern können. Wie genau Sie dies tun, hängt von der Art und Weise ab, wie die Module miteinander kommunizieren. Es kann ausreichen, die Methoden Ihres Moduls nur mit hartcodierten Argumenten aufzurufen, und es kann so komplex werden, als würde ein ganzes Framework geschrieben, das die öffentlichen Schnittstellen der anderen Module mit der Testumgebung verbindet.

Dies ist jedoch nur der Einzeltestfall. Sie möchten auch Integrationstests, bei denen Sie alle Module gemeinsam testen. Wiederum möchten Sie sowohl den glücklichen Fall als auch die Fehler testen.

In Ihrem Fall "Basic Example" schreiben Sie eine Mock-Klasse, die die Datenbankebene simuliert, um Ihren Code auf Unit-Tests zu testen. Ihre Mock-Klasse geht jedoch nicht wirklich in die Datenbank: Sie laden sie nur mit erwarteten Eingaben und festen Ausgaben vor. Im Pseudocode:

function test_ValidUser() {
    // set up mocking and fixtures
    userid = 23;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "Doe" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);
    expectedResult = "John Doe";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Und so testen Sie, ob Felder fehlen, die korrekt gemeldet werden :

function test_IncompleteUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedResult = { firstName: "John", lastName: "NA" };
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // let's say the user controller is specified to leave "NA" fields 
    // blank
    expectedResult = "John";

    // run the actual test
    actualResult = userController.displayUserAsString(userid);

    // check assertions
    assertEquals(expectedResult, actualResult);
    db.assertExpectedCall();
}

Jetzt wird es interessant. Was ist, wenn sich die echte DB-Klasse schlecht benimmt? Zum Beispiel könnte es aus unklaren Gründen eine Ausnahme auslösen. Wir wissen nicht, ob dies der Fall ist, aber wir möchten, dass unser eigener Code ordnungsgemäß damit umgeht. Kein Problem, wir müssen nur unsere MockDB zu einer Ausnahme machen, indem wir z. B. eine Methode wie die folgende hinzufügen:

class MockDB {
    // ... snip
    function getUser(userid) {
        if (this.fixedException) {
            throw this.fixedException;
        }
        else {
            return this.fixedResult;
        }
    }
}

Und dann sieht unser Testfall so aus:

function test_MisbehavingUser() {
    // set up mocking and fixtures
    userid = 57;
    db = new MockDB();
    db.fixedException = new SQLException("You have an error in your SQL syntax");
    db.expectedCall = { method: 'getUser', params: { userid: userid } };
    userController = new UserController(db);

    // run the actual test
    try {
        userController.displayUserAsString(userid);
    }
    catch (DatabaseException ex) {
        // This is good: our userController has caught the raw exception
        // from the database layer and wrapped it in a DatabaseException.
        return TEST_PASSED;
    }
    catch (Exception ex) {
        // This is not good: we have an exception, but it's the wrong kind.
        testLog.log("Found the wrong exception: " + ex);
        return TEST_FAILED;
    }
    // This is bad, too: either our mocking class didn't throw even when it
    // should have, or our userController swallowed the exception and
    // discarded it
    testLog.log("Expected an exception to be thrown, but nothing happened.");
    return TEST_FAILED;
}

Dies sind Ihre Unit-Tests. Für den Integrationstest verwenden Sie nicht die MockDB-Klasse. Stattdessen verketten Sie beide tatsächlichen Klassen miteinander. Sie brauchen noch Geräte; Beispielsweise sollten Sie die Testdatenbank auf einen bekannten Status initialisieren, bevor Sie den Test ausführen.

Was die Zuständigkeiten betrifft: Ihr Code sollte erwarten, dass der Rest der Codebasis gemäß der Spezifikation implementiert wird, aber er sollte auch darauf vorbereitet sein, die Dinge ordnungsgemäß zu handhaben, wenn der Rest versagt. Sie sind nicht dafür verantwortlich, anderen Code als Ihren eigenen zu testen, aber Sie sind dafür verantwortlich, dass Ihr Code widerstandsfähig gegen fehlerhaftes Verhalten des Codes auf der anderen Seite ist, und Sie sind auch dafür verantwortlich, die Widerstandsfähigkeit Ihres Codes zu testen. Das ist, was der dritte Test oben tut.

tdammers
quelle
Hast du die Kommentare unter der Frage gelesen? Das OP schrieb "Tests", aber er meinte es im Sinne von "Validierungsprüfungen" und / oder "Ausnahmebehandlung"
Doc Brown
1
@tdammers: sorry für das missverständnis, ich meinte in der tat ausnahmebehandlung .. trotzdem danke für die vollständige antwort, der letzte absatz ist was ich gesucht habe.
Rdurand
1

Es gibt drei Hauptprinzipien, die ich zu programmieren versuche:

  • TROCKEN

  • KUSS

  • YAGNI

All dies führt dazu, dass Sie das Risiko eingehen, Validierungscode zu schreiben, der an anderer Stelle dupliziert wird. Wenn sich die Validierungsregeln ändern, müssen diese an mehreren Stellen aktualisiert werden.

Natürlich könnten Sie irgendwann in der Zukunft Ihre Datenbank neu plattformen (es passiert). In diesem Fall könnte es vorteilhaft sein, den Code an mehr als einer Stelle zu haben. Aber ... Sie programmieren für etwas, das möglicherweise nicht passiert.

Jeder zusätzliche Code (auch wenn er sich nie ändert) ist mit einem Mehraufwand verbunden, da er geschrieben, gelesen, gespeichert und getestet werden muss.

Wenn all das oben Gesagte zutrifft, wäre es nicht angebracht, überhaupt keine Validierung durchzuführen. Um einen vollständigen Namen in der Anwendung anzuzeigen, benötigen Sie einige grundlegende Daten - auch wenn Sie die Daten selbst nicht validieren.

Robbie Dee
quelle
1

In den Wörtern des Laien.

Es gibt keine "Datenbank" oder "Anwendung" .

  1. Eine Datenbank kann von mehreren Anwendungen verwendet werden.
  2. Eine Anwendung kann mehr als eine Datenbank verwenden.
  3. Das Datenbankmodell sollte die Datenintegrität erzwingen, einschließlich des Auslösens eines Fehlers, wenn ein erforderliches Feld nicht in einer Einfügeoperation enthalten ist, es sei denn, in der Tabellendefinition ist ein Standardwert definiert. Dies muss auch dann erfolgen, wenn Sie die Zeile unter Umgehung der App direkt in die Datenbank einfügen. Lassen Sie das Datenbanksystem dies für Sie tun.
  4. Datenbanken sollten die Datenintegrität schützen und Fehler auslösen .
  5. Die Geschäftslogik muss diese Fehler abfangen und Ausnahmen für die Präsentationsebene auslösen.
  6. Die Präsentationsebene muss Eingaben validieren, Ausnahmen behandeln oder dem Benutzer einen traurigen Hamster zeigen.

Nochmal:

  • Datenbank-> Fehler werfen
  • Business Logic-> Fehler abfangen und Ausnahmen auslösen
  • Präsentationsebene-> Überprüfen, Ausnahmen auslösen oder traurige Nachrichten anzeigen.
Tulains Córdova
quelle