Ausnahme gegen leere Ergebnismenge, wenn die Eingaben technisch gültig, aber nicht zufriedenstellend sind

108

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 someInputSetweniger 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 resultdieser 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?

Anaximander
quelle
66
Da die leere Menge mathematisch korrekt ist, ist die Wahrscheinlichkeit groß, dass Sie sie tatsächlich haben möchten, wenn Sie sie erhalten. Mathematische Definitionen und Konventionen werden im Allgemeinen aus Gründen der Konsistenz und Zweckmäßigkeit ausgewählt, damit die Dinge mit ihnen klappen.
Asmeurer
5
@asmeurer Sie werden so gewählt, dass die Sätze konsistent und praktisch sind. Sie werden nicht ausgewählt, um die Programmierung zu vereinfachen. (Das ist manchmal ein Nebeneffekt, aber manchmal erschwert es auch das Programmieren.)
jpmc26
7
@ jpmc26 "Sie werden so ausgewählt, dass die Theoreme konsistent und praktisch sind" - sicherzustellen, dass Ihr Programm immer wie erwartet funktioniert, entspricht im Wesentlichen dem Beweisen eines Theorems.
Artem
2
@ jpmc26 Ich verstehe nicht, warum du die funktionale Programmierung erwähnt hast. Der Nachweis der Korrektheit für imperative Programme ist durchaus möglich, immer von Vorteil und kann auch informell erfolgen. Denken Sie nur ein wenig nach und verwenden Sie beim Schreiben Ihres Programms den gesunden Menschenverstand, und Sie werden weniger Zeit für Tests aufwenden. Statistisch belegt anhand eines Musters ;-)
Artem
5
@DmitryGrigoryev 1/0 als undefiniert ist mathematisch viel korrekter als 1/0 als unendlich.
Asmeurer

Antworten:

145

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

Ewan
quelle
5
Mathematisch ja, aber es ist auch sehr wahrscheinlich eine Fehlerquelle. Wenn diese Art der Eingabe erwartet wird, ist es möglicherweise besser, den Benutzer zu verpflichten, die Ausnahme in einer Universalbibliothek abzufangen.
Casey Kuball
56
Ich bin nicht einverstanden, dass es eine "sehr wahrscheinliche" Fehlerursache ist. Angenommen, Sie implementieren beispielsweise einen naiven "Matchmaknig" -Algorithmus aus einem umfangreichen Eingabesatz. Sie können nach allen Kombinationen von zwei Elementen fragen, die "beste" Übereinstimmung zwischen ihnen finden, dann beide Elemente entfernen und mit dem neuen, kleineren Satz von vorne beginnen. Irgendwann ist Ihr Set leer und es sind keine Paare mehr zu berücksichtigen: Eine Ausnahme ist an dieser Stelle hinderlich. Ich denke, es gibt viele Möglichkeiten, in eine ähnliche Situation zu geraten.
Amalloy
11
Eigentlich wissen Sie nicht, ob dies ein Fehler in den Augen des Benutzers ist. Das leere Set sollte der Benutzer bei Bedarf überprüfen. if (result.Any ()) DoSomething (result.First ()); sonst DoSomethingElse (); ist viel besser als {result.first (). dosomething ();} catch {DoSomethingElse ();}
Guran
4
@TripeHound Das war der Auslöser für die Entscheidung: Wenn der Entwickler diese Methode zum Überprüfen und anschließenden Auslösen verwenden muss, hat dies eine viel geringere Auswirkung als das Auslösen einer Ausnahme, die er in Bezug auf Entwicklungsaufwand, Programmleistung und Leistung nicht wünscht Einfachheit des Programmablaufs.
Anaximander
6
@Darthfett Vergleichen Sie dies mit einer vorhandenen LINQ-Erweiterungsmethode: Where (). Wenn ich eine Klausel habe: Wobei (x => 1 == 2) ich keine Ausnahme erhalte, erhalte ich eine leere Menge.
Necoras
79

Fragen Sie im Zweifelsfall einen anderen.

Ihr Beispiel Funktion hat einen sehr ähnlichen einen in Python: itertools.combinations. Mal sehen, wie es funktioniert:

>>> import itertools
>>> input = [1, 2, 3, 4, 5]
>>> list(itertools.combinations(input, 2))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
>>> list(itertools.combinations(input, 5))
[(1, 2, 3, 4, 5)]
>>> list(itertools.combinations(input, 6))
[]

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:

