Ich habe eine Methode, bei der die gesamte Logik in einer foreach-Schleife ausgeführt wird, die den Parameter der Methode durchläuft:
public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
foreach(var node in nodes)
{
// yadda yadda yadda
yield return transformedNode;
}
}
In diesem Fall führt das Einreichen einer leeren Sammlung zu einer leeren Sammlung, aber ich frage mich, ob das nicht klug ist.
Meine Logik hier ist, dass, wenn jemand diese Methode aufruft, er beabsichtigt, Daten weiterzuleiten, und nur unter irrtümlichen Umständen eine leere Auflistung an meine Methode übergeben würde.
Sollte ich dieses Verhalten abfangen und eine Ausnahme auslösen, oder ist es empfehlenswert, die leere Auflistung zurückzugeben?
c#
exceptions
collections
parameters
Nick Udell
quelle
quelle
null
aber nicht, wenn sie leer ist.Antworten:
Dienstprogrammmethoden sollten keine leeren Sammlungen verwenden. Ihre API-Kunden würden Sie dafür hassen.
Eine Sammlung kann leer sein. Eine "Sammlung, die nicht leer sein darf" ist konzeptionell viel schwieriger zu bearbeiten.
Das Transformieren einer leeren Sammlung hat ein offensichtliches Ergebnis: die leere Sammlung. (Sie können sogar etwas Müll sparen, indem Sie den Parameter selbst zurückgeben.)
Es gibt viele Umstände, unter denen ein Modul Listen von Dingen führt, die möglicherweise bereits mit etwas gefüllt sind oder nicht. Vor jedem Anruf auf Leere prüfen zu müssen,
transform
ist ärgerlich und kann dazu führen, dass ein einfacher, eleganter Algorithmus zu einem hässlichen Durcheinander wird.Utility-Methoden sollten immer danach streben, in ihren Eingaben liberal und in ihren Ausgaben konservativ zu sein.
Gehen Sie aus all diesen Gründen, um Himmels willen, korrekt mit der leeren Sammlung um. Nichts ist ärgerlicher als ein Hilfsmodul, das denkt, es weiß, was Sie wollen, besser als Sie.
quelle
null
in die leere Sammlung einfüge. Funktionen mit impliziten Einschränkungen sind ein Schmerz.Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input);
Wenn Populate1 die Auflistung leer lässt und Sie sie für den ersten TransformNodes-Aufruf zurückgeben, sind Ausgabe1 und Eingabe dieselbe Auflistung, und wenn Populaten2 aufgerufen wird, wenn Knoten in die Auflistung eingefügt werden, erhalten Sie die zweite Set von Input in Output2.Ich sehe zwei wichtige Fragen, die die Antwort darauf bestimmen:
1. Sinnvolle Rendite
Wenn Sie etwas Sinnvolles zurückgeben können, geben Sie im Wesentlichen keine Ausnahme aus. Lassen Sie den Anrufer mit dem Ergebnis umgehen. Also, wenn Ihre Funktion ...
Im Allgemeinen sagt mir mein FP-Bias "etwas Sinnvolles zurückgeben" und Null kann in diesem Fall eine gültige Bedeutung haben.
2. Stil
Befürwortet Ihr allgemeiner Code (oder der Code des Projekts oder des Teams) einen funktionalen Stil? Wenn nein, werden Ausnahmen erwartet und behandelt. Wenn ja, können Sie einen Optionstyp zurückgeben . Bei einem Optionstyp geben Sie entweder eine aussagekräftige Antwort oder None / Nothing zurück . In meinem dritten Beispiel von oben wäre Nichts eine gute Antwort im FP-Stil. Die Tatsache, dass die Funktion einen Optionstyp zurückgibt, signalisiert dem Anrufer möglicherweise eindeutig, dass es sich um eine aussagekräftige Antwort handelt, und dass der Anrufer darauf vorbereitet sein sollte, damit umzugehen. Ich glaube, es gibt dem Anrufer mehr Möglichkeiten (wenn Sie das Wortspiel verzeihen).
F # ist , wo alle kühlen .Net Kinder diese Art der Sache zu tun , aber C # tut unterstützen diesen Stil.
tl; dr
Bewahren Sie Ausnahmen für unerwartete Fehler in Ihrem eigenen Codepfad auf, nicht vollständig vorhersehbare (und zulässige) Eingaben von Dritten.
quelle
Wie immer kommt es darauf an.
Ist es wichtig, dass die Sammlung leer ist?
Der meiste Code für die Sammlung würde wahrscheinlich "nein" sagen. Eine Sammlung kann eine beliebige Anzahl von Elementen enthalten, einschließlich Null.
Wenn Sie eine Art Sammlung haben, in der es "ungültig" ist, keine Elemente darin zu haben, dann ist dies eine neue Anforderung, und Sie müssen entscheiden, was Sie dagegen tun möchten.
Leihen Sie sich eine Testlogik aus der Datenbankwelt aus: Prüfen Sie , ob keine , eine und zwei Elemente vorhanden sind. Dies ist für die wichtigsten Fälle geeignet (Herauslösen schlecht geformter innerer oder kartesischer Verbindungsbedingungen).
quelle
java.util.Collection
, sondern eine benutzerdefiniertecom.foo.util.NonEmptyCollection
Klasse, die diese Invariante konsistent beibehält und verhindert, dass Sie zunächst in einen ungültigen Zustand gelangen.Akzeptieren Sie im Hinblick auf ein gutes Design so viele Variationen in Ihren Eingaben wie möglich. Ausnahmen sollten nur ausgelöst werden, wenn (bei der Verarbeitung werden nicht akzeptable Eingaben angezeigt ODER es treten unerwartete Fehler auf) UND das Programm daher nicht vorhersehbar fortgesetzt werden kann .
In diesem Fall ist zu erwarten, dass eine leere Auflistung angezeigt wird und Ihr Code damit umgehen muss (was er bereits tut). Es wäre eine Verletzung von allem, was gut ist, wenn Ihr Code hier eine Ausnahme auslösen würde. Dies wäre vergleichbar mit dem Multiplizieren von 0 mit 0 in der Mathematik. Es ist überflüssig, aber absolut notwendig, damit es so funktioniert, wie es funktioniert.
Nun zum Argument der Nullsammlung. In diesem Fall ist eine Nullsammlung ein Programmierfehler: Der Programmierer hat vergessen, eine Variable zuzuweisen. Dies ist ein Fall, in dem eine Ausnahme ausgelöst werden kann, da Sie diese nicht sinnvoll in eine Ausgabe verarbeiten können. Wenn Sie dies versuchen, tritt ein unerwartetes Verhalten auf. Dies wäre in der Mathematik gleichbedeutend mit einer Division durch Null - es ist völlig bedeutungslos.
quelle
Die richtige Lösung ist viel schwieriger zu erkennen, wenn Sie Ihre Funktion nur isoliert betrachten. Betrachten Sie Ihre Funktion als Teil eines größeren Problems . Eine mögliche Lösung für dieses Beispiel sieht folgendermaßen aus (in Scala):
Zuerst teilen Sie die Zeichenfolge nach Nicht-Ziffern auf, filtern die leeren Zeichenfolgen heraus, konvertieren die Zeichenfolgen in Ganzzahlen und filtern dann, um nur die vierstelligen Zahlen beizubehalten. Ihre Funktion könnte die
map (_.toInt)
in der Pipeline sein.Dieser Code ist ziemlich einfach, da jede Phase in der Pipeline nur eine leere Zeichenfolge oder eine leere Auflistung verarbeitet. Wenn Sie am Anfang eine leere Zeichenfolge eingeben, wird am Ende eine leere Liste ausgegeben. Sie müssen nicht
null
nach jedem Anruf anhalten und nach einer Ausnahme suchen.Dies setzt natürlich voraus, dass eine leere Ausgabeliste nicht mehr als eine Bedeutung hat. Wenn Sie zwischen einer leeren Ausgabe, die durch eine leere Eingabe verursacht wird, und einer durch die Transformation selbst verursachten Ausgabe unterscheiden müssen , ändert sich das grundlegend.
quelle
Bei dieser Frage handelt es sich eigentlich um Ausnahmen. Wenn Sie dies so betrachten und die leere Auflistung als Implementierungsdetail ignorieren, ist die Antwort einfach:
1) Eine Methode sollte eine Ausnahme auslösen, wenn sie nicht fortfahren kann: entweder die angegebene Aufgabe nicht ausführen oder den entsprechenden Wert zurückgeben.
2) Eine Methode sollte eine Ausnahme abfangen, wenn sie trotz des Fehlers fortfahren kann.
Daher sollte Ihre Hilfsmethode nicht "hilfreich" sein und eine Ausnahme auslösen, es sei denn, sie kann ihre Aufgabe nicht mit einer leeren Auflistung erledigen. Lassen Sie den Anrufer bestimmen, ob die Ergebnisse verarbeitet werden können.
Ob es eine leere Sammlung oder null zurückgibt, ist ein bisschen schwieriger, aber nicht viel: nullfähige Sammlungen sollten nach Möglichkeit vermieden werden. Der Zweck einer nullfähigen Auflistung besteht darin, (wie in SQL) anzugeben, dass Sie nicht über die Informationen verfügen. Eine Auflistung von untergeordneten Elementen kann beispielsweise null sein, wenn Sie nicht wissen, ob eine andere vorhanden ist, dies jedoch nicht wissen, dass sie es nicht tun. Aber wenn das aus irgendeinem Grund wichtig ist, ist es wahrscheinlich eine zusätzliche Variable wert, um es zu verfolgen.
quelle
Die Methode heißt
TransformNodes
. Im Falle einer leeren Sammlung als Eingabe ist es natürlich und intuitiv, eine leere Sammlung zurückzugewinnen, und dies ist mathematisch sinnvoll.Wenn die Methode so benannt
Max
und entworfen wurde, dass sie das maximale Element zurückgibt, wäre es natürlich,NoSuchElementException
eine leere Auflistung zu verwenden, da das Maximum von nichts keinen mathematischen Sinn ergibt.Wenn die Methode so benannt
JoinSqlColumnNames
und entworfen wurde, dass sie eine Zeichenfolge zurückgibt, bei der die Elemente durch ein Komma verbunden sind, um sie in SQL-Abfragen zu verwenden, wäre es sinnvoll,IllegalArgumentException
eine leere Auflistung zu verwenden, da der Aufrufer schließlich ohnehin einen SQL-Fehler erhalten würde, wenn er sie verwendet die Zeichenfolge in einer SQL-Abfrage direkt ohne weitere Überprüfungen, und anstatt auf eine zurückgegebene leere Zeichenfolge zu prüfen, hätte er stattdessen wirklich auf leere Auflistung prüfen müssen.quelle
Max
von nichts ist oft negative Unendlichkeit.Lassen Sie uns einen Schritt zurücktreten und ein anderes Beispiel verwenden, das das arithmetische Mittel eines Array von Werten berechnet.
Wenn das Eingabearray leer (oder null) ist, können Sie die Anforderung des Aufrufers vernünftigerweise erfüllen? Nein. Welche Möglichkeiten haben Sie? Nun, Sie könnten:
Ich sage, gib ihnen den Fehler, wenn sie dir eine ungültige Eingabe gegeben haben und die Anfrage nicht abgeschlossen werden kann. Ich meine einen schweren Fehler vom ersten Tag an, damit sie die Anforderungen Ihres Programms verstehen. Schließlich ist Ihre Funktion nicht in der Lage zu reagieren. Wenn die Operation fehlschlagen könnte (z. B. Kopieren einer Datei), sollte Ihre API einen Fehler melden, mit dem sie umgehen kann.
Auf diese Weise können Sie festlegen, wie Ihre Bibliothek fehlerhafte und möglicherweise fehlgeschlagene Anforderungen behandelt.
Es ist sehr wichtig, dass Ihr Code konsistent mit diesen Fehlerklassen umgeht.
In der nächsten Kategorie legen Sie fest, wie Ihre Bibliothek mit Unsinnanforderungen umgeht. Kommen wir zurück auf ein Beispiel ähnlich wie bei Ihnen - wir verwenden , um eine Funktion , die bestimmt , ob eine Datei auf einem Pfad vorhanden ist :
bool FileExistsAtPath(String)
. Wie gehen Sie mit diesem Szenario um, wenn der Client eine leere Zeichenfolge übergibt? Wie wäre es mit einem leeren oder einem Null-Array, an das übergeben wirdvoid SaveDocuments(Array<Document>)
? Entscheiden Sie sich für Ihre Bibliothek / Codebasis und seien Sie konsistent. Ich betrachte diese Fälle zufällig als Fehler und verbiete Kunden, unsinnige Anfragen zu stellen, indem ich sie (über eine Behauptung) als Fehler markiere. Einige Leute werden sich dieser Idee / Aktion stark widersetzen. Ich finde diese Fehlererkennung sehr hilfreich. Es ist sehr gut, um Probleme in Programmen zu lokalisieren - mit einer guten Lokalisierung des betreffenden Programms. Programme sind viel klarer und korrekter (unter Berücksichtigung der Weiterentwicklung Ihrer Codebasis) und brennen keine Zyklen innerhalb von Funktionen, die nichts bewirken. Auf diese Weise wird der Code kleiner / sauberer, und die Überprüfungen werden im Allgemeinen an die Stellen verschoben, an denen das Problem möglicherweise auftritt.quelle
if (!FileExists(path)) { ...create it!!!... }
Bugs gesehen - viele davon würden vor dem Commit gefangen werden, wenn die Linien der Korrektheit nicht verschwimmen würden.Als Faustregel gilt, dass eine Standardfunktion in der Lage sein sollte, die breiteste Liste von Eingaben zu akzeptieren und Feedback zu geben. Es gibt viele Beispiele, in denen Programmierer Funktionen auf eine Weise verwenden, die der Designer nicht geplant hatte. Aus diesem Grund glaube ich an die Funktion Sie sollten nicht nur leere Sammlungen, sondern eine Vielzahl von Eingabetypen akzeptieren und ordnungsgemäß Feedback geben können, auch wenn es sich um ein Fehlerobjekt handelt, das von einer beliebigen Operation an der Eingabe ausgeführt wurde.
quelle
writePaycheckToEmployee
sollte keine negative Zahl als Eingabe akzeptieren ... aber wenn "Rückmeldung" bedeutet "tut etwas, was als nächstes passieren könnte", dann tut ja jede Funktion etwas, was sie als nächstes tut.