Wyjątek od pustego zestawu wyników, gdy dane wejściowe są technicznie prawidłowe, ale niezadowalające

108

Tworzę bibliotekę przeznaczoną do publicznego wydania. Zawiera różne metody działania na zestawach obiektów - generowanie, sprawdzanie, partycjonowanie i rzutowanie zbiorów na nowe formy. W razie IEnumerablepotrzeby jest to biblioteka klasy C # zawierająca włączone rozszerzenia w stylu LINQ , która ma zostać wydana jako pakiet NuGet.

Niektóre metody w tej bibliotece mogą mieć niezadowalające parametry wejściowe. Na przykład w metodach kombinatorycznych istnieje metoda generowania wszystkich zbiorów n elementów, które można zbudować ze zbioru źródłowego m elementów. Na przykład, biorąc pod uwagę zestaw:

1, 2, 3, 4, 5

a zapytanie o kombinacje 2 spowoduje:

1, 2
1, 3
1, 4
itd. ...
5, 3
5, 4

Teraz oczywiście można poprosić o coś, czego nie można zrobić, np. Dać mu zestaw 3 przedmiotów, a następnie poprosić o kombinację 4 przedmiotów, jednocześnie ustawiając opcję, która mówi, że można użyć każdego elementu tylko raz.

W tym scenariuszu każdy parametr jest indywidualnie ważny:

  • Kolekcja źródłowa nie ma wartości NULL i zawiera elementy
  • Żądany rozmiar kombinacji jest dodatnią liczbą całkowitą niezerową
  • Żądany tryb (użyj każdego elementu tylko raz) jest prawidłowym wyborem

Jednak stan parametrów wziętych razem powoduje problemy.

Czy w tym scenariuszu można oczekiwać, że metoda zgłosi wyjątek (np. InvalidOperationException) Lub zwróci pustą kolekcję? Oba wydają mi się ważne:

  • Nie możesz wytworzyć kombinacji n przedmiotów z zestawu m przedmiotów, gdzie n> m, jeśli możesz użyć każdego elementu tylko raz, więc operację tę można uznać za niemożliwą InvalidOperationException.
  • Zestaw kombinacji rozmiaru n, który można wytworzyć z m elementów, gdy n> m jest pustym zestawem; nie można tworzyć kombinacji.

Argument za pustym zestawem

Moim pierwszym problemem jest to, że wyjątek zapobiega idiomatycznemu łączeniu metod w stylu LINQ, gdy mamy do czynienia z zestawami danych, które mogą mieć nieznany rozmiar. Innymi słowy, możesz chcieć zrobić coś takiego:

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

Jeśli zestaw danych wejściowych ma zmienną wielkość, zachowanie tego kodu jest nieprzewidywalne. Jeśli .CombinationsOf()zgłasza wyjątek, gdy someInputSetma mniej niż 4 elementy, ten kod czasami zawiedzie w czasie wykonywania bez wstępnego sprawdzenia. W powyższym przykładzie sprawdzanie jest trywialne, ale jeśli nazywasz to w połowie dłuższego łańcucha LINQ, może to być uciążliwe. Jeśli zwróci pusty zestaw, wówczas resultbędzie pusty, z czego możesz być całkowicie zadowolony.

Argument za wyjątkiem

Moja druga obawa polega na tym, że zwrócenie pustego zestawu może ukryć problemy - jeśli wywołujesz tę metodę w połowie łańcucha LINQ i po cichu zwraca pusty zestaw, możesz mieć problemy kilka kroków później lub znaleźć się z pustym zestaw wyników i może nie być oczywiste, jak to się stało, biorąc pod uwagę, że zdecydowanie masz coś w zestawie wejściowym.

Czego byś się spodziewał i jaki jest twój argument za tym?