>>> list(itertools.combinations(input, -1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: r must be non-negative

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 SQLAbfrage wie SELECT <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 werfen InvalidConditionsError.

Mathias Ettinger
quelle
4
Heruntergestuft, weil es im C # / Linq-Sprachraum nicht idiomatisch ist (siehe die Antwort von sbecker, um zu erfahren, wie ähnliche Probleme außerhalb der Grenzen in der Sprache behandelt werden). Wenn dies eine Python-Frage wäre, wäre es eine +1 von mir.
James Snell
32
@ JamesSnell Ich verstehe kaum, wie es mit dem Out-of-Bound-Problem zusammenhängt. Wir wählen keine Elemente nach Indizes aus, um sie neu anzuordnen, sondern nElemente 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), nElemente aus dieser Sammlung auszuwählen.
Mathias Ettinger
1
Dennoch ist Python kein gutes Orakel für C # -Fragen: Zum Beispiel würde C # zulassen 1.0 / 0, Python nicht.
Dmitry Grigoryev
2
@MathiasEttinger Pythons Richtlinien für die Verwendung von Ausnahmen unterscheiden sich stark von denen in C #. Daher ist die Verwendung des Verhaltens von Python-Modulen als Richtlinie keine gute Metrik für C #.
Ant P
2
Anstatt Python zu wählen, könnten wir einfach SQL wählen. Erzeugt eine wohlgeformte 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).
Bakuriu
72

In den Begriffen des Laien:

  • Wenn ein Fehler auftritt, sollten Sie eine Ausnahme auslösen. Dies kann dazu führen, dass Schritte anstelle eines einzigen verketteten Aufrufs ausgeführt werden, um genau zu wissen, wo der Fehler aufgetreten ist.
  • Wenn es keinen Fehler gibt, die resultierende Menge jedoch leer ist, lösen Sie keine Ausnahme aus, und geben Sie die leere Menge zurück. Ein leerer Satz ist ein gültiger Satz.
Tulains Córdova
quelle
23
+1, 0 ist eine absolut gültige und in der Tat äußerst wichtige Zahl. Es gibt so viele Fälle, in denen eine Abfrage zu Recht keine Treffer zurückgibt, dass das Auslösen einer Ausnahme äußerst ärgerlich wäre.
Kilian Foth
19
guter Rat, aber ist Ihre Antwort klar?
Ewan
54
Ich bin mir immer noch nicht sicher, ob diese Antwort darauf hindeutet, dass das OP throwein leeres Set zurücksenden sollte oder ...
James Snell
7
In Ihrer Antwort steht, dass ich das auch tun könnte, je nachdem, ob ein Fehler vorliegt, aber Sie erwähnen nicht, ob Sie das Beispielszenario als Fehler betrachten würden. In dem obigen Kommentar sagen Sie, ich solle ein leeres Set zurückgeben, "wenn es keine Probleme gibt", aber auch hier könnten unbefriedigende Bedingungen als Problem angesehen werden, und Sie sagen weiter, dass ich keine Ausnahmen aufwerfe, wenn ich sollte. Also was soll ich tun?
Anaximander
3
Ah, ich verstehe - Sie können falsch verstanden haben; Ich verkette nichts, ich schreibe eine Methode, von der ich erwarte, dass sie in einer Kette verwendet wird. Ich frage mich, ob der hypothetische zukünftige Entwickler, der diese Methode verwendet, möchte, dass sie das obige Szenario ausführt.
Anaximander
53

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.

Sigy
quelle
9
Das ist ein guter Punkt. Wenn die Bibliothek eine höhere Ebene aufweist, z. B. das Auswählen von Farbkombinationen, um eine Palette zu erstellen, ist es sinnvoll, einen Fehler zu werfen. Weil Sie wissen , eine Palette ohne Farben ist keine Palette. Aber eine Menge ohne Einträge ist immer noch eine Menge, und die Mathematik definiert sie als eine leere Menge.
Captain Man
1
Viel besser als Ewans Antwort, weil es die mathematische Praxis zitiert. +1
user949300
24

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:

  1. 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!"

  2. 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:

  1. 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. nullals 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 keine option/maybeTypen 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.

David Arno
quelle
4
Dieser Rat ist gut, gilt aber nicht für diesen Fall. Ein besseres Beispiel: Wie viele Tage nach dem 30. Januar ?: 1 Wie viele Tage nach dem 30. Februar ?: 0 Wie viele Tage nach dem 30. Februar ?: Ausnahme: Wie viele Arbeitsstunden am 30. Januar? : th Februar: Ausnahme
Guran
9

Da es sich um eine Allzweckbibliothek handelt, würde mein Instinkt lauten: Lassen Sie den Endbenutzer wählen.

