Mam metodę, w której cała logika jest wykonywana w pętli foreach, która iteruje nad parametrem metody:
public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
foreach(var node in nodes)
{
// yadda yadda yadda
yield return transformedNode;
}
}
W takim przypadku wysłanie pustej kolekcji skutkuje pustą kolekcją, ale zastanawiam się, czy to nierozsądne.
Moja logika polega na tym, że jeśli ktoś wywołuje tę metodę, to zamierza przekazać dane i przekazałby pustą kolekcję mojej metodzie tylko w błędnych okolicznościach.
Czy powinienem wychwycić to zachowanie i zgłosić wyjątek, czy też najlepszą praktyką jest zwrócenie pustej kolekcji?
c#
exceptions
collections
parameters
Nick Udell
źródło
źródło
null
ale nie, jeśli jest pusta.Odpowiedzi:
Metody użytkowe nie powinny rzucać pustych kolekcji. Twoi klienci API nienawidzą cię za to.
Kolekcja może być pusta; „kolekcja-która-nie może być pusta” jest koncepcyjnie znacznie trudniejsza do pracy.
Przekształcenie pustej kolekcji ma oczywisty skutek: pusta kolekcja. (Możesz nawet zaoszczędzić trochę śmieci, zwracając sam parametr.)
Istnieje wiele okoliczności, w których moduł przechowuje listy rzeczy, które mogą, ale nie muszą być już czymś wypełnione. Konieczność sprawdzania pustki przed każdym wezwaniem do
transform
jest denerwująca i może przekształcić prosty, elegancki algorytm w brzydki bałagan.Metody użyteczności powinny zawsze dążyć do tego, aby ich wkład był liberalny, a wyniki - konserwatywne.
Z tych wszystkich powodów, na miłość boską, postępuj właściwie z pustą kolekcją. Nic nie jest bardziej irytujące niż moduł pomocnika, który myśli, że wie, czego chcesz lepiej niż ty.
źródło
null
w pustą kolekcję. Funkcje z ukrytymi ograniczeniami są uciążliwe.Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input);
jeśli Populate1 opuści kolekcję pustą i zwrócisz ją dla pierwszego wywołania TransformNodes, wyjście1 i dane wejściowe będą tą samą kolekcją, a gdy Populaten2 zostanie wywołany, jeśli umieści węzły w kolekcji, skończysz na drugim zestaw danych wejściowych w wyjściu 2.Widzę dwa ważne pytania, które determinują odpowiedź na to:
1. Znaczący zwrot
Zasadniczo, jeśli możesz zwrócić coś znaczącego, nie rzucaj wyjątku. Pozwól dzwoniącemu poradzić sobie z wynikiem. Więc jeśli twoja funkcja ...
Ogólnie rzecz biorąc, moje uprzedzenie do FP mówi mi „zwróć coś znaczącego”, a null może mieć w tym przypadku ważne znaczenie.
2. Styl
Czy Twój ogólny kod (lub kod projektu lub kod zespołu) faworyzuje styl funkcjonalny? Jeśli nie, należy spodziewać się wyjątków i zająć się nimi. Jeśli tak, rozważ zwrócenie typu opcji . W przypadku typu opcji zwracana jest znacząca odpowiedź lub Brak / Nic . W moim trzecim przykładzie z góry Nic nie byłoby dobrą odpowiedzią w stylu FP. Fakt, że funkcja zwraca typ opcji wyraźnie dzwoniącemu niż sensowna odpowiedź, może nie być możliwy i osoba dzwoniąca powinna być na to przygotowana. Wydaje mi się, że daje to dzwoniącemu więcej opcji (jeśli wybaczysz kalamburowi).
F # jest gdzie wszystkie fajne .Net dzieci robić tego typu rzeczy, ale C # ma wspierać ten styl.
tl; dr
Zachowaj wyjątki dla nieoczekiwanych błędów we własnej ścieżce kodu, nie do końca przewidywalnych (i legalnych) danych wejściowych pochodzących od kogoś innego.
źródło
Jak zawsze to zależy.
Czy to ważne, że kolekcja jest pusta?
Większość kodu obsługi kolekcji prawdopodobnie powiedziałaby „nie”; kolekcja może zawierać dowolną liczbę elementów, w tym zero.
Teraz, jeśli masz jakąś kolekcję, w której „nieważne” jest to, że nie ma w niej żadnych przedmiotów, jest to nowy wymóg i musisz zdecydować, co z tym zrobić.
Pożycz trochę logiki testowania ze świata bazy danych: test na zero przedmiotów, jeden przedmiot i dwa przedmioty. Dotyczy to najważniejszych przypadków (spłukanie źle sformułowanych wewnętrznych lub kartezjańskich warunków łączenia).
źródło
java.util.Collection
byłacom.foo.util.NonEmptyCollection
klasa niestandardowa , która może konsekwentnie zachowywać ten niezmiennik i uniemożliwić na początku stan nieprawidłowy.W ramach dobrego projektu zaakceptuj jak najwięcej zmian w danych wejściowych, tak praktycznych, jak to możliwe. Wyjątki powinny być zgłaszane tylko wtedy, gdy (niedopuszczalne dane wejściowe są prezentowane LUB wystąpią nieoczekiwane błędy podczas przetwarzania) ORAZ program nie może kontynuować w przewidywalny sposób .
W takim przypadku należy się spodziewać, że zostanie zaprezentowana pusta kolekcja, a Twój kod musi ją obsłużyć (co już robi). Byłoby naruszeniem wszystkiego, co dobre, gdyby Twój kod zgłosił tutaj wyjątek. Byłoby to podobne do mnożenia 0 przez 0 w matematyce. Jest zbędny, ale absolutnie niezbędny, aby działał tak, jak działa.
Teraz przejdźmy do argumentu zerowej kolekcji. W tym przypadku kolekcja zerowa jest błędem programistycznym: programista zapomniał przypisać zmienną. Jest to przypadek, w którym wyjątek może zostać zgłoszony, ponieważ nie można w sposób znaczący przetworzyć go w dane wyjściowe, a próba zrobienia tego spowodowałaby nieoczekiwane zachowanie. Byłoby to podobne do dzielenia przez zero w matematyce - jest to całkowicie bez znaczenia.
źródło
Właściwe rozwiązanie jest znacznie trudniejsze do zauważenia, gdy patrzysz tylko na swoją funkcję w izolacji. Rozważ swoją funkcję jako część większego problemu . Jedno z możliwych rozwiązań tego przykładu wygląda następująco (w Scali):
Najpierw dzielisz ciąg znaków na nie cyfrowy, odfiltrowujesz puste ciągi, konwertujesz ciągi na liczby całkowite, a następnie filtrujesz, aby zachować tylko liczby czterocyfrowe. Twoja funkcja może być
map (_.toInt)
w przygotowaniu.Ten kod jest dość prosty, ponieważ każdy etap w potoku obsługuje tylko pusty ciąg lub pustą kolekcję. Jeśli umieścisz pusty ciąg na początku, otrzymasz pustą listę na końcu. Nie musisz się zatrzymywać i sprawdzać
null
ani wyjątku po każdym połączeniu.Oczywiście, zakładając, że pusta lista wyników nie ma więcej niż jednego znaczenia. Jeśli musisz odróżnić puste wyjście spowodowane pustym wejściem od tego spowodowane przez samą transformację, to całkowicie to zmienia.
źródło
To pytanie dotyczy w rzeczywistości wyjątków. Jeśli spojrzysz na to w ten sposób i zignorujesz pustą kolekcję jako szczegół implementacji, odpowiedź jest prosta:
1) Metoda powinna zgłosić wyjątek, gdy nie może kontynuować: albo nie może wykonać wyznaczonego zadania, albo zwrócić odpowiednią wartość.
2) Metoda powinna wychwycić wyjątek, gdy jest w stanie kontynuować pomimo niepowodzenia.
Zatem twoja metoda pomocnicza nie powinna być „pomocna” i rzucać wyjątek, chyba że nie jest w stanie wykonać swojej pracy z pustą kolekcją. Pozwól dzwoniącemu ustalić, czy wyniki mogą być obsługiwane.
Niezależnie od tego, czy zwraca pustą kolekcję, czy ma wartość NULL, jest to nieco trudniejsze, ale niewiele: należy w miarę możliwości unikać kolekcji zerowalnych. Celem zbioru zerowalnego byłoby wskazanie (jak w SQL), że nie masz informacji - na przykład zbiór dzieci może być zerowy, jeśli nie wiesz, czy ktoś go posiada, ale nie masz wiedzą, że nie. Ale jeśli jest to ważne z jakiegoś powodu, prawdopodobnie warto go śledzić.
źródło
Nazwa metody
TransformNodes
. W przypadku pustej kolekcji jako danych wejściowych odzyskanie pustej kolekcji jest naturalne i intuicyjne i ma sens matematyczny.Jeśli metoda została nazwana
Max
i zaprojektowana tak, aby zwracała maksymalną liczbę elementów, naturalnym byłoby wrzucenieNoSuchElementException
pustej kolekcji, ponieważ maksimum niczego nie ma sensu matematycznego.Jeśli metoda została nazwana
JoinSqlColumnNames
i zaprojektowana w taki sposób, aby zwracała ciąg znaków, do którego elementy są połączone przecinkiem w celu użycia w zapytaniach SQL, wówczas sensownym byłoby wrzucenieIllegalArgumentException
pustej kolekcji, ponieważ wywołujący i tak otrzymałby błąd SQL, gdyby użył ciąg w zapytaniu SQL bezpośrednio bez dalszych sprawdzeń, a zamiast sprawdzania zwróconego pustego ciągu naprawdę powinien był sprawdzić, czy nie ma pustej kolekcji.źródło
Max
niczego jest często ujemną nieskończonością.Cofnijmy się i skorzystajmy z innego przykładu, który oblicza średnią arytmetyczną tablicy wartości.
Jeśli tablica wejściowa jest pusta (lub zerowa), czy możesz w uzasadniony sposób spełnić żądanie osoby dzwoniącej? Nie. Jakie masz opcje? Cóż, możesz:
Mówię, że daj im błąd, jeśli podali ci nieprawidłowe dane wejściowe, a prośba nie może zostać zrealizowana. Mam na myśli poważny błąd od pierwszego dnia, aby zrozumieli wymagania twojego programu. W końcu twoja funkcja nie jest w stanie odpowiedzieć. Jeśli operacja może się nie powieść (np. Skopiować plik), wówczas Twój interfejs API powinien dać im błąd, z którym mogą sobie poradzić.
Dzięki temu można zdefiniować sposób obsługi źle sformułowanych żądań i żądań, które mogą się nie powieść.
Bardzo ważne jest, aby kod był spójny w zakresie obsługi tych klas błędów.
Kolejną kategorią jest decyzja, w jaki sposób Twoja biblioteka będzie obsługiwać nonsensowne żądania. Wracając do przykładu podobny do Ciebie - użyjmy funkcję, która określa, czy plik istnieje w ścieżce:
bool FileExistsAtPath(String)
. Jeśli klient przekaże pusty ciąg, jak poradzić sobie z tym scenariuszem? Co powiesz na pustą lub zerową tablicę przekazanąvoid SaveDocuments(Array<Document>)
? Wybierz bibliotekę / bazę kodów i zachowaj spójność. Zdarza mi się brać pod uwagę te przypadki błędów i zabraniam klientom wysuwania bzdurnych żądań, oznaczając je jako błędy (poprzez asercję). Niektórzy ludzie zdecydowanie oprą się temu pomysłowi / działaniu. Uważam, że to wykrywanie błędów jest bardzo pomocne. Jest bardzo dobry do lokalizowania problemów w programach - z dobrą lokalizacją dla szkodliwego programu. Programy są znacznie bardziej przejrzyste i poprawne (rozważ ewolucję bazy kodu) i nie nagrywaj cykli w ramach funkcji, które nic nie robią. Kod jest mniejszy / czystszy w ten sposób, a kontrole są generalnie wypychane do miejsc, w których problem może zostać wprowadzony.źródło
if (!FileExists(path)) { ...create it!!!... }
błędów - z których wiele można było złapać przed zatwierdzeniem, gdyby nie zostały zamazane linie poprawności.Zasadniczo standardowa funkcja powinna być w stanie zaakceptować jak najszerszą listę danych wejściowych i przekazać informacje zwrotne na jej temat, istnieje wiele przykładów, w których programiści używają funkcji w sposób, który nie został zaplanowany przez projektanta, dlatego też uważam, że funkcja powinien być w stanie zaakceptować nie tylko puste kolekcje, ale szeroki zakres typów danych wejściowych i wdzięcznie zwracać informacje zwrotne, czy to nawet obiekt błędu z jakiejkolwiek operacji wykonanej na wejściu ...
źródło
writePaycheckToEmployee
nie powinien przyjmować liczby ujemnej jako danych wejściowych ... ale jeśli „sprzężenie zwrotne” oznacza „robi coś, co może się zdarzyć później”, to tak, każda funkcja zrobi coś, co zrobi dalej.