anaksymander
źródło
66
Ponieważ pusty zestaw jest matematycznie poprawny, są szanse, że kiedy go zdobędziesz, to jest dokładnie to, czego chcesz. Definicje matematyczne i konwencje są na ogół wybierane ze względu na spójność i wygodę, aby po prostu wszystko działało.
asmeurer
5
@asmeurer Wybrano je tak, aby twierdzenia były spójne i wygodne. Nie zostały wybrane, aby ułatwić programowanie. (Czasami jest to dodatkowa korzyść, ale czasami utrudniają również programowanie.)
jpmc26
7
@ jpmc26 „Zostały one wybrane tak, aby twierdzenia były spójne i wygodne” - upewnienie się, że Twój program zawsze działa zgodnie z oczekiwaniami, jest zasadniczo równoważne udowodnieniu twierdzenia.
artem
2
@ jpmc26 Nie rozumiem, dlaczego wspomniałeś o programowaniu funkcjonalnym. Udowodnienie poprawności programów imperatywnych jest całkiem możliwe, zawsze korzystne i może być również wykonane nieformalnie - wystarczy pomyśleć i zachować zdrowy rozsądek matematyczny podczas pisania programu, a będziesz spędzać mniej czasu na testowaniu. Statystycznie udowodniono na próbce jednego ;-)
artem
5
@DmitryGrigoryev 1/0 jako niezdefiniowana jest znacznie bardziej matematycznie poprawne niż 1/0 jako nieskończoność.
asmeurer

Odpowiedzi:

145

Zwróć pusty zestaw

Spodziewałbym się pustego zestawu, ponieważ:

Istnieje 0 kombinacji 4 liczb z zestawu 3, kiedy mogę użyć każdej liczby tylko raz

Ewan
źródło
5
Matematycznie tak, ale jest to również bardzo prawdopodobne źródło błędu. Jeśli oczekuje się tego typu danych wejściowych, lepiej może wymagać od użytkownika przechwycenia wyjątku w bibliotece ogólnego przeznaczenia.
Casey Kuball
56
Nie zgadzam się, że jest to „bardzo prawdopodobna” przyczyna błędu. Załóżmy na przykład, że implementowałeś naiwny algorytm „matchmaknig” z dużego zestawu danych wejściowych. Możesz poprosić o wszystkie kombinacje dwóch elementów, znaleźć wśród nich pojedynczą „najlepszą” kombinację, a następnie usunąć oba elementy i zacząć od nowa z mniejszym zestawem. W końcu twój zestaw będzie pusty i nie będzie już par do rozważenia: wyjątek w tym momencie jest przeszkodą. Myślę, że istnieje wiele sposobów, aby znaleźć się w podobnej sytuacji.
amalloy
11
W rzeczywistości nie wiesz, czy to błąd w oczach użytkownika. W razie potrzeby użytkownik powinien sprawdzić pusty zestaw. if (result.Any ()) DoSomething (result.First ()); else DoSomethingElse (); jest znacznie lepszy niż try {result.first (). dosomething ();} catch {DoSomethingElse ();}
Guran
4
@TripeHound Na tym właśnie polegała decyzja: wymaganie od programisty użycia tej metody do sprawdzenia, a następnie zgłoszenia, ma znacznie mniejszy wpływ niż zgłoszenie wyjątku, którego nie chcą, pod względem nakładów programistycznych, wydajności programu i prostota przebiegu programu.
anaximander,
6
@Darthfett Spróbuj porównać to z istniejącą metodą rozszerzenia LINQ: Where (). Jeśli mam klauzulę: Gdzie (x => 1 == 2) nie otrzymuję wyjątku, otrzymuję pusty zestaw.
Necoras,
79

W razie wątpliwości zapytaj kogoś innego.

Twój przykład funkcja ma bardzo podobną w Pythonie: itertools.combinations. Zobaczmy, jak to działa:

>>> 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))
[]

I czuję się doskonale. Spodziewałem się wyniku, który mógłbym powtórzyć i dostałem taki.

Ale oczywiście, jeśli zapytasz o coś głupiego:

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

Powiedziałbym więc, że jeśli wszystkie parametry się sprawdzą, ale wynikiem będzie pusty zestaw, zwróć pusty zestaw, nie tylko ty to robisz.


Jak powiedział @Bakuriu w komentarzach, to samo dotyczy SQLzapytania takiego jak SELECT <columns> FROM <table> WHERE <conditions>. Dopóki <columns>, <table>, <conditions>są dobrze uformowane formowane i odnoszą się do istniejących nazw, można zbudować zestaw warunków, które wykluczają się nawzajem. Wynikowe zapytanie po prostu nie zwróci żadnych wierszy zamiast wyrzucenia InvalidConditionsError.