Ähnlich wie wir es haben Parse()und TryParse()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.

James Snell
quelle
3
+1 weil ich die Idee der Wahl immer mag und weil dieses Muster dazu neigt, Programmierer zu ermutigen, ihre eigenen Eingaben zu validieren. Das bloße Vorhandensein einer TryFoo-Funktion macht deutlich, dass bestimmte Eingabekombinationen Probleme verursachen können. Wenn in der Dokumentation die potenziellen Probleme erläutert werden, kann der Codierer diese überprüfen und ungültige Eingaben auf eine sauberere Art und Weise behandeln, als dies durch das Abfangen einer Ausnahme möglich wäre oder auf eine leere Gruppe antworten. Oder sie können faul sein. In jedem Fall liegt die Entscheidung bei ihnen.
Aleppke
19
Nein, eine leere Menge ist die mathematisch korrekte Antwort. Etwas anderes zu tun bedeutet, fest vereinbarte Mathematik neu zu definieren, was nicht aus einer Laune heraus geschehen sollte. Sollen wir die Exponentierungsfunktion neu definieren, um einen Fehler zu werfen, wenn der Exponent gleich Null ist, weil wir die Konvention nicht mögen, dass eine zur "nullten" Potenz erhobene Zahl gleich 1 ist?
Wildcard
1
Ich wollte etwas Ähnliches beantworten. Die Linq - Erweiterungsmethoden Single()und SingleOrDefault()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ückgibt default(T). Vielleicht könnte OP CombinationsOf()und verwenden CombinationsOfOrThrow().
RubberDuck
1
@Wildcard - Sie sprechen von zwei unterschiedlichen Ergebnissen für denselben Eingang, was nicht dasselbe ist. Das OP ist nur daran interessiert, a) das Ergebnis auszuwählen oder b) eine Ausnahme auszulösen, die kein Ergebnis ist.
James Snell
4
Singleund SingleOrDefault(oder Firstund FirstOrDefault) 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.
Paul Kertscher
4

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.

sbecker
quelle
3
+1 für das Zitieren eines Beispiels für eine ähnliche Situation in der Sprache.
James Snell
13
Seltsamerweise brachte Ihr Beispiel mich zum Nachdenken und führte mich zu etwas, von dem ich denke, dass es ein starkes Gegenbeispiel ist. Wie ich bereits erwähnt habe, ist diese Methode so konzipiert, dass sie LINQ-ähnlich ist, sodass Benutzer sie mit anderen LINQ-Methoden verketten können. Wenn Sie 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.
Anaximander
14
Hierbei handelt es sich nicht um eine Frage der Sprachsemantik. Die Semantik der Problemdomäne liefert bereits die richtige Antwort, dh die Rückgabe des leeren Satzes. Alles andere zu tun, verstößt gegen das Prinzip des geringsten Erstaunens für jemanden, der im Bereich der kombinatorischen Probleme arbeitet.
Ukko
1
Ich fange an, die Argumente für die Rückgabe einer leeren Menge zu verstehen. Warum würde es Sinn machen. Zumindest für dieses Beispiel.
sbecker
2
@sbecker, um den Punkt nach Hause zu bringen: Wenn die Frage "Wie viele Kombinationen gibt es von ..." wäre, dann wäre "Null" eine völlig gültige Antwort. Aus dem gleichen Grund hat "Was sind die Kombinationen, so dass ..." die leere Menge als eine vollständig gültige Antwort. Wenn die Frage "Was ist die erste Kombination, die ..." wäre, dann und nur dann wäre eine Ausnahme angebracht, da die Frage nicht beantwortbar ist. Siehe auch Paul K Kommentar .
Wildcard
3

