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 IEnumerable
potrzeby 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 someInputSet
ma 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 result
bę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?
źródło
Odpowiedzi:
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
źródło
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: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:
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
SQL
zapytania takiego jakSELECT <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 wyrzuceniaInvalidConditionsError
.źródło
n
elementy 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 nan
pobranie elementów ze wspomnianej kolekcji.1.0 / 0
, Python nie.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).W kategoriach laika:
źródło
throw
albo zwrócić zbiór pusty ...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.
źródło
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:
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ć!”
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:
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.
null
Był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ługujeoption/maybe
typy, 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.źródło
Ponieważ chodzi o bibliotekę ogólnego przeznaczenia, moim instynktem byłoby Pozwól użytkownikowi końcowemu wybrać.
Podobnie jak my
Parse()
iTryParse()
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.źródło
Single()
iSingleOrDefault()
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 zwracadefault(T)
. Być może OP mógłby użyćCombinationsOf()
iCombinationsOfOrThrow()
.Single
iSingleOrDefault
(lubFirst
iFirstOrDefault
) 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.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.źródło
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ą.Powinieneś wykonać jedną z następujących czynności (choć nadal konsekwentnie rzucać się na podstawowe problemy, takie jak ujemna liczba kombinacji):
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ć
CombinationsOf
iCombinationsOfWithInputCheck
. Lub cokolwiek chcesz. Możesz to odwrócić, aby sprawdzanie wejścia było krótszą nazwą, a liście -CombinationsOfAllowInconsistentParameters
.W przypadku metod Linq zwróć pole puste
IEnumerable
dokładnie według wskazanego założenia. Następnie dodaj te metody Linq do swojej biblioteki:Na koniec użyj tego w ten sposób:
lub tak:
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
ThrowIfEmpty
metoda 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.
źródło
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)
źródło
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
SimpleDateFormat
masetLenient
metodę 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.źródło
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:
AssertNonempty
czek, który mogą umieścić w swoich łańcuchach.źródło
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ć,
Inf
alboNaN
gdy dzielimy przez zero. Jednak ani nie jest dobre, ani złe:Inf
do biblioteki Python, ludzie będą Cię atakować za ukrywanie błędówW 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ść
strict
powinna być domyślna?”źródło
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:
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:
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ę:
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).
źródło
FemaleClass
chyba ż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 przyjmujeFemaleClass
za argument, nie musi obsługiwać wyjątków, ponieważ obiekt jest w złym stanie.