Mathias Ettinger
źródło
4
Odebrano, ponieważ nie jest to idiomatyczne w przestrzeni językowej C # / Linq (zobacz odpowiedź sbeckera na to, jak podobne problemy poza granicami są obsługiwane w języku). Gdyby to było pytanie w języku Python, byłoby to ode mnie +1.
James Snell
32
@JamesSnell Ledwo widzę, jak odnosi się to do problemu poza granicami. Nie wybieramy elementów według indeksów w celu ich uporządkowania, wybieramy nelementy w kolekcji, aby policzyć liczbę sposobów ich wybrania. Jeśli w którymś momencie nie zostaną żadne elementy podczas ich pobierania, to nie ma (0) sposobu na npobranie elementów ze wspomnianej kolekcji.
Mathias Ettinger,
1
Mimo to, Python nie jest dobrą wyrocznią dla pytań w C #: na przykład C # pozwoliłby 1.0 / 0, Python nie.
Dmitrij Grigoriew
2
@MathiasEttinger Wskazówki Pythona dotyczące tego, jak i kiedy używać wyjątków, są bardzo różne od C #, więc używanie zachowania z modułów Pythona jako wytycznych nie jest dobrą miarą dla C #.
Ant P
2
Zamiast wybierać Python, moglibyśmy po prostu wybrać SQL. Czy poprawnie sformułowane SELECT zapytanie dotyczące tabeli generuje wyjątek, gdy zestaw wyników jest pusty? (spoiler: nie!). „Dobrze uformowana” kwerenda zależy tylko od samego kwerendy i niektórych metadanych (np. Czy tabela istnieje i ma to pole (tego typu)?) Po prawidłowym sformułowaniu kwerendy i wykonaniu jej oczekujesz (być może pusty) prawidłowy wynik lub wyjątek, ponieważ „serwer” miał problem (np. serwer db upadł lub coś takiego).
Bakuriu,
72

W kategoriach laika:

  • Jeśli wystąpi błąd, powinieneś zgłosić wyjątek. Może to obejmować wykonywanie czynności krokami zamiast jednego połączenia łańcuchowego, aby dokładnie wiedzieć, gdzie wystąpił błąd.
  • Jeśli nie ma błędu, ale wynikowy zestaw jest pusty, nie zgłaszaj wyjątku, zwróć pusty zestaw. Pusty zestaw jest prawidłowym zestawem.
Tulains Córdova
źródło
23
+1, 0 to doskonale ważna i rzeczywiście niezwykle ważna liczba. Jest tak wiele przypadków, w których zapytanie może zgodnie z prawem zwrócić zero trafień, że zgłoszenie wyjątku byłoby bardzo irytujące.
Kilian Foth
19
dobra rada, ale czy twoja odpowiedź jest jasna?
Ewan
54
Jestem jeszcze żaden mądrzejszy co do tego, czy odpowiedź sugeruje OP powinien throwalbo zwrócić zbiór pusty ...
James Snell
7
Twoja odpowiedź mówi, że mogę to zrobić w zależności od tego, czy wystąpił błąd, ale nie wspominasz, czy uważasz przykładowy scenariusz za błąd. W powyższym komentarzu mówisz, że powinienem zwrócić pusty zestaw „jeśli nie ma problemów”, ale ponownie, niespełniające warunki mogą być uważane za problem, a ty mówisz dalej, że nie podnoszę wyjątków, kiedy powinienem. Co powinienem zrobić?
anaximander
3
Ach, rozumiem - być może źle zrozumiałeś; Nic nie wiążę, piszę metodę, której spodziewam się po łańcuchu. Zastanawiam się, czy hipotetyczny przyszły programista, który użyje tej metody, chciałby, aby wrzucił powyższy scenariusz.
anaximander
53

Zgadzam się z odpowiedzią Ewana, ale chcę dodać konkretne uzasadnienie.

Masz do czynienia z operacjami matematycznymi, więc dobrym pomysłem może być trzymanie się tych samych definicji matematycznych. Z matematycznego punktu widzenia liczba zestawów r zestawu n (tj. NCr ) jest dobrze zdefiniowana dla wszystkich r> n> = 0. Jest to zero. Dlatego zwrócenie pustego zestawu byłoby oczekiwanym przypadkiem z matematycznego punktu widzenia.

sigy
źródło
9
To dobra uwaga. Jeśli biblioteka znajdowała się na wyższym poziomie, np. Wybierając kombinacje kolorów, aby utworzyć paletę - wówczas sensowne jest zgłoszenie błędu. Ponieważ wiesz, że paleta bez kolorów nie jest paletą. Ale zbiór bez wpisów jest wciąż zbiorem, a matematyka definiuje go jako równy pustemu zestawowi.
Captain Man,
1
Znacznie lepsza niż odpowiedź Ewansa, ponieważ przytacza praktykę matematyczną. +1
użytkownik949300
24