Sie sollten einen der folgenden Schritte ausführen (wobei Sie weiterhin grundlegende Probleme wie eine negative Anzahl von Kombinationen konsequent angehen sollten):

  1. 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 CombinationsOfund CombinationsOfWithInputCheck. Oder was auch immer du magst. Sie können dies umkehren, sodass die Eingabeprüfung der kürzere Name und die Liste ist CombinationsOfAllowInconsistentParameters.

  2. Geben Sie bei Linq-Methoden das leere Feld IEnumerablegenau an der von Ihnen angegebenen Prämisse zurück. Fügen Sie Ihrer Bibliothek dann die folgenden Linq-Methoden hinzu:

    public static class EnumerableExtensions {
       public static IEnumerable<T> ThrowIfEmpty<T>(this IEnumerable<T> input) {
          return input.IfEmpty<T>(() => {
             throw new InvalidOperationException("An enumerable was unexpectedly empty");
          });
       }
    
       public static IEnumerable<T> IfEmpty<T>(
          this IEnumerable<T> input,
          Action callbackIfEmpty
       ) {
          var enumerator = input.GetEnumerator();
          if (!enumerator.MoveNext()) {
             // Safe because if this throws, we'll never run the return statement below
             callbackIfEmpty();
          }
          return EnumeratePrimedEnumerator(enumerator);
       }
    
       private static IEnumerable<T> EnumeratePrimedEnumerator<T>(
          IEnumerator<T> primedEnumerator
       ) {
          yield return primedEnumerator.Current;
          while (primedEnumerator.MoveNext()) {
             yield return primedEnumerator.Current;
          }
       }
    }

    Zum Schluss benutze das so:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .ThrowIfEmpty()
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    oder so:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .IfEmpty(() => _log.Warning(
          $@"Unexpectedly received no results when creating combinations for {
             nameof(someInputSet)}"
       ))
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    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 ThrowIfEmptyVerfahren hat aufzuzählen mindestens ein Element, so sollten nicht durch so tun , überrascht sein. Aber du weißt nie. Sie könnten dies expliziter machen ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. 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.

ErikE
quelle
Bitte erläutern Sie, was Sie meinen. Wie verhält sich etwas in meinem Beitrag "nicht wie ein Set" und warum ist das eine schlechte Sache?
ErikE
Sie tun im Grunde das Gleiche wie meine Antwort. Anstatt die Abfrage, die Sie für eine If-Bedingung durchgeführt haben, umzubrechen, ändert sich das Ergebnis nicht uu
GameDeveloper
Ich kann nicht sehen, wie meine Antwort "im Grunde das Gleiche wie Ihre Antwort" ist. Sie erscheinen mir völlig anders.
ErikE
Grundsätzlich erzwingen Sie nur die gleiche Bedingung von "meine schlechte Klasse". Aber das sagst du nicht ^^. Stimmen Sie zu, dass Sie die Existenz von 1 Element im Abfrageergebnis erzwingen?
GameDeveloper
Sie müssen klarer für die offensichtlich dummen Programmierer sprechen, die immer noch nicht verstehen, wovon Sie sprechen. Welche Klasse ist schlecht? Warum ist es schlecht? Ich erzwinge nichts. Ich erlaube dem BENUTZER der Klasse, das endgültige Verhalten anstelle des ERSTELLERS der Klasse zu bestimmen. Dies ermöglicht die Verwendung eines konsistenten Codierungsparadigmas, bei dem Linq-Methoden aufgrund eines Rechenaspekts, der nicht wirklich gegen normale Regeln verstößt, nicht zufällig werfen, z. B. wenn Sie jeweils nach -1 Elementen gefragt haben.
ErikE
2

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)

Christian Sauer
quelle
10
Ich versuche zu vermeiden, dass Methoden mit vielen übergebenen Optionen ihr Verhalten ändern. Ich bin immer noch nicht 100% zufrieden mit der bereits vorhandenen Modus-Aufzählung. Die Idee eines Optionsparameters, der das Ausnahmeverhalten einer Methode ändert, gefällt mir wirklich nicht. Ich habe das Gefühl, wenn eine Methode eine Ausnahme auslösen muss, muss sie ausgelöst werden. Wenn Sie diese Ausnahme ausblenden möchten, ist dies Ihre Entscheidung als aufrufender Code und nicht etwas, was Sie mit der richtigen Eingabeoption tun können.
Anaximander
Wenn der Anrufer einen Satz erwartet, der nicht leer ist, muss der Anrufer entweder einen leeren Satz behandeln oder eine Ausnahme auslösen. Für den Angerufenen ist ein leeres Set eine perfekte Antwort. Und Sie können mehr als einen Anrufer haben, der unterschiedliche Meinungen dazu hat, ob leere Sätze in Ordnung sind oder nicht.
gnasher729
2

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 SimpleDateFormateine setLenientMethode, 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.

JollyJoker
quelle
2

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:

  • Machen Sie es zu einer Konfigurationsoption für das Objekt, das die Aktion für den Benutzer ausführt.
  • Machen Sie es zu einem Flag, das der Benutzer optional an die Funktion übergeben kann.
  • Geben Sie einen AssertNonemptyScheck, den sie in ihre Ketten legen können.
  • Machen Sie zwei Funktionen, eine, die nicht leer ist und eine, die nicht leer ist.

