Ich habe gehört, dass es empfohlen wird, Argumente öffentlicher Methoden zu validieren:
- Sollte man nach null suchen, wenn man nicht null erwartet?
- Sollte eine Methode ihre Parameter validieren?
- MSDN - CA1062: Überprüfen Sie die Argumente öffentlicher Methoden (ich habe .NET-Hintergrund, aber die Frage ist nicht C # -spezifisch ).
Motivation ist verständlich. Wenn ein Modul falsch verwendet wird, möchten wir sofort eine Ausnahme auslösen, anstatt ein unvorhersehbares Verhalten.
Was mich stört, ist, dass falsche Argumente nicht der einzige Fehler sind, der bei der Verwendung eines Moduls gemacht werden kann. Hier sind einige Fehlerszenarien, in denen wir eine Überprüfungslogik hinzufügen müssen, wenn wir den Empfehlungen folgen und keine Fehlereskalation wünschen:
- Eingehender Anruf - unerwartete Argumente
- Eingehender Anruf - Modul ist in einem falschen Zustand
- Externer Anruf - unerwartete Ergebnisse zurückgegeben
- Externer Aufruf - unerwartete Nebenwirkungen (doppelter Eintrag in ein aufrufendes Modul, Aufheben anderer Abhängigkeitszustände)
Ich habe versucht, all diese Bedingungen zu berücksichtigen und ein einfaches Modul mit einer Methode zu schreiben (sorry, nicht-C # Jungs):
public sealed class Room
{
private readonly IDoorFactory _doorFactory;
private bool _entered;
private IDoor _door;
public Room(IDoorFactory doorFactory)
{
if (doorFactory == null)
throw new ArgumentNullException("doorFactory");
_doorFactory = doorFactory;
}
public void Open()
{
if (_door != null)
throw new InvalidOperationException("Room is already opened");
if (_entered)
throw new InvalidOperationException("Double entry is not allowed");
_entered = true;
_door = _doorFactory.Create();
if (_door == null)
throw new IncompatibleDependencyException("doorFactory");
_door.Open();
_entered = false;
}
}
Jetzt ist es sicher =)
Es ist ziemlich gruselig. Aber stellen Sie sich vor, wie gruselig es in einem realen Modul mit Dutzenden von Methoden, komplexem Zustand und vielen externen Aufrufen sein kann (Hallo, Liebhaber der Abhängigkeitsinjektion!). Beachten Sie, dass Sie, wenn Sie ein Modul aufrufen, dessen Verhalten überschrieben werden kann (nicht versiegelte Klasse in C #), einen externen Anruf tätigen und die Konsequenzen für den Aufrufer nicht vorhersehbar sind.
Zusammenfassend, was ist der richtige Weg und warum? Wenn Sie aus den folgenden Optionen auswählen können, beantworten Sie bitte weitere Fragen.
Überprüfen Sie die gesamte Modulnutzung. Benötigen wir Unit-Tests? Gibt es Beispiele für solchen Code? Sollte die Abhängigkeitsinjektion in der Verwendung eingeschränkt sein (da dies mehr Überprüfungslogik verursacht)? Ist es nicht praktisch, diese Prüfungen auf die Debug-Zeit zu verschieben (nicht in der Version enthalten)?
Überprüfen Sie nur Argumente. Nach meiner Erfahrung ist die Argumentprüfung - insbesondere die Nullprüfung - die am wenigsten wirksame Prüfung, da Argumentfehler selten zu komplexen Fehlern und Fehlereskalationen führen. Meistens erhalten Sie eine NullReferenceException
in der nächsten Zeile. Warum sind Argumentprüfungen so besonders?
Überprüfen Sie nicht die Modulnutzung. Es ist eine ziemlich unpopuläre Meinung, können Sie erklären, warum?
Antworten:
TL; DR: Statusänderung validieren, auf [Gültigkeit des] aktuellen Status verlassen.
Im Folgenden betrachte ich nur freigegebene Überprüfungen. Nur aktive Debug-Zusicherungen sind eine Form der Dokumentation, die auf ihre eigene Weise nützlich ist und für diese Frage nicht in Frage kommt.
Beachten Sie folgende Grundsätze:
Definitionen
Veränderlicher Zustand
Problem
In imperativen Sprachen können das Fehlersymptom und seine Ursache durch stundenlanges schweres Heben getrennt sein. Staatliche Korruption kann sich verstecken und mutieren, was zu unerklärlichen Fehlern führt, da die Überprüfung des aktuellen Zustands keinen vollständigen Korruptionsprozess und damit die Ursache des Fehlers aufdecken kann.
Lösung
Jede Zustandsänderung sollte sorgfältig ausgearbeitet und überprüft werden. Eine Möglichkeit, mit veränderlichen Zuständen umzugehen, besteht darin, sie auf ein Minimum zu beschränken. Dies wird erreicht durch:
Wenn Sie den Status einer Komponente erweitern, sollten Sie dies in Betracht ziehen, indem Sie den Compiler die Unveränderlichkeit neuer Daten erzwingen lassen. Erzwingen Sie außerdem jede sinnvolle Laufzeitbeschränkung und beschränken Sie mögliche resultierende Zustände auf einen kleinstmöglichen genau definierten Satz.
Beispiel
Wiederholung und Verantwortungszusammenhalt
Problem
Das Überprüfen der Voraussetzungen und Nachbedingungen des Betriebs führt zu einer Verdoppelung des Bestätigungscodes sowohl im Client als auch in der Komponente. Das Überprüfen des Komponentenaufrufs zwingt den Client häufig dazu, einige der Verantwortlichkeiten der Komponente zu übernehmen.
Lösung
Verlassen Sie sich nach Möglichkeit auf die Komponente, um die Statusüberprüfung durchzuführen. Komponenten müssen eine API bereitstellen, für die keine spezielle Verifizierungsüberprüfung erforderlich ist (z. B. Überprüfung der Argumente oder Durchsetzung der Operationssequenz), damit der Komponentenstatus genau definiert bleibt. Sie verpflichten sich, API-Aufrufargumente nach Bedarf zu überprüfen, Fehler mit den erforderlichen Mitteln zu melden und ihre Statusbeschädigung zu verhindern.
Clients sollten sich auf Komponenten verlassen, um die Verwendung ihrer API zu überprüfen. Nicht nur Wiederholungen werden vermieden, der Client hängt nicht mehr von zusätzlichen Implementierungsdetails der Komponente ab. Betrachten Sie Framework als eine Komponente. Schreiben Sie nur dann benutzerdefinierten Bestätigungscode, wenn die Invarianten der Komponente nicht streng genug sind oder um die Komponentenausnahme als Implementierungsdetail zu kapseln.
Wenn eine Operation den Status nicht ändert und nicht durch Statusänderungsüberprüfungen abgedeckt ist, überprüfen Sie jedes Argument auf einer möglichst tiefen Ebene.
Beispiel
Antworten
Wenn die beschriebenen Prinzipien auf das betreffende Beispiel angewendet werden, erhalten wir:
Zusammenfassung
Der Client-Status besteht aus eigenen Feldwerten und Teilen des Komponentenstatus, die nicht durch eigene Invarianten abgedeckt sind. Die Überprüfung sollte nur vor der tatsächlichen Statusänderung eines Clients erfolgen.
quelle
Eine Klasse ist für ihren eigenen Zustand verantwortlich. Validieren Sie also in dem Maße, in dem die Dinge in einem akzeptablen Zustand gehalten oder versetzt werden.
Nein, werfen Sie keine Ausnahme, sondern liefern Sie vorhersehbares Verhalten. Eine Folge der staatlichen Verantwortung ist es, die Klasse / Anwendung so tolerant wie möglich zu gestalten. Zum Beispiel
null
anaCollection.Add()
? Nur nicht hinzufügen und weitermachen. Sie erhaltennull
Eingaben zum Erstellen eines Objekts? Erstellen Sie ein Nullobjekt oder ein Standardobjekt. Oben ist dasdoor
schonopen
? Also, mach weiter.DoorFactory
Argument ist null? Erstellen Sie eine neue. Wenn ich eine erstelle, habeenum
ich immer einUndefined
Mitglied. Ich benutzeDictionary
s liberal undenums
definiere Dinge explizit; und dies trägt wesentlich dazu bei, vorhersehbares Verhalten zu liefern.Ja, obwohl ich durch den Schatten des Tals der Parameter gehe, werde ich keine Argumente fürchten. Zu den vorhergehenden verwende ich auch so oft wie möglich Standard- und optionale Parameter.
All dies ermöglicht es der internen Verarbeitung, weiterzumachen. In einer bestimmten Anwendung habe ich Dutzende von Methoden in mehreren Klassen mit nur einer Stelle, an der eine Ausnahme ausgelöst wird. Selbst dann liegt es nicht an Null-Argumenten oder daran, dass ich die Verarbeitung nicht fortsetzen konnte, sondern daran, dass der Code ein "nicht funktionierendes" / "Null" -Objekt erstellt hat.
bearbeiten
Ich zitiere meinen Kommentar in seiner Gesamtheit. Ich denke, das Design sollte nicht einfach "aufgeben", wenn man auf "null" stößt. Besonders mit einem zusammengesetzten Objekt.
Ende bearbeiten
quelle
encapsulation
&single responsibility
.null
Nach der ersten, mit dem Client interagierenden Schicht erfolgt praktisch keine Überprüfung. Der Code ist <strike> tolerant </ strike> robust. Klassen werden mit Standardzuständen entworfen und funktionieren so, ohne geschrieben zu werden, als ob interagierender Code von Fehlern befallen ist. Ein zusammengesetzter Elternteil muss nicht die untergeordneten Ebenen erreichen, um die Gültigkeit zu bewerten (und implizitnull
in allen Ecken und Winkeln nachsehen). Der Elternteil weiß, was der Standardzustand eines Kindes bedeutet