Dobrym sposobem na określenie, czy skorzystać z wyjątku, jest wyobrażenie sobie ludzi zaangażowanych w transakcję.

Przykładem może być pobranie zawartości pliku:

  1. Proszę o pobranie zawartości pliku „nie istnieje. Txt”

    za. „Oto treść: pusty zbiór znaków”

    b. „Eee, jest problem, ten plik nie istnieje. Nie wiem, co robić!”

  2. Proszę o pobranie zawartości pliku „istnieje, ale jest pusty.txt”

    za. „Oto treść: pusty zbiór znaków”

    b. „Eee, jest problem, w tym pliku nie ma nic. Nie wiem, co robić!”

Bez wątpienia niektórzy się nie zgodzą, ale dla większości ludzi „Erm, jest problem” ma sens, gdy plik nie istnieje i zwraca „pustą kolekcję znaków”, gdy plik jest pusty.

Więc stosując to samo podejście do swojego przykładu:

  1. Proszę podać mi wszystkie kombinacje 4 elementów dla {1, 2, 3}

    za. Nie ma żadnych, oto pusty zestaw.

    b. Jest problem, nie wiem co robić.

Ponownie, „Wystąpił problem” miałoby sens, gdyby np. nullByły oferowane jako zestaw przedmiotów, ale „tutaj jest pusty zestaw” wydaje się rozsądną odpowiedzią na powyższe żądanie.

Jeśli zwrócenie pustej wartości maskuje problem (np. Brakujący plik, a null), wówczas zamiast tego należy ogólnie zastosować wyjątek (chyba że wybrany język obsługuje option/maybetypy, wtedy czasem mają większy sens). W przeciwnym razie zwrócenie pustej wartości prawdopodobnie uprości koszt i lepiej spełni zasadę najmniejszego zdziwienia.

David Arno
źródło
4
Ta rada jest dobra, ale nie dotyczy tej sprawy. Lepszy przykład: Ile dni po 30. stycznia ?: 1 Ile dni po 30. lutego ?: 0 Ile dni po 30. dniu uzupełnienia ?: Wyjątek Ile godzin pracy na 30 : th lutego: Wyjątek
Guran
9

Ponieważ chodzi o bibliotekę ogólnego przeznaczenia, moim instynktem byłoby Pozwól użytkownikowi końcowemu wybrać.

Podobnie jak my Parse()i TryParse()dostępni dla nas, możemy mieć opcję, której używamy, w zależności od tego, czego potrzebujemy od funkcji. Spędziłbyś mniej czasu na pisaniu i utrzymywaniu opakowania funkcji, aby zgłosić wyjątek, niż kłótnie o wybranie jednej wersji funkcji.

James Snell
źródło
3
+1, ponieważ zawsze podoba mi się pomysł wyboru i ponieważ ten wzór ma tendencję do zachęcania programistów do sprawdzania własnego wkładu. Samo istnienie funkcji TryFoo sprawia, że ​​oczywiste jest, że pewne kombinacje danych wejściowych mogą powodować problemy, a jeśli dokumentacja wyjaśnia, jakie są te potencjalne problemy, koder może je sprawdzić i obsłużyć nieprawidłowe dane wejściowe w czystszy sposób, niż mogłyby to zrobić po prostu wychwytując wyjątek lub odpowiadanie na pusty zestaw. Lub mogą być leniwi. Tak czy inaczej, decyzja należy do nich.
aleppke
19
Nie, pusty zestaw jest poprawną matematycznie odpowiedzią. Robienie czegoś innego na nowo definiuje matematykę o ustalonej strukturze, czego nie należy robić kaprysem. Skoro już to robimy, czy powinniśmy ponownie zdefiniować funkcję potęgowania, aby zgłosić błąd, jeśli wykładnik jest równy zero, ponieważ uważamy, że nie podoba nam się konwencja, że ​​dowolna liczba podniesiona do potęgi „zerowej” jest równa 1?
Wildcard
1
Już miałem odpowiedzieć na coś podobnego. Metody rozszerzenia Linq Single()i SingleOrDefault()od razu przyszło mi do głowy. Single zgłasza wyjątek, jeśli występuje zero lub> 1 wynik, a SingleOrDefault nie zgłasza, a zamiast tego zwraca default(T). Być może OP mógłby użyć CombinationsOf()i CombinationsOfOrThrow().
RubberDuck,
1
@Wildcard - mówisz o dwóch różnych wynikach dla tego samego wejścia, co nie jest tym samym. OP jest zainteresowany jedynie wyborem a) wyniku lub b) zgłoszeniem wyjątku, który nie jest wynikiem.
James Snell,
4
Singlei SingleOrDefault(lub Firsti FirstOrDefault) to zupełnie inna historia, @RubberDuck. Podnoszę przykład z poprzedniego komentarza. Nie ma potrzeby, aby nie dodawać żadnego do zapytania „Co nawet istnieje liczby pierwsze większe niż dwa?” , ponieważ jest to rozsądna matematycznie i rozsądna odpowiedź. W każdym razie, jeśli pytasz: „Która pierwsza liczba pierwsza jest większa niż dwa?” nie ma naturalnej odpowiedzi. Po prostu nie możesz odpowiedzieć na pytanie, ponieważ prosisz o jeden numer. To nie jest żadna (nie jest to pojedyncza liczba, ale zestaw), a nie 0. Stąd wyjątek.
Paul Kertscher,
4