quelle
dies scheint nicht alles zu bieten erhebliche über Punkte gemacht und erläutert vor 12 Antworten
gnat
1

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 Infoder NaNdurch Null dividieren. Weder ist richtig noch falsch:

  • Wenn Sie Infin eine Python-Bibliothek zurückkehren, werden Sie angegriffen, weil Sie Fehler versteckt haben
  • Wenn Sie einen Fehler in einer Matlab-Bibliothek auslösen, werden Sie angegriffen, weil Sie Daten mit fehlenden Werten nicht verarbeiten können

In 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 strictsollte der Standardwert sein?"

Dmitry Grigoryev
quelle
2
Ich habe darüber nachgedacht, das NaN-Argument in meiner Antwort zu verwenden, und teile Ihre Meinung. Wir können nicht mehr erzählen, ohne die Domain des OP
GameDeveloper
0

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:

  • Set.Foreach (Prädikat); // gibt für leere Mengen immer true zurück
  • Set.Exists (Prädikat); // gibt für leere Mengen immer false zurück

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:

class FemaleClass{

    FemaleStudent GetAnyFemale(){
        var femaleSet= mySet.Select( x=> x.IsFemale());
        if(femaleSet.IsEmpty())
            throw new Exception("No female students");
        else
            return femaleSet.Any();
    }
}

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:

List SomeMethod( Set someInputSet){
    var result = someInputSet
        .CombinationsOf(4, CombinationsGenerationMode.Distinct)
        .Select(combo => /* do some operation to a combination */)
        .ToList();

    // the only information here is that set is empty => there are no combinations

    // BEWARE! if 0 here it may be invalid input, but also a empty set
    if(result.Count == 0)  //Add: "&&someInputSet.NotEmpty()"

    // we go a step further, our API require combinations, so
    // this method cannot satisfy the API request, then we throw.
         throw new Exception("you requsted impossible combinations");

    return result;
}

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.

Spielentwickler
quelle
Das Problem besteht darin, dass Sie mathematisch definierte Objekte verwenden, um ein Problem zu lösen. Für das Problem selbst ist jedoch möglicherweise kein mathematisch definiertes Objekt als Antwort erforderlich (hier wird eine Liste zurückgegeben). Es hängt alles von der höheren Ebene des Problems ab, unabhängig davon, wie Sie es lösen. Dies wird als Verkapselung / Verstecken von Informationen bezeichnet.
GameDeveloper
Der Grund, warum sich "SomeMethod" nicht mehr wie ein Set verhalten sollte, ist, dass Sie eine Information "aus Sets" abfragen, insbesondere den kommentierten Teil "&& someInputSet.NotEmpty ()"
GameDeveloper
1
NEIN! Du solltest in deiner weiblichen Klasse keine Ausnahme machen. Sie sollten das Builder-Muster verwenden, das erzwingt, dass Sie nicht einmal eine Instanz von FemaleClassRUFEN 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, der FemaleClassals Argument verwendet wird, keine Ausnahmen behandeln, da sich das Objekt im falschen Zustand befindet.
ErikE
Sie gehen davon aus, dass eine Klasse so einfach ist, dass Sie ihre Voraussetzungen in der Realität einfach durch einen Konstruktor durchsetzen können. Ihr Argument ist fehlerhaft. Wenn Sie verbieten, keine Frauen mehr zu haben, können Sie keine Methode zum Entfernen von Schülern aus der Klasse haben oder Ihre Methode muss eine Ausnahme auslösen, wenn noch 1 Schüler übrig ist. Sie haben gerade mein Problem woanders hingezogen. Der Punkt ist, dass es immer einige Vorbedingungen / Funktionszustände geben wird, die nicht "durch Design" aufrechterhalten werden können, und dass der Benutzer brechen wird: aus diesem Grund gibt es Ausnahmen! Das ist ziemlich theoretisches Zeug, ich hoffe, die Gegenstimme ist nicht deine.
GameDeveloper
Die Ablehnung ist nicht meine, aber wenn es so wäre, wäre ich stolz darauf. Ich habe sorgfältig, aber mit Überzeugung abgestimmt. Wenn die Regel Ihrer Klasse lautet, dass ein Element enthalten sein muss und alle Elemente eine bestimmte Bedingung erfüllen müssen, ist es eine schlechte Idee, der Klasse einen inkonsistenten Status zuzuweisen. Das Problem an einen anderen Ort zu verlegen, ist genau meine Absicht! Ich möchte, dass das Problem zur Erstellungszeit und nicht zur Nutzungszeit auftritt. Wenn Sie eine Builder-Klasse anstelle einer Konstruktorauslösung verwenden, können Sie durch Inkonsistenzen einen sehr komplexen Zustand in vielen Teilen und Bestandteilen aufbauen.
ErikE