Ich entwickle eine Bibliothek für die Veröffentlichung. Es enthält verschiedene Methoden zum Bearbeiten von Objektgruppen - Erzeugen, Untersuchen, Partitionieren und Projizieren der Gruppen in neue Formen. Für den Fall , es relevant ist, ist es eine C # -Klasse Bibliothek mit LINQ-style - Erweiterungen auf IEnumerable
, als NuGet Paket freigegeben werden.
Bei einigen Methoden in dieser Bibliothek können unbefriedigende Eingabeparameter angegeben werden. Beispielsweise gibt es in den kombinatorischen Methoden eine Methode zum Generieren aller Mengen von n Elementen, die aus einer Quellmenge von m Elementen erstellt werden können. Zum Beispiel angesichts des Satzes:
1, 2, 3, 4, 5
und nach Kombinationen von 2 zu fragen, würde ergeben:
1, 2
1, 3
1, 4
etc ...
5, 3
5, 4
Jetzt ist es natürlich möglich, nach etwas zu fragen, das nicht erledigt werden kann, z. B. 3 Elemente zuzuweisen und dann nach Kombinationen von 4 Elementen zu fragen, während die Option festgelegt wird, die besagt, dass jedes Element nur einmal verwendet werden kann.
In diesem Szenario ist jeder Parameter einzeln gültig:
- Die Quellensammlung ist nicht null und enthält Elemente
- Die angeforderte Größe von Kombinationen ist eine positive Ganzzahl ungleich Null
- Der angeforderte Modus (benutze jeden Gegenstand nur einmal) ist eine gültige Wahl
Der Zustand der Parameter zusammen verursacht jedoch Probleme.
Würden Sie in diesem Szenario erwarten, dass die Methode eine Ausnahme auslöst (z. B. InvalidOperationException
) oder eine leere Auflistung zurückgibt? Beides scheint mir gültig zu sein:
- Sie können keine Kombinationen von n Elementen aus einer Menge von m Elementen erzeugen , wobei n> m ist, wenn Sie jedes Element nur einmal verwenden dürfen, sodass diese Operation als unmöglich angesehen werden kann
InvalidOperationException
. - Die Menge von Kombinationen der Größe n , die aus m Elementen erzeugt werden können, wenn n> m eine leere Menge ist; Es können keine Kombinationen hergestellt werden.
Das Argument für eine leere Menge
Meine erste Sorge ist, dass eine Ausnahme die idiomatische Verkettung von Methoden im LINQ-Stil verhindert, wenn Sie mit Datensätzen arbeiten, deren Größe möglicherweise unbekannt ist. Mit anderen Worten, möchten Sie vielleicht so etwas tun:
var result = someInputSet
.CombinationsOf(4, CombinationsGenerationMode.Distinct)
.Select(combo => /* do some operation to a combination */)
.ToList();
Wenn Ihr Eingabesatz eine variable Größe hat, ist das Verhalten dieses Codes nicht vorhersehbar. Wenn .CombinationsOf()
bei someInputSet
weniger als 4 Elementen eine Ausnahme ausgelöst wird, schlägt dieser Code manchmal zur Laufzeit ohne vorherige Überprüfung fehl. Im obigen Beispiel ist diese Überprüfung trivial, aber wenn Sie sie auf halber Strecke einer längeren LINQ-Kette aufrufen, wird dies möglicherweise mühsam. Wenn ein leerer Satz zurückgegeben wird, ist result
dieser leer, womit Sie möglicherweise vollkommen zufrieden sind.
Das Argument für eine Ausnahme
Meine zweite Sorge ist, dass das Zurückgeben einer leeren Menge Probleme verbergen könnte. Wenn Sie diese Methode in der Mitte einer LINQ-Kette aufrufen und eine leere Menge stillschweigend zurückgeben, kann es sein, dass Sie einige Schritte später auf Probleme stoßen oder eine leere finden Ergebnismenge, und es kann nicht offensichtlich sein, wie das passiert ist, vorausgesetzt, Sie hatten definitiv etwas in der Eingabemenge.
Was würden Sie erwarten und was ist Ihr Argument dafür?
quelle
Antworten:
Geben Sie ein leeres Set zurück
Ich würde eine leere Menge erwarten, weil:
Es gibt 0 Kombinationen von 4 Zahlen aus dem Satz von 3, wenn ich jede Zahl nur einmal verwenden kann
quelle
Fragen Sie im Zweifelsfall einen anderen.
Ihr Beispiel Funktion hat einen sehr ähnlichen einen in Python:
itertools.combinations
. Mal sehen, wie es funktioniert:Und es fühlt sich für mich vollkommen gut an. Ich hatte ein Ergebnis erwartet, über das ich noch einmal iterieren konnte und das ich bekam.
Aber natürlich, wenn Sie etwas Dummes fragen sollten:
Also würde ich sagen, wenn alle Ihre Parameter validieren, aber das Ergebnis eine leere Menge ist, geben Sie eine leere Menge zurück, dann sind Sie nicht der einzige, der dies tut.
Wie von @Bakuriu in den Kommentaren gesagt , gilt dies auch für eine
SQL
Abfrage wieSELECT <columns> FROM <table> WHERE <conditions>
. Solange<columns>
,<table>
,<conditions>
sind den bestehenden Namen gebildet und beziehen sich gebildet, können Sie eine Reihe von Bedingungen erstellen , die sich gegenseitig ausschließen. Die resultierende Abfrage würde nur keine Zeilen ergeben, anstatt ein zu werfenInvalidConditionsError
.quelle
n
Elemente in einer Auflistung, um zu ermitteln, wie oft wir sie auswählen können. Wenn irgendwann beim Auswählen keine Elemente mehr vorhanden sind, gibt es keine Möglichkeit (0),n
Elemente aus dieser Sammlung auszuwählen.1.0 / 0
, Python nicht.SELECT
Abfrage über eine Tabelle eine Ausnahme, wenn die Ergebnismenge leer ist? (Spoiler: nein!). Die "Wohlgeformtheit" einer Abfrage hängt nur von der Abfrage selbst und einigen Metadaten ab (zB existiert die Tabelle und hat dieses Feld (mit diesem Typ)?). Sobald die Abfrage wohlgeformt ist und Sie sie ausführen, erwarten Sie entweder a (möglicherweise leer) gültiges Ergebnis oder eine Ausnahme, weil der "Server" ein Problem hatte (z. B. Datenbankserver ist ausgefallen oder so).In den Begriffen des Laien:
quelle
throw
ein leeres Set zurücksenden sollte oder ...Ich stimme der Antwort von Ewan zu , möchte aber eine spezifische Begründung hinzufügen.
Sie haben es mit mathematischen Operationen zu tun, daher ist es möglicherweise ein guter Rat, sich an dieselben mathematischen Definitionen zu halten. Aus mathematischer Sicht ist die Anzahl der r- Mengen einer n- Menge (dh nCr ) für alle r> n> = 0 gut definiert. Sie ist Null. Daher wäre die Rückgabe einer leeren Menge aus mathematischer Sicht der erwartete Fall.
quelle
Ich finde eine gute Möglichkeit, um festzustellen, ob eine Ausnahme verwendet werden soll, indem ich mir vorstelle, dass Personen an der Transaktion beteiligt sind.
Das Abrufen des Inhalts einer Datei als Beispiel:
Bitte holen Sie mir den Inhalt der Datei "existiert nicht.txt"
ein. "Hier ist der Inhalt: eine leere Sammlung von Zeichen"
b. "Ähm, es gibt ein Problem, diese Datei existiert nicht. Ich weiß nicht, was ich tun soll!"
Bitte holen Sie mir den Inhalt der Datei, "existiert aber ist leer.txt"
ein. "Hier ist der Inhalt: eine leere Sammlung von Zeichen"
b. "Ähm, es gibt ein Problem, in dieser Akte ist nichts. Ich weiß nicht, was ich tun soll!"
Zweifellos werden einige anderer Meinung sein, aber für die meisten Leute ist "Ähm, es gibt ein Problem" sinnvoll, wenn die Datei nicht existiert, und "eine leere Sammlung von Zeichen" zurückzugeben, wenn die Datei leer ist.
Wenden Sie also den gleichen Ansatz auf Ihr Beispiel an:
Bitte geben Sie mir alle Kombinationen von 4 Artikeln für
{1, 2, 3}
ein. Es gibt keine, hier ist ein leeres Set.
b. Es gibt ein Problem, ich weiß nicht, was ich tun soll.
Auch hier wäre "Es gibt ein Problem" sinnvoll, wenn z. B.
null
als Artikelmenge angeboten würde , aber "Hier ist eine leere Menge" scheint eine sinnvolle Antwort auf die obige Anfrage zu sein.Wenn die Rückgabe eines leeren Werts ein Problem maskiert (z. B. eine fehlende Datei, a
null
), sollte stattdessen im Allgemeinen eine Ausnahme verwendet werden (sofern die von Ihnen ausgewählte Sprache keineoption/maybe
Typen unterstützt , sind diese manchmal sinnvoller). Andernfalls wird die Rücksendung eines leeren Werts wahrscheinlich die Kosten vereinfachen und dem Grundsatz des geringsten Erstaunens besser entsprechen.quelle
Da es sich um eine Allzweckbibliothek handelt, würde mein Instinkt lauten: Lassen Sie den Endbenutzer wählen.
Ähnlich wie wir es haben
Parse()
undTryParse()
uns zur Verfügung stehen, können wir die Option verwenden, die davon abhängt, welche Ausgabe wir von der Funktion benötigen. Sie würden weniger Zeit damit verbringen, einen Funktionswrapper zu schreiben und zu warten, um die Ausnahme auszulösen, als sich über die Auswahl einer einzelnen Version der Funktion zu streiten.quelle
Single()
undSingleOrDefault()
gefedert sofort in den Sinn. Single löst eine Ausnahme aus, wenn das Ergebnis Null oder> 1 ist, während SingleOrDefault keine Auslösung durchführt und stattdessen zurückgibtdefault(T)
. Vielleicht könnte OPCombinationsOf()
und verwendenCombinationsOfOrThrow()
.Single
undSingleOrDefault
(oderFirst
undFirstOrDefault
) sind eine ganz andere Geschichte, @RubberDuck. Ich greife mein Beispiel aus meinem vorherigen Kommentar auf. Es ist völlig in Ordnung, keine der Fragen zu beantworten: "Welche Primzahlen gibt es überhaupt, die größer als zwei sind?" , da dies eine mathematisch fundierte und vernünftige Antwort ist. Wie auch immer, wenn Sie fragen "Was ist die erste Primzahl größer als zwei?" Es gibt keine natürliche Antwort. Sie können die Frage einfach nicht beantworten, weil Sie nach einer einzelnen Nummer fragen. Es ist nicht none (es ist keine einzelne Zahl, sondern eine Menge), nicht 0. Daher werfen wir eine Ausnahme.Sie müssen die beim Aufruf Ihrer Funktion angegebenen Argumente validieren. Tatsächlich möchten Sie wissen, wie Sie mit ungültigen Argumenten umgehen. Die Tatsache, dass mehrere Argumente voneinander abhängen, macht nicht die Tatsache wett, dass Sie die Argumente validieren.
Daher würde ich für die ArgumentException stimmen, die dem Benutzer die erforderlichen Informationen liefert, um zu verstehen, was schief gelaufen ist.
Überprüfen Sie beispielsweise die
public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)
Funktion in Linq. Wodurch eine ArgumentOutOfRangeException ausgelöst wird, wenn der Index kleiner als 0 oder größer als oder gleich der Anzahl der Elemente in der Quelle ist. Somit wird der Index in Bezug auf die vom Aufrufer bereitgestellte Aufzählung validiert.quelle
new[] { 1, 2, 3 }.Skip(4).ToList();
einen leeren Satz erhalten, ist die Rückgabe eines leeren Satzes meines Erachtens möglicherweise die bessere Option.Sie sollten einen der folgenden Schritte ausführen (wobei Sie weiterhin grundlegende Probleme wie eine negative Anzahl von Kombinationen konsequent angehen sollten):
Stellen Sie zwei Implementierungen bereit, eine, die eine leere Menge zurückgibt, wenn die Eingaben zusammen unsinnig sind, und eine, die ausgelöst wird. Rufen Sie sie an
CombinationsOf
undCombinationsOfWithInputCheck
. Oder was auch immer du magst. Sie können dies umkehren, sodass die Eingabeprüfung der kürzere Name und die Liste istCombinationsOfAllowInconsistentParameters
.Geben Sie bei Linq-Methoden das leere Feld
IEnumerable
genau an der von Ihnen angegebenen Prämisse zurück. Fügen Sie Ihrer Bibliothek dann die folgenden Linq-Methoden hinzu:Zum Schluss benutze das so:
oder so:
Beachten Sie, dass die private Methode von der öffentlichen Methode verschieden sein muss, damit das Auslöse- oder Aktionsverhalten beim Erstellen der Linq-Kette auftritt, anstatt einige Zeit später, wenn sie aufgelistet wird. Sie möchten, dass es sofort geworfen wird.
Beachten Sie jedoch, dass mindestens das erste Element aufgezählt werden muss, um festzustellen, ob Elemente vorhanden sind. Dies ist ein möglicher Nachteil , dass ich denke vor allem durch die Tatsache , dass alle zukünftigen Zuschauer können ganz leicht folgern , dass ein gemildert
ThrowIfEmpty
Verfahren hat aufzuzählen mindestens ein Element, so sollten nicht durch so tun , überrascht sein. Aber du weißt nie. Sie könnten dies expliziter machenThrowIfEmptyByEnumeratingAndReEmittingFirstItem
. Aber das scheint ein gigantischer Overkill zu sein.Ich denke, # 2 ist ziemlich gut fantastisch! Jetzt liegt die Kraft im aufrufenden Code, und der nächste Leser des Codes versteht genau, was er tut, und muss sich nicht mit unerwarteten Ausnahmen befassen.
quelle
Ich sehe Argumente für beide Anwendungsfälle - eine Ausnahme ist großartig, wenn der Downstream-Code Mengen erwartet, die Daten enthalten. Auf der anderen Seite ist einfach ein leerer Satz großartig, wenn dies erwartet wird.
Ich denke, es hängt von den Erwartungen des Anrufers ab, ob dies ein Fehler oder ein akzeptables Ergebnis ist - daher würde ich die Auswahl an den Anrufer übertragen. Vielleicht eine Option vorstellen?
.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)
quelle
Es gibt zwei Ansätze, um zu entscheiden, ob es keine offensichtliche Antwort gibt:
Schreiben Sie den Code mit der Annahme, dass zuerst eine Option, dann die andere Option ausgewählt wird. Überlegen Sie, welches in der Praxis am besten funktionieren würde.
Fügen Sie einen "strengen" booleschen Parameter hinzu, um anzugeben, ob die Parameter streng überprüft werden sollen oder nicht. Beispielsweise hat Java
SimpleDateFormat
einesetLenient
Methode, um zu versuchen, Eingaben zu analysieren, die nicht vollständig mit dem Format übereinstimmen. Natürlich müssten Sie sich für die Standardeinstellung entscheiden.quelle
Basierend auf Ihrer eigenen Analyse scheint die Rücksendung des leeren Sets eindeutig richtig zu sein - Sie haben es sogar als etwas identifiziert, das einige Benutzer möglicherweise tatsächlich möchten, und sind nicht in die Falle gegangen, eine Nutzung zu verbieten, da Sie sich nicht vorstellen können, dass Benutzer es jemals verwenden möchten dieser Weg.
Wenn Sie wirklich der Meinung sind, dass einige Benutzer nicht leere Rückgaben erzwingen möchten, geben Sie ihnen die Möglichkeit , nach diesem Verhalten zu fragen , anstatt es allen aufzuzwingen. Zum Beispiel könnten Sie:
AssertNonempty
Scheck, den sie in ihre Ketten legen können.quelle
Es hängt wirklich davon ab, was Ihre Benutzer erwarten. Wenn Ihr Code beispielsweise eine Division durchführt, können Sie entweder eine Ausnahme auslösen oder einen Rückgabewert angeben
Inf
oderNaN
durch Null dividieren. Weder ist richtig noch falsch:Inf
in eine Python-Bibliothek zurückkehren, werden Sie angegriffen, weil Sie Fehler versteckt habenIn Ihrem Fall würde ich die Lösung auswählen, die für Endbenutzer am wenigsten erstaunlich ist. Da Sie eine Bibliothek entwickeln, die sich mit Mengen befasst, scheint eine leere Menge etwas zu sein, womit sich Ihre Benutzer befassen würden. Es klingt also nach einer vernünftigen Vorgehensweise, wenn Sie sie zurückgeben. Aber ich kann mich irren: Sie haben ein viel besseres Verständnis des Kontexts als alle anderen hier. Wenn Sie also erwarten, dass Ihre Benutzer darauf vertrauen, dass der Satz immer nicht leer ist, sollten Sie sofort eine Ausnahme auslösen.
Lösungen, bei denen der Benutzer wählen kann (wie das Hinzufügen eines "strengen" Parameters), sind nicht endgültig, da sie die ursprüngliche Frage durch eine neue äquivalente Frage ersetzen: "Welcher Wert
strict
sollte der Standardwert sein?"quelle
In der Mathematik ist es üblich, dass Sie bei der Auswahl von Elementen über einer Menge kein Element finden und daher eine leere Menge erhalten . Natürlich muss man mit der Mathematik übereinstimmen, wenn man diesen Weg geht:
Gemeinsame Regeln:
Ihre Frage ist sehr subtil:
Möglicherweise muss bei der Eingabe Ihrer Funktion ein Vertrag eingehalten werden : In diesem Fall sollte eine ungültige Eingabe eine Ausnahme auslösen. Das ist es, die Funktion arbeitet nicht mit regulären Parametern.
Möglicherweise muss sich die Eingabe Ihrer Funktion genau wie eine Menge verhalten und sollte daher in der Lage sein, eine leere Menge zurückzugeben.
Wenn ich jetzt in dir wäre, würde ich den "Set" Weg gehen, aber mit einem großen "ABER".
Angenommen, Sie haben eine Sammlung, die "durch Hypotesis" nur weibliche Studenten haben soll:
Jetzt ist Ihre Sammlung kein "reines Set" mehr, da Sie einen Vertrag haben und daher Ihren Vertrag mit einer Ausnahme durchsetzen sollten.
Wenn Sie Ihre "Mengen" -Funktionen auf reine Weise verwenden, sollten Sie im Fall einer leeren Menge keine Ausnahmen auslösen . Wenn Sie jedoch Sammlungen haben, die keine "reinen Mengen" mehr sind, sollten Sie Ausnahmen auslösen, wo dies angemessen ist .
Sie sollten immer das tun, was sich natürlicher und konsequenter anfühlt: Für mich sollte eine Menge Regeln einhalten, während Dinge, die keine Mengen sind, ihre eigenen Regeln haben sollten.
In Ihrem Fall scheint es eine gute Idee zu sein:
Aber es ist keine gute Idee, wir haben jetzt einen ungültigen Zustand, der zur Laufzeit in zufälligen Momenten auftreten kann. Dies ist jedoch im Problem enthalten, so dass wir ihn nicht entfernen können. Sicher können wir die Ausnahme innerhalb einer Dienstprogrammmethode verschieben (genau das ist der Fall) derselbe Code, verschoben an verschiedenen Stellen), aber das ist falsch und das Beste, was Sie tun können, ist, sich an reguläre Regeln zu halten .
Das Hinzufügen neuer Komplexität, nur um zu zeigen, dass Sie linq-Abfragemethoden schreiben können, scheint für Ihr Problem keinen Wert zu haben. Ich bin mir ziemlich sicher, dass wir, wenn OP uns mehr über die Domäne erzählen kann, wahrscheinlich die Stelle finden könnten, an der die Ausnahme wirklich benötigt wird (wenn Möglicherweise erfordert das Problem überhaupt keine Ausnahme.
quelle
FemaleClass
RUFEN können, es sei denn, es verfügt nur über Frauen (es kann nicht öffentlich erstellt werden, nur die Builder-Klasse kann eine Instanz verteilen) und möglicherweise mindestens eine Frau . Dann muss Ihr Code, derFemaleClass
als Argument verwendet wird, keine Ausnahmen behandeln, da sich das Objekt im falschen Zustand befindet.