Musisz sprawdzić poprawność argumentów podanych podczas wywoływania funkcji. W rzeczywistości chcesz wiedzieć, jak obsługiwać nieprawidłowe argumenty. Fakt, że wiele argumentów zależy od siebie, nie rekompensuje faktu, że weryfikujesz argumenty.

Dlatego głosowałbym na ArgumentException zapewniający użytkownikowi niezbędne informacje, aby zrozumieć, co poszło nie tak.

Na przykład sprawdź public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)funkcję w Linq. Który zgłasza ArgumentOutOfRangeException, jeśli indeks jest mniejszy niż 0 lub większy lub równy liczbie elementów w źródle. W ten sposób indeks jest sprawdzany pod kątem wyliczenia podanego przez dzwoniącego.

Sbecker
źródło
3
+1 za przytoczenie przykładu podobnej sytuacji w języku.
James Snell
13
Co dziwne, twój przykład skłonił mnie do myślenia i doprowadził mnie do czegoś, co moim zdaniem stanowi silny kontrprzykład. Jak zauważyłem, ta metoda ma być podobna do LINQ, dzięki czemu użytkownicy mogą łączyć ją w łańcuch razem z innymi metodami LINQ. Jeśli tak new[] { 1, 2, 3 }.Skip(4).ToList();, otrzymujesz pusty zestaw, co sprawia, że ​​myślę, że zwrócenie pustego zestawu jest być może lepszą opcją.
anaximander
14
To nie jest problem semantyki języka, semantyka domeny problemu już zapewnia poprawną odpowiedź, czyli zwrócenie pustego zestawu. Robienie czegokolwiek innego narusza zasadę najmniejszego zdziwienia dla osoby pracującej w dziedzinie problemów kombinatorycznych.
Ukko
1
Zaczynam rozumieć argumenty za zwróceniem pustego zestawu. Dlaczego miałoby to sens. Przynajmniej dla tego konkretnego przykładu.
sbecker
2
@sbecker, aby doprowadzić Home Point: Jeśli pytanie było: „Jak wiele kombinacje są tam z ...”, a następnie „zero” byłby całkowicie poprawny odpowiedź. Z tego samego powodu „Jakie takie kombinacje, że ...” ma pusty zestaw jako całkowicie poprawną odpowiedź. Gdyby pytanie brzmiało: „Jaka jest pierwsza kombinacja taka, że ​​...”, wtedy i tylko wtedy odpowiedni byłby wyjątek, ponieważ na pytanie nie można odpowiedzieć. Zobacz również komentarz Paula K za .
Wildcard
3

Powinieneś wykonać jedną z następujących czynności (choć nadal konsekwentnie rzucać się na podstawowe problemy, takie jak ujemna liczba kombinacji):

  1. Podaj dwie implementacje, jedną, która zwraca pusty zestaw, gdy dane wejściowe są bezsensowne, i drugą, która rzuca. Spróbuj do nich zadzwonić CombinationsOfi CombinationsOfWithInputCheck. Lub cokolwiek chcesz. Możesz to odwrócić, aby sprawdzanie wejścia było krótszą nazwą, a liście - CombinationsOfAllowInconsistentParameters.

  2. W przypadku metod Linq zwróć pole puste IEnumerabledokładnie według wskazanego założenia. Następnie dodaj te metody Linq do swojej biblioteki:

    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;
          }
       }
    }

    Na koniec użyj tego w ten sposób:

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

    lub tak:

    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();

    Należy pamiętać, że metoda prywatna różniąca się od publicznej jest wymagana, aby zachowanie rzucania lub działania występowało podczas tworzenia łańcucha linq, a nie później, gdy jest ono wyliczane. Ty chcesz go wyrzucić od razu.

    Należy jednak pamiętać, że oczywiście należy wymienić co najmniej pierwszy element, aby ustalić, czy są jakieś elementy. Jest to potencjalna wada, którą, jak sądzę, łagodzi głównie fakt, że przyszli widzowie mogą dość łatwo uzasadnić, że ThrowIfEmptymetoda musi wyliczyć co najmniej jeden element, więc nie powinno być to zaskoczone. Ale nigdy nie wiesz. Możesz to wyjaśnić ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Ale to wydaje się gigantyczną przesadą.

