Es hängt davon ab, ob. Die Entscheidung darüber, wo die Validierung erfolgen soll, sollte auf der Beschreibung und der Stärke des Vertrags beruhen, der von der Methode impliziert (oder dokumentiert) wird. Die Validierung ist ein guter Weg, um die Einhaltung eines bestimmten Vertrags zu stärken. Wenn die Methode aus irgendeinem Grund einen sehr strengen Vertrag hat, müssen Sie dies vor dem Anruf überprüfen.
Dies ist ein besonders wichtiges Konzept, wenn Sie eine öffentliche Methode erstellen , da Sie im Grunde dafür werben, dass eine Methode eine Operation ausführt. Es ist besser, das zu tun, was du sagst!
Nehmen Sie die folgende Methode als Beispiel:
public void DeletePerson(Person p)
{
_database.Delete(p);
}
Was beinhaltet der Vertrag DeletePerson
? Der Programmierer kann nur davon ausgehen, dass eventuell Person
übergebene Daten gelöscht werden. Wir wissen jedoch, dass dies nicht immer der Fall ist. Was ist, wenn p
ein null
Wert ist? Was ist, wenn p
es in der Datenbank keine gibt? Was ist, wenn die Datenbank getrennt ist? Daher scheint DeletePerson seinen Vertrag nicht gut zu erfüllen. Manchmal löscht es eine Person, und manchmal löst es eine NullReferenceException oder eine DatabaseNotConnectedException aus, oder manchmal führt es nichts aus (z. B. wenn die Person bereits gelöscht wurde).
APIs wie diese sind notorisch schwierig zu verwenden, denn wenn Sie diese "Black Box" einer Methode nennen, können alle möglichen schrecklichen Dinge passieren.
Hier sind ein paar Möglichkeiten, wie Sie den Vertrag verbessern können:
Fügen Sie eine Validierung hinzu und fügen Sie dem Vertrag eine Ausnahme hinzu. Dies macht den Vertrag stärker , aber erfordert , dass die Anrufer - Validierung durchzuführen. Der Unterschied ist jedoch, dass sie jetzt ihre Anforderungen kennen. In diesem Fall kommuniziere ich dies mit einem C # XML-Kommentar, aber Sie können stattdessen ein throws
(Java), ein Assert
oder ein Vertragstool wie Code Contracts hinzufügen .
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
_database.Delete(p);
}
Randnotiz: Das Argument gegen diesen Stil ist oft, dass er eine übermäßige Vorprüfung durch den gesamten aufrufenden Code verursacht, aber meiner Erfahrung nach ist dies oft nicht der Fall. Stellen Sie sich ein Szenario vor, in dem Sie versuchen, eine leere Person zu löschen. Wie ist das passiert? Woher kam die null Person? Wenn dies beispielsweise eine Benutzeroberfläche ist, warum wurde die Entf-Taste behandelt, wenn keine aktuelle Auswahl vorhanden ist? Wenn es bereits gelöscht wurde, sollte es nicht bereits von der Anzeige entfernt worden sein? Offensichtlich gibt es Ausnahmen, aber wenn ein Projekt wächst, werden Sie häufig solchen Code dafür danken, dass er verhindert, dass Fehler tief in das System eindringen.
Validierung und Code defensiv hinzufügen. Dies macht den Vertrag lockerer , da diese Methode jetzt mehr als nur das Löschen der Person bewirkt. Ich habe den Methodennamen geändert, um dies widerzuspiegeln, er ist jedoch möglicherweise nicht erforderlich, wenn Sie in Ihrer API konsistent sind. Dieser Ansatz hat Vor- und Nachteile. Der Vorteil ist, dass Sie jetzt TryDeletePerson
alle Arten von ungültigen Eingaben übergeben können und sich keine Gedanken über Ausnahmen machen müssen. Das Gegenteil ist natürlich, dass Benutzer Ihres Codes diese Methode wahrscheinlich zu oft aufrufen oder das Debuggen in Fällen, in denen p null ist, erschweren. Dies könnte als leichte Verletzung des Grundsatzes der einheitlichen Verantwortung angesehen werden. Denken Sie also daran, wenn ein Flammenkrieg ausbricht.
public void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
Ansätze kombinieren. Manchmal möchten Sie ein wenig von beidem, wenn Sie möchten, dass externe Anrufer die Regeln genau befolgen (um sie zum verantwortlichen Code zu zwingen), aber Sie möchten, dass Ihr privater Code flexibel ist.
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
TryDeletePerson(p);
}
internal void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
Nach meiner Erfahrung funktioniert es am besten, sich auf die Verträge zu konzentrieren, die Sie angedeutet haben, und nicht auf eine harte Regel. Defensive Codierung scheint in Fällen, in denen der Aufrufer nur schwer oder gar nicht feststellen kann, ob eine Operation gültig ist, besser zu funktionieren. Strikte Verträge scheinen besser zu funktionieren, wenn Sie erwarten, dass der Aufrufer Methodenaufrufe nur dann ausführt, wenn sie wirklich, wirklich sinnvoll sind.
Es ist eine Frage der Konvention, Dokumentation und des Anwendungsfalls.
Nicht alle Funktionen sind gleich. Nicht alle Anforderungen sind gleich. Nicht alle Validierungen sind gleich.
Wenn Ihr Java-Projekt beispielsweise nach Möglichkeit versucht, Nullzeiger zu vermeiden (siehe z. B. die Empfehlungen im Guava-Stil ), überprüfen Sie trotzdem jedes Funktionsargument, um sicherzustellen, dass es nicht Null ist? Es ist wahrscheinlich nicht notwendig, aber es besteht die Möglichkeit, dass Sie es trotzdem tun, um das Auffinden von Fehlern zu erleichtern. Sie können jedoch eine Zusicherung verwenden, bei der Sie zuvor eine NullPointerException ausgelöst haben.
Was ist, wenn das Projekt in C ++ ist? Konvention / Tradition in C ++ ist es, Vorbedingungen zu dokumentieren, sie jedoch (wenn überhaupt) nur in Debugbuilds zu überprüfen.
In beiden Fällen haben Sie eine dokumentierte Voraussetzung für Ihre Funktion: Kein Argument darf null sein. Sie können stattdessen die Domäne der Funktion erweitern, um Nullen mit definiertem Verhalten einzuschließen, z. B. "Wenn ein Argument Null ist, wird eine Ausnahme ausgelöst". Das ist natürlich wieder mein C ++ - Erbe - in Java ist es üblich genug, Vorbedingungen auf diese Weise zu dokumentieren.
Es können aber nicht alle Voraussetzungen auch nur zumutbar überprüft werden. Ein binärer Suchalgorithmus setzt beispielsweise voraus, dass die zu durchsuchende Sequenz sortiert werden muss. Die Überprüfung, ob dies tatsächlich der Fall ist, ist jedoch eine O (N) -Operation. Wenn Sie dies also bei jedem Aufruf tun, verlieren Sie den Punkt, an dem Sie überhaupt einen O (log (N)) -Algorithmus verwenden. Wenn Sie defensiv programmieren, können Sie kleinere Überprüfungen durchführen (z. B. um sicherzustellen, dass für jede Partition, die Sie durchsuchen, die Start-, Mittel- und Endwerte sortiert sind), aber dies erfasst nicht alle Fehler. In der Regel müssen Sie sich nur darauf verlassen, dass die Voraussetzung erfüllt ist.
Der einzige reale Ort, an dem Sie explizite Prüfungen benötigen, liegt an den Grenzen. Externer Input für Ihr Projekt? Validieren, validieren, validieren. Ein grauer Bereich ist API-Grenzen. Es hängt wirklich davon ab, wie sehr Sie dem Client-Code vertrauen möchten, wie viel Schaden ungültige Eingaben anrichten und wie viel Unterstützung Sie beim Auffinden von Fehlern leisten möchten. Jede Berechtigungsgrenze muss natürlich als extern gelten - Syscalls werden beispielsweise in einem Kontext mit erhöhten Berechtigungen ausgeführt und müssen daher sehr sorgfältig validiert werden. Eine solche Validierung muss natürlich systemintern erfolgen.
quelle
Die Parameterüberprüfung sollte das Anliegen der aufgerufenen Funktion sein. Die Funktion sollte wissen, was als gültige Eingabe gilt und was nicht. Anrufer wissen dies möglicherweise nicht, insbesondere wenn sie nicht wissen, wie die Funktion intern implementiert ist. Es ist zu erwarten, dass die Funktion jede Kombination von Parameterwerten von Aufrufern verarbeitet.
Da die Funktion für die Überprüfung von Parametern verantwortlich ist, können Sie Komponententests für diese Funktion schreiben, um sicherzustellen, dass sie sich sowohl für gültige als auch für ungültige Parameterwerte wie beabsichtigt verhält.
quelle
Innerhalb der Funktion selbst. Wenn die Funktion mehrmals verwendet wird, möchten Sie den Parameter nicht für jeden Funktionsaufruf überprüfen.
Wenn die Funktion so aktualisiert wird, dass die Validierung des Parameters beeinträchtigt wird, müssen Sie nach jedem Auftreten der Aufrufervalidierung suchen, um sie zu aktualisieren. Es ist nicht schön :-).
Sie können sich auf die Schutzklausel beziehen
Aktualisieren
Siehe meine Antwort für jedes von Ihnen bereitgestellte Szenario.
Wenn die Behandlung von ungültigen Variablen variieren kann, ist es gut, sie auf der Anruferseite zu validieren (z. B.
sqrt()
Funktion - in einigen Fällen möchte ich möglicherweise mit komplexen Zahlen arbeiten, also behandle ich die Bedingung im Anrufer).Antworten
Die Mehrheit der Programmiersprachen unterstützt standardmäßig ganze und reelle Zahlen, keine komplexen Zahlen, daher
sqrt
akzeptiert ihre Implementierung nur nicht negative Zahlen. Der einzige Fall, in dem Sie einesqrt
Funktion haben, die eine komplexe Zahl zurückgibt, ist die Verwendung einer auf Mathematik ausgerichteten Programmiersprache wie MathematicaDarüber hinaus ist
sqrt
für die meisten Programmiersprachen bereits eine Implementierung implementiert, sodass Sie diese nicht ändern können. Wenn Sie versuchen, die Implementierung zu ersetzen (siehe Patching für Affen), sind Ihre Mitarbeiter zutiefst geschockt, warumsqrt
plötzlich negative Zahlen akzeptiert werden.Wenn Sie eine möchten, können Sie sie um Ihre benutzerdefinierte
sqrt
Funktion wickeln , die negative Zahlen verarbeitet und komplexe Zahlen zurückgibt.Wenn die Überprüfungsbedingung bei jedem Aufrufer gleich ist, ist es besser, sie in der Funktion zu überprüfen, um Doppelungen zu vermeiden
Antworten
Ja, dies ist eine gute Vorgehensweise, um eine Streuung der Parameterüberprüfung in Ihrem Code zu vermeiden.
Die Validierung der Eingabeparameter im Aufrufer erfolgt nur einmal vor dem Aufruf vieler Funktionen mit diesem Parameter. Daher ist die Validierung eines Parameters in jeder Funktion nicht wirksam
Antworten
Es wird schön sein, wenn der Anrufer eine Funktion ist, meinst du nicht auch?
Wenn die Funktionen innerhalb des Aufrufers von einem anderen Aufrufer verwendet werden, was hindert Sie daran, den Parameter innerhalb der vom Aufrufer aufgerufenen Funktionen zu validieren?
Die richtige Lösung hängt vom jeweiligen Fall ab
Antworten
Streben Sie nach wartbarem Code. Durch das Verschieben der Parametervalidierung wird sichergestellt, dass eine Quelle der Wahrheit darüber vorliegt, was die Funktion akzeptieren kann oder nicht.
quelle
Eine Funktion sollte ihre Vor- und Nachbedingungen angeben.
Die Vorbedingungen sind die Bedingungen, die vom Aufrufer erfüllt werden müssen, bevor er die Funktion korrekt verwenden kann und die Gültigkeit von Eingabeparametern einschließen kann (und dies häufig auch tut).
Die Nachbedingungen sind die Versprechungen, die die Funktion ihren Anrufern macht.
Wenn die Gültigkeit der Parameter einer Funktion Teil der Vorbedingungen ist, muss der Aufrufer sicherstellen, dass diese Parameter gültig sind. Dies bedeutet jedoch nicht, dass jeder Aufrufer jeden Parameter vor dem Aufruf explizit überprüfen muss. In den meisten Fällen sind keine expliziten Tests erforderlich, da die interne Logik und die Bedingungen des Aufrufers bereits sicherstellen, dass die Parameter gültig sind.
Als Sicherheitsmaßnahme gegen Programmierfehler (Bugs) können Sie überprüfen, ob die an eine Funktion übergebenen Parameter tatsächlich die angegebenen Voraussetzungen erfüllen. Da diese Tests kostspielig sein können, ist es eine gute Idee, sie für Release-Builds deaktivieren zu können. Wenn diese Tests fehlschlagen, sollte das Programm beendet werden, da nachweislich ein Fehler aufgetreten ist.
Obwohl auf den ersten Blick das Einchecken des Anrufers zu doppelten Codes zu führen scheint, ist es tatsächlich umgekehrt. Das Einchecken des Angerufenen führt zu doppelten Codes und vielen unnötigen Arbeiten.
Denken Sie nur daran, wie oft Sie Parameter durch mehrere Funktionsebenen führen und dabei nur geringfügige Änderungen an einigen von ihnen vornehmen. Wenn Sie die Check-in-Callee- Methode konsequent anwenden , muss jede dieser Zwischenfunktionen die Prüfung für jeden der Parameter erneut durchführen.
Und nun stellen Sie sich vor, dass einer dieser Parameter eine sortierte Liste sein soll.
Beim Einchecken des Anrufers müsste nur die erste Funktion sicherstellen, dass die Liste wirklich sortiert ist. Alle anderen wissen, dass die Liste bereits sortiert ist (wie sie in ihrer Vorbedingung angegeben haben) und können sie ohne weitere Überprüfung weiterleiten.
quelle
Meistens können Sie nicht wissen, wer, wann und wie die von Ihnen geschriebene Funktion aufruft. Es ist am besten, das Schlimmste anzunehmen: Ihre Funktion wird mit ungültigen Parametern aufgerufen. Darum sollten Sie sich unbedingt kümmern.
Wenn die von Ihnen verwendete Sprache Ausnahmen unterstützt, prüfen Sie möglicherweise nicht auf bestimmte Fehler und stellen sicher, dass eine Ausnahme ausgelöst wird. In diesem Fall müssen Sie den Fall jedoch in der Dokumentation beschreiben (Dokumentation erforderlich). Die Ausnahme gibt dem Aufrufer genügend Informationen darüber, was passiert ist, und lenkt die Aufmerksamkeit auf die ungültigen Argumente.
quelle