Myślę, że # 2 jest całkiem, świetnie, niesamowity! Teraz moc znajduje się w kodzie wywołującym, a następny czytnik kodu zrozumie dokładnie, co robi, i nie będzie musiał radzić sobie z nieoczekiwanymi wyjątkami.

ErikE
źródło
Wyjaśnij, co masz na myśli. Jak coś w moim poście „nie zachowuje się jak zestaw” i dlaczego jest to coś złego?
ErikE,
Zasadniczo robisz to samo z moją odpowiedzią, zamiast owijać zapytanie, które zdarzyło ci się zapytać o warunek Jeśli warunek, wynik się nie zmienia uu
GameDeveloper
Nie rozumiem, jak moja odpowiedź brzmi „w zasadzie to samo co” z twoją odpowiedzią. Wydają mi się zupełnie inne.
ErikE,
Zasadniczo wymuszasz ten sam warunek „mojej złej klasy”. Ale nie mówisz tego ^^. Czy zgadzasz się, że wymuszasz istnienie 1 elementu w wyniku zapytania?
GameDeveloper
Będziesz musiał mówić wyraźniej dla najwyraźniej głupich programistów, którzy wciąż nie rozumieją, o czym mówisz. Jaka klasa jest zła? Czemu to jest złe? Nic nie wymuszam. Pozwalam UŻYTKOWNIKOWI klasy decydować o ostatecznym zachowaniu zamiast TWÓRCY klasy. Pozwala to na zastosowanie spójnego paradygmatu kodowania, w którym metody Linq nie są losowo generowane z powodu aspektu obliczeniowego, który tak naprawdę nie jest naruszeniem normalnych zasad, takich jak prośba o -1 elementów na raz.
ErikE,
2

Widzę argumenty dla obu przypadków użycia - wyjątek jest świetny, jeśli dalszy kod oczekuje zbiorów zawierających dane. Z drugiej strony, po prostu pusty zestaw jest świetny, jeśli się tego spodziewa.

Myślę, że to zależy od oczekiwań osoby dzwoniącej, jeśli jest to błąd lub akceptowalny wynik - więc przeniosę wybór na osobę dzwoniącą. Może wprowadzić opcję?

.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)

Christian Sauer
źródło
10
Kiedy docieram do celu, staram się unikać metod, w których przekazano wiele opcji modyfikujących ich zachowanie. Nadal nie jestem w 100% zadowolony z wyliczenia trybu, które już tam jest; Naprawdę nie podoba mi się pomysł parametru opcji, który zmienia zachowanie wyjątku metody. Wydaje mi się, że jeśli metoda musi zgłosić wyjątek, musi zgłosić; jeśli chcesz ukryć ten wyjątek, to twoja decyzja jako kod wywołujący, a nie coś, co możesz zrobić, aby mój kod działał z odpowiednią opcją wprowadzania.
anaximander
Jeśli dzwoniący oczekuje zestawu, który nie jest pusty, to do dzwoniącego należy obsłużenie pustego zestawu lub zgłoszenie wyjątku. Jeśli chodzi o odbiorcę, pusty zestaw jest doskonałą odpowiedzią. I możesz mieć więcej niż jednego rozmówcę, z różnymi opiniami na temat tego, czy puste zestawy są w porządku, czy nie.
gnasher729,
2

Istnieją dwa podejścia do podjęcia decyzji, czy nie ma oczywistej odpowiedzi:

  • Napisz kod, przyjmując najpierw jedną opcję, a potem drugą. Zastanów się, który z nich najlepiej sprawdziłby się w praktyce.

  • Dodaj „ścisły” parametr boolowski, aby wskazać, czy parametry mają być ściśle weryfikowane, czy nie. Na przykład Java SimpleDateFormatma setLenientmetodę próbowania analizować dane wejściowe, które nie są w pełni zgodne z formatem. Oczywiście musisz zdecydować, jaka jest wartość domyślna.

JollyJoker
źródło
2

Opierając się na własnej analizie, zwracanie pustego zestawu wydaje się wyraźnie słuszne - nawet zidentyfikowałeś go jako coś, czego niektórzy użytkownicy mogą chcieć i nie wpadłeś w pułapkę zakazania używania, ponieważ nie możesz sobie wyobrazić, że użytkownicy kiedykolwiek chcieliby go użyć w ten sposób.

Jeśli naprawdę uważasz, że niektórzy użytkownicy mogą chcieć wymusić niepuste zwroty, daj im sposób, by poprosił o takie zachowanie, zamiast narzucać je wszystkim. Na przykład możesz:

  • Ustaw jako opcję konfiguracji dowolnego obiektu wykonującego akcję dla użytkownika.
  • Ustaw jako flagę, którą użytkownik może opcjonalnie przekazać do funkcji.
  • Podaj AssertNonemptyczek, który mogą umieścić w swoich łańcuchach.
  • Wykonaj dwie funkcje, jedną, która twierdzi, że jest pusta, a drugą, która nie.

źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami poczynionymi i wyjaśnionymi w poprzednich 12 odpowiedziach
gnat
1

To zależy od tego, czego oczekują użytkownicy. W przypadku (nieco niepowiązanego) przykładu, jeśli Twój kod wykonuje dzielenie, możesz albo zgłosić wyjątek, albo zwrócić, Infalbo NaNgdy dzielimy przez zero. Jednak ani nie jest dobre, ani złe:

  • jeśli wrócisz Infdo biblioteki Python, ludzie będą Cię atakować za ukrywanie błędów
  • jeśli zgłosisz błąd w bibliotece Matlab, ludzie zaatakują cię za brak przetwarzania danych z brakującymi wartościami

W twoim przypadku wybrałbym rozwiązanie, które będzie najmniej zaskakujące dla użytkowników końcowych. Ponieważ tworzysz bibliotekę zajmującą się zestawami, pusty zestaw wydaje się czymś, z czym użytkownicy powinni się zmagać, więc zwracanie go wydaje się rozsądnym posunięciem. Ale mogę się mylić: masz znacznie lepsze zrozumienie kontekstu niż ktokolwiek inny tutaj, więc jeśli oczekujesz, że użytkownicy będą polegać na tym, że zestaw zawsze nie jest pusty, powinieneś od razu rzucić wyjątek.

Rozwiązania, które pozwalają użytkownikowi wybrać (np. Dodanie parametru „ścisłego”) nie są ostateczne, ponieważ zastępują oryginalne pytanie nowym równoważnym: „Która wartość strictpowinna być domyślna?”

Dmitrij Grigoriew
źródło
2
Pomyślałem o użyciu argumentu NaN w mojej odpowiedzi i podzielam twoją opinię. Nie możemy powiedzieć więcej, nie znając domeny OP
GameDeveloper
0

Powszechną koncepcją (w matematyce) jest, że kiedy wybierasz elementy nad zestawem , nie możesz znaleźć elementu, a zatem otrzymujesz pusty zestaw . Oczywiście musisz postępować zgodnie z matematyką, jeśli pójdziesz tą drogą:

Wspólne ustalone zasady:

  • Set.Foreach (predicate); // zawsze zwraca true dla pustych zestawów
  • Set.Exists (predicate); // zawsze zwraca false dla pustych zestawów

Twoje pytanie jest bardzo subtelne:

  • Może to oznaczać, że dane wejściowe funkcji muszą być zgodne z umową : w takim przypadku każde nieprawidłowe dane wejściowe powinny wywoływać wyjątek, to znaczy funkcja nie działa pod normalnymi parametrami.

  • Może być tak, że wejście twojej funkcji musi zachowywać się dokładnie jak zestaw , a zatem powinno być w stanie zwrócić pusty zestaw.

Teraz gdybym był w tobie, wybrałbym „Set”, ale z dużym „ALE”.

Załóżmy, że masz kolekcję, która „hipotezą” powinna obejmować wyłącznie studentki:

class FemaleClass{

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

Teraz twoja kolekcja nie jest już „czystym zestawem”, ponieważ masz na niej umowę, a zatem powinieneś egzekwować ją z wyjątkiem.

Kiedy używasz funkcji „zestawu” w czysty sposób, nie powinieneś rzucać wyjątków w przypadku pustego zestawu, ale jeśli masz kolekcje, które nie są już „czystymi zestawami”, powinieneś rzucać wyjątki tam, gdzie jest to właściwe .

Zawsze powinieneś robić to, co wydaje się bardziej naturalne i spójne: dla mnie zestaw powinien przestrzegać ustalonych reguł, a rzeczy, które nie są zestawami, powinny mieć swoje właściwie przemyślane reguły.

W twoim przypadku dobrym pomysłem wydaje się:

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;
}

Ale to naprawdę nie jest dobry pomysł, mamy teraz niepoprawny stan, który może występować w czasie wykonywania w przypadkowych momentach, ale jest to ukryte w tym problemie, więc nie możemy go usunąć, na pewno możemy przenieść wyjątek wewnątrz jakiejś metody narzędziowej (jest to dokładnie ten sam kod, przenoszony w różnych miejscach), ale to źle i zasadniczo najlepszą rzeczą, jaką możesz zrobić, jest przestrzeganie normalnych reguł .

Faktycznie dodanie nowej złożoności tylko po to, aby pokazać, że możesz pisać metody zapytań linq, wydaje się nie warte twojego problemu , jestem prawie pewien, że jeśli OP może powiedzieć nam więcej o swojej domenie, prawdopodobnie moglibyśmy znaleźć miejsce, w którym wyjątek jest naprawdę potrzebny (jeśli w ogóle możliwe jest, że problem nie wymaga żadnego wyjątku).

Twórca gier
źródło
Problem polega na tym, że używasz matematycznie zdefiniowanych obiektów do rozwiązania problemu, ale sam problem może nie wymagać matematycznie zdefiniowanego obiektu jako odpowiedzi (w tym przypadku zwracanie listy). Wszystko zależy od wyższego poziomu problemu, niezależnie od sposobu jego rozwiązania, co nazywa się enkapsulacją / ukrywaniem informacji.
GameDeveloper
Powodem, dla którego Twój „SomeMethod” nie powinien już zachowywać się jak zestaw, jest to, że pytasz o informacje „z zestawów”, w szczególności spójrz na komentowaną część „&& someInputSet.NotEmpty ()”
GameDeveloper
1
NIE! Nie powinieneś rzucać wyjątku w swojej klasie żeńskiej. Powinieneś użyć wzorca konstruktora, który wymusza, że ​​nie możesz nawet uzyskać instancji, FemaleClasschyba że ma ona tylko kobiety (nie można jej utworzyć publicznie, tylko klasa konstruktora może rozdawać instancję) i prawdopodobnie ma w niej co najmniej jedną kobietę . Wówczas twój kod w całym miejscu, który przyjmuje FemaleClassza argument, nie musi obsługiwać wyjątków, ponieważ obiekt jest w złym stanie.
ErikE
Zakładasz, że klasa jest tak prosta, że ​​możesz wymusić jej warunki wstępne po prostu przez konstruktora, w rzeczywistości. Twój argument jest wadliwy. Jeśli zabronisz już nie mieć kobiet, to lub nie możesz mieć metody usuwania stad z klasy lub twoja metoda musi rzucić wyjątek, gdy zostanie 1 uczeń. Właśnie przeniosłeś mój problem gdzie indziej. Chodzi o to, że zawsze będzie istniał pewien warunek wstępny / stan funkcji, którego nie można utrzymać „zgodnie z projektem”, i że użytkownik się złamie: dlatego istnieją wyjątki! To dość teoretyczne rzeczy, mam nadzieję, że głosowanie nie jest twoje.
GameDeveloper,
Głos nie jest mój, ale gdyby tak było, byłbym z tego dumny . Głosuję z uwagą, ale z przekonaniem. Jeśli reguła twojej klasy jest taka, że ​​MUSI mieć w niej przedmiot, a wszystkie przedmioty muszą spełniać określony warunek, to pozostawienie klasy w niespójnym stanie jest złym pomysłem. Przeniesienie problemu gdzie indziej jest dokładnie moją intencją! Chcę, aby problem pojawił się w czasie budowy, a nie w czasie użytkowania. Użycie klasy Builder zamiast rzucania konstruktora polega na tym, że można zbudować bardzo złożony stan w wielu częściach i częściach poprzez niespójności.
ErikE,