Moim szczególnym przypadkiem jest to, że użytkownik może przekazać ciąg znaków do aplikacji, aplikacja analizuje go i przypisuje do obiektów strukturalnych. Czasami użytkownik może wpisać coś nieprawidłowego. Na przykład ich wkład może opisywać osobę, ale może powiedzieć, że jej wiek to „jabłko”. Prawidłowe zachowanie w takim przypadku polega na wycofaniu transakcji i poinformowaniu użytkownika o wystąpieniu błędu, a on będzie musiał spróbować ponownie. Może istnieć wymóg zgłaszania każdego błędu, jaki znajdziemy w danych wejściowych, a nie tylko pierwszego.
W tym przypadku argumentowałem, że powinniśmy zgłosić wyjątek. Nie zgodził się, mówiąc: „Wyjątki powinny być wyjątkowe: oczekuje się, że użytkownik może wprowadzić nieprawidłowe dane, więc nie jest to wyjątkowy przypadek”. Naprawdę nie wiedziałem, jak argumentować ten punkt, ponieważ z definicji tego słowa on wydaje się mieć rację.
Ale rozumiem, że właśnie dlatego Wyjątki zostały wymyślone w pierwszej kolejności. Kiedyś trzeba było sprawdzić wynik, aby zobaczyć, czy wystąpił błąd. Jeśli nie sprawdzisz, złe rzeczy mogą się zdarzyć bez Twojej uwagi.
Bez wyjątków każdy poziom stosu musi sprawdzić wynik wywoływanych metod, a jeśli programista zapomni sprawdzić na jednym z tych poziomów, kod może przypadkowo kontynuować i zapisać nieprawidłowe dane (na przykład). W ten sposób wydaje się bardziej podatny na błędy.
W każdym razie nie krępuj się i popraw coś, co tu powiedziałem. Moje główne pytanie brzmi: czy ktoś mówi, że wyjątki powinny być wyjątkowe, skąd mam wiedzieć, czy moja sprawa jest wyjątkowa?
źródło
Odpowiedzi:
Wymyślono wyjątki, aby ułatwić obsługę błędów przy mniejszym bałaganie kodu. Powinieneś ich używać w przypadkach, gdy ułatwiają obsługę błędów przy mniejszym bałaganie kodu. Ta „wyjątki tylko w wyjątkowych okolicznościach” wywodzi się z czasów, gdy obsługa wyjątków została uznana za niedopuszczalny spadek wydajności. Tak już nie jest w przeważającej większości kodu, ale ludzie wciąż wyrzucają regułę, nie pamiętając przyczyny.
Zwłaszcza w Javie, która jest chyba najbardziej kochającym wyjątki językiem, jaki kiedykolwiek wymyślono, nie powinieneś czuć się źle, używając wyjątków, gdy upraszcza to twój kod. W rzeczywistości własna
Integer
klasa Java nie ma możliwości sprawdzenia, czy łańcuch jest prawidłową liczbą całkowitą bez potencjalnego wyrzuceniaNumberFormatException
.Ponadto, chociaż nie możesz polegać tylko na sprawdzaniu poprawności interfejsu użytkownika, pamiętaj, że jeśli interfejs użytkownika jest poprawnie zaprojektowany, na przykład za pomocą pokrętła do wprowadzania krótkich wartości liczbowych, wówczas wartość nienumeryczna, która dostanie się do zaplecza, naprawdę byłaby wyjątkowy stan.
źródło
throw new ...
. Lub wyrzuć niestandardowe wyjątki, w których fillInStackTrace () jest zastępowany. Nie powinieneś wtedy zauważać spadku wydajności, nie mówiąc już o hitach .if (foo() == ERROR) { return ERROR; } else { // continue }
na każdym poziomie. Jeśli zgłosisz niezaznaczony wyjątek, nie będzie głośnego i zbędnego „błędu powrotu błędu”. Ponadto, jeśli przekazujesz funkcje jako argument, użycie kodu błędu może zmienić sygnaturę funkcji na niezgodny typ, nawet jeśli błąd może nie wystąpić.Kiedy należy wprowadzić wyjątek? Jeśli chodzi o kod, myślę, że następujące wyjaśnienie jest bardzo pomocne:
Wyjątkiem jest sytuacja, gdy członek nie wykona zadania, które powinien wykonać zgodnie ze swoją nazwą . (Jeffry Richter, CLR przez C #)
Dlaczego to jest pomocne? Sugeruje to, że zależy od kontekstu, kiedy coś należy traktować jako wyjątek, czy nie. Na poziomie wywołań metod kontekst podaje: (a) nazwa, (b) podpis metody i (b) kod klienta, który używa lub oczekuje się, że użyje metody.
Aby odpowiedzieć na twoje pytanie, powinieneś zajrzeć do kodu, w którym przetwarzane są dane wprowadzone przez użytkownika. Może to wyglądać mniej więcej tak:
Czy nazwa metody sugeruje wykonanie pewnej weryfikacji? Nie. W takim przypadku niepoprawny PersonData powinien zgłosić wyjątek.
Załóżmy, że klasa ma inną metodę, która wygląda następująco:
Czy nazwa metody sugeruje wykonanie pewnej weryfikacji? Tak. W takim przypadku niepoprawne dane osobowe nie powinny zgłaszać wyjątku.
Podsumowując, obie metody sugerują, że kod klienta powinien wyglądać następująco:
Jeśli nie jest jasne, czy metoda powinna zgłosić wyjątek, być może przyczyną jest źle wybrana nazwa lub podpis metody. Może projekt klasy nie jest jasny. Czasami trzeba zmodyfikować projekt kodu, aby uzyskać jasną odpowiedź na pytanie, czy należy zgłosić wyjątek, czy nie.
źródło
struct
„ValidationResult” i uporządkowałem swój kod tak, jak to opisujesz.Validate
(zwracanie wartości False, jeśli jest nieważne) i raz podczasSave
(rzucanie konkretnego, dobrze udokumentowanego wyjątku, jeśli jest nieważny). Oczywiście wynik sprawdzania poprawności można zapisać w pamięci podręcznej wewnątrz obiektu, ale spowodowałoby to dodatkową złożoność, ponieważ wynik sprawdzania poprawności musiałby zostać unieważniony przy zmianach.Validate()
wywoływał się wSave()
metodzie, a konkretne szczegóły z tegoValidationResult
mogłyby zostać wykorzystane do skonstruowania odpowiedniego komunikatu dla wyjątku.W związku z tym argumentem:
Jakikolwiek wyjątek, który złapiesz, musisz się spodziewać, ponieważ, cóż, postanowiłeś go złapać. Dzięki tej logice nigdy nie powinieneś rzucać żadnych wyjątków, które faktycznie planujesz złapać.
Dlatego uważam, że „wyjątki powinny być wyjątkowe” to okropna zasada.
To, co powinieneś zrobić, zależy od języka. Różne języki mają różne konwencje dotyczące tego, kiedy należy zgłaszać wyjątki. Na przykład Python generuje wyjątki od wszystkiego, a gdy w Pythonie podążam za nimi. Z drugiej strony C ++ generuje stosunkowo niewiele wyjątków i tam podążam za nimi. Możesz traktować C ++ lub Javę jak Python i wyrzucać wyjątki od wszystkiego, ale pracujesz w sprzeczności z tym, jak ten język będzie używany.
Wolę podejście Pythona, ale uważam, że kiepskim pomysłem jest użycie innych języków.
źródło
"exceptions should be exceptional" is a terrible rule of thumb.
Dobrze powiedziane! To jedna z tych rzeczy, które ludzie powtarzają, nie myśląc o nich.Zawsze myślę o takich rzeczach, jak dostęp do serwera bazy danych lub interfejsu API sieci Web, gdy myślę o wyjątkach. Oczekujesz, że interfejs API serwera / sieci będzie działał, ale w wyjątkowych przypadkach może nie działać (serwer nie działa). Żądanie internetowe może zwykle być szybkie, ale w wyjątkowych okolicznościach (duże obciążenie) może upłynąć limit czasu. To jest coś poza twoją kontrolą.
Dane wejściowe użytkowników są pod twoją kontrolą, ponieważ możesz sprawdzić, co wysyłają i zrobić z nimi, co chcesz. W twoim przypadku sprawdziłbym dane wejściowe użytkownika, zanim spróbowałbym je zapisać. Zgadzam się, że użytkownicy, którzy podają nieprawidłowe dane, powinni się spodziewać, a Twoja aplikacja powinna to uwzględnić, sprawdzając poprawność danych wejściowych i wyświetlając przyjazny dla użytkownika komunikat o błędzie.
To powiedziawszy, używam wyjątków w większości moich ustawiaczy modeli domen, w których absolutnie nie powinno być szansy na wejście nieprawidłowych danych. Jest to jednak ostatnia linia obrony i mam tendencję do budowania formularzy wejściowych przy użyciu bogatych reguł sprawdzania poprawności , dzięki czemu praktycznie nie ma szans na wywołanie tego wyjątku modelu domeny. Kiedy więc rozgrywający oczekuje jednej rzeczy, a dostaje inną, jest to wyjątkowa sytuacja, która nie powinna mieć miejsca w zwykłych okolicznościach.
EDYCJA (coś jeszcze do rozważenia):
Wysyłając dane dostarczone przez użytkownika do bazy danych, wcześniej wiesz, co powinieneś i nie powinieneś wprowadzać do swoich tabel. Oznacza to, że dane mogą być sprawdzane pod kątem oczekiwanego formatu. To jest coś, co możesz kontrolować. Tym, czego nie możesz kontrolować, jest awaria serwera w trakcie zapytania. Więc wiesz, że zapytanie jest w porządku, a dane są filtrowane / sprawdzane, próbujesz zapytania i nadal się nie powiedzie, jest to wyjątkowa sytuacja.
Podobnie w przypadku żądań internetowych, nie możesz wiedzieć, czy upłynie limit czasu żądania, czy nie uda się nawiązać połączenia, zanim spróbujesz go wysłać. Gwarantuje to również podejście try / catch, ponieważ nie można zapytać serwera, czy zadziała kilka milisekund później, gdy wyślesz żądanie.
źródło
FileNotFoundException
gdy otrzyma nieprawidłowe dane wejściowe (na przykład nieistniejącą nazwę pliku). To jedyny prawidłowy sposób zagrożenia tym błędem. Co jeszcze można zrobić bez zwracania kodów błędów?Odniesienie
Od pragmatycznego programisty:
Następnie badają przykład otwierania pliku do odczytu, a plik nie istnieje - czy to powinno stanowić wyjątek?
Później dyskutują, dlaczego wybrali takie podejście:
Jeśli chodzi o twoją sytuację
Twoje pytanie sprowadza się do pytania „Czy błędy weryfikacji powinny powodować wyjątki?” Odpowiedź jest taka, że zależy to od tego, gdzie odbywa się walidacja.
Jeśli dana metoda znajduje się w sekcji kodu, w której zakłada się, że dane wejściowe zostały już zatwierdzone, nieprawidłowe dane wejściowe powinny zgłaszać wyjątek; jeśli kod jest zaprojektowany w taki sposób, że ta metoda otrzyma dokładne dane wprowadzone przez użytkownika, należy oczekiwać nieprawidłowych danych i nie należy zgłaszać wyjątku.
źródło
Jest tu wiele filozoficznej pontyfikacji, ale ogólnie mówiąc, wyjątkowymi warunkami są po prostu te warunki, których nie możesz lub nie chcesz (z wyjątkiem czyszczenia, raportowania błędów itp.) Bez interwencji użytkownika. Innymi słowy, są to warunki niemożliwe do odzyskania.
Jeśli podasz programowi ścieżkę pliku, z zamiarem przetworzenia tego pliku w jakiś sposób, a plik określony przez tę ścieżkę nie istnieje, jest to wyjątkowy warunek. Nie możesz nic na to poradzić w kodzie, poza zgłoszeniem tego użytkownikowi i zezwoleniem mu na określenie innej ścieżki pliku.
źródło
Należy wziąć pod uwagę dwa problemy:
omawiasz jeden problem - nazwijmy go,
Assigner
ponieważ ten problem dotyczy przypisywania danych wejściowych do obiektów strukturalnych - i wyrażasz ograniczenie, że dane wejściowe są poprawnedobrze wdrożony interfejs użytkownika posiada dodatkową troskę: walidacja danych wejściowych użytkownika i konstruktywnej informacji zwrotnej na temat błędów (nazwijmy to część
Validator
)Z punktu widzenia
Assigner
komponentu zgłoszenie wyjątku jest całkowicie uzasadnione, ponieważ wyrażono ograniczenie, które zostało naruszone.Z punktu widzenia doświadczenia użytkownika użytkownik nie powinien rozmawiać z tym bezpośrednio
Assigner
. Powinny one być rozmowy z nim poprzezValidator
.Teraz, w przypadku
Validator
nieprawidłowego wprowadzania danych przez użytkownika, nie jest to wyjątkowy przypadek, tak naprawdę jest to przypadek, który Cię bardziej interesuje. Więc tutaj wyjątek nie byłby odpowiedni, i tutaj również chciałbyś zidentyfikować wszystkie błędy zamiast ratować za pierwszym razem.Zauważysz, że nie wspomniałem o tym, jak te obawy są realizowane. Wygląda na to, że mówisz o tym,
Assigner
a twój kolega mówi o połączeniuValidator+Assigner
. Kiedy uświadomisz sobie, że istnieją dwie odrębne (lub możliwe do rozdzielenia) obawy, przynajmniej możesz je rozsądnie przedyskutować.Aby rozwiązać komentarz Renana, ja tylko przy założeniu , że po zidentyfikowaniu swoje dwa oddzielne obawy, to oczywiste, co należy uznać za przypadki wyjątkowe w każdym kontekście.
W rzeczywistości, jeśli nie jest oczywiste, czy coś należy uznać za wyjątkowe, twierdzę, że prawdopodobnie nie skończyłeś identyfikować niezależnych problemów w swoim rozwiązaniu.
To chyba bezpośrednia odpowiedź
upraszczaj, aż będzie to oczywiste . Kiedy masz stos prostych pojęć, które dobrze rozumiesz, możesz jasno uzasadnić ich ponowne złożenie w kod, klasy, biblioteki lub cokolwiek innego.
źródło
Inni dobrze odpowiedzieli, ale wciąż tu jest moja krótka odpowiedź. Wyjątkiem jest sytuacja, w której coś w środowisku ma coś złego, czego nie można kontrolować, a kod nie może w ogóle iść do przodu. W takim przypadku musisz również poinformować użytkownika, co poszło źle, dlaczego nie możesz iść dalej i jaka jest rozdzielczość.
źródło
Nigdy nie byłem wielkim fanem rady, że powinieneś rzucać wyjątki tylko w wyjątkowych przypadkach, częściowo dlatego, że nic nie mówi (to tak, jakby powiedzieć, że powinieneś jeść tylko jedzenie, które jest jadalne), ale także dlatego, że jest bardzo subiektywna i często nie jest jasne, co stanowi wyjątkowy przypadek, a co nie.
Istnieją jednak dobre powody, dla których ta rada: wyrzucanie i wychwytywanie wyjątków jest powolne, a jeśli uruchamiasz swój kod w debuggerze w Visual Studio z ustawieniem powiadamiania cię za każdym razem, gdy zostanie zgłoszony wyjątek, możesz zostać spamowany przez dziesiątki jeśli nie setki wiadomości na długo przed wystąpieniem problemu.
Zasadniczo, jeśli:
wtedy twój kod nigdy nie powinien wyrzucać wyjątku, nawet takiego, który zostanie przechwycony później. Aby przechwycić nieprawidłowe dane, możesz użyć walidatorów na poziomie interfejsu użytkownika lub kodu, na przykład
Int32.TryParse()
w warstwie prezentacji.W każdym innym przypadku powinieneś trzymać się zasady, że wyjątek oznacza, że twoja metoda nie może robić tego, co mówi jej nazwa. Zasadniczo nie jest dobrym pomysłem stosowanie kodów powrotu w celu wskazania niepowodzenia (chyba że nazwa metody wyraźnie wskazuje, że tak się dzieje, np.
TryParse()
) Z dwóch powodów. Po pierwsze, domyślną odpowiedzią na kod błędu jest zignorowanie warunku błędu i kontynuowanie niezależnie; po drugie, możesz zbyt łatwo skończyć z niektórymi metodami używającymi kodów powrotu i innymi metodami wykorzystującymi wyjątki i zapominając, który jest który. Widziałem nawet bazy kodu, w których dwie różne wymienne implementacje tego samego interfejsu przyjmują tutaj różne podejścia.źródło
Wyjątki powinny reprezentować warunki, które prawdopodobnie nie zostaną przygotowane do obsługi kodu natychmiastowego wywołania, nawet jeśli metoda wywołująca mogłaby to zrobić. Rozważmy na przykład kod, który odczytuje niektóre dane z pliku, może słusznie założyć, że każdy prawidłowy plik zakończy się prawidłowym rekordem i nie jest wymagane wyodrębnienie żadnych informacji z rekordu częściowego.
Jeśli procedura odczytu danych nie używa wyjątków, ale po prostu informuje, czy odczyt się powiódł, kod wywołujący musiałby wyglądać następująco:
itd. wydając trzy wiersze kodu na każdy użyteczny kawałek pracy. Natomiast jeśli
readInteger
zgłosi wyjątek po napotkaniu końca pliku, a jeśli program wywołujący może po prostu przekazać wyjątek, kod staje się:O wiele prostszy i czystszy wygląd, ze znacznie większym naciskiem na przypadek, w którym wszystko działa normalnie. Zauważ, że w przypadkach, w których natychmiastowe rozmówca byłoby oczekiwać, aby obsłużyć warunek, metodę, która zwraca kod błędu często będzie bardziej pomocny niż ten, który zgłasza wyjątek. Na przykład, aby zsumować wszystkie liczby całkowite w pliku:
przeciw
Kod pytający o liczby całkowite oczekuje, że jedno z tych wywołań zakończy się niepowodzeniem. Posługiwanie się kodem przez niekończącą się pętlę, która będzie działać, dopóki tak się nie stanie, jest o wiele mniej eleganckie niż użycie metody, która wskazuje awarie za pomocą wartości zwracanej.
Ponieważ klasy często nie wiedzą, jakich warunków będą lub nie będą oczekiwać ich klienci, często pomocne jest zaoferowanie dwóch wersji metod, które mogą zawieść w sposób, jakiego oczekują niektórzy rozmówcy, a inni nie. Takie postępowanie pozwoli na czyste stosowanie takich metod w obu typach rozmówców. Należy również pamiętać, że nawet metody „wypróbowania” powinny generować wyjątki, jeśli wystąpią sytuacje, że osoba dzwoniąca prawdopodobnie nie oczekuje. Na przykład
tryReadInteger
nie powinien zgłaszać wyjątku, jeśli napotka czysty warunek końca pliku (jeśli program wywołujący nie spodziewał się tego, użyłbyreadInteger
). Z drugiej strony prawdopodobnie powinien zgłosić wyjątek, jeśli danych nie można odczytać, ponieważ np. Karta pamięci zawierająca je została odłączona. Chociaż takie zdarzenia powinny zawsze być rozpoznawane jako możliwość, jest mało prawdopodobne, aby kod wywoływania natychmiastowego był przygotowany na zrobienie czegokolwiek przydatnego w odpowiedzi; z pewnością nie powinien być zgłaszany w taki sam sposób, jak warunek końca pliku.źródło
Najważniejszą rzeczą w pisaniu oprogramowania jest uczynienie go czytelnym. Wszystkie inne kwestie są drugorzędne, w tym uczynienie go wydajnym i poprawnym. Jeśli jest czytelny, resztę można załatwić podczas konserwacji, a jeśli nie jest czytelny, lepiej po prostu go wyrzuć. Dlatego należy zgłaszać wyjątki, gdy poprawia to czytelność.
Kiedy piszesz jakiś algorytm, pomyśl tylko o osobie, która będzie go czytać w przyszłości. Gdy dojdziesz do miejsca, w którym mógłby tam być potencjalny problem, należy zadać sobie pytanie, czy czytelnik chce zobaczyć, w jaki sposób poradzić sobie z tym problemem teraz , czy też czytelnik wolą po prostu dostać się na algorytmie?
Lubię wymyślić przepis na ciasto czekoladowe. Kiedy mówi ci, aby dodać jajka, ma wybór: może albo założyć, że masz jajka i przejść do przepisu, albo może rozpocząć wyjaśnienie, w jaki sposób możesz zdobyć jajka, jeśli nie masz jaj. Może wypełnić całą książkę technikami polowania na dzikie kurczaki, wszystko po to, aby upiec ciasto. To dobrze, ale większość ludzi nie będzie chciała czytać tego przepisu. Większość ludzi wolałaby po prostu założyć, że jajka są dostępne i zacząć od przepisu. To wezwanie do osądu, które autorzy muszą sporządzić, pisząc przepisy.
Nie ma żadnych gwarantowanych reguł dotyczących tego, co stanowi dobry wyjątek i problemów, które należy natychmiast rozwiązać, ponieważ wymaga to przeczytania w myślach czytelnika. Najlepsze, co kiedykolwiek zrobisz, to zasady ogólne, a „wyjątki dotyczą tylko wyjątkowych okoliczności” jest całkiem niezłe. Zwykle, gdy czytelnik czyta twoją metodę, szuka tego, co zrobi metoda w 99% przypadków, i wolałby, aby nie było zaśmiecone dziwacznymi przypadkami narożnymi, takimi jak kontaktowanie się z użytkownikami wprowadzającymi nielegalne dane i inne rzeczy, które prawie nigdy się nie zdarzają. Chcą zobaczyć normalny przepływ oprogramowania ułożony bezpośrednio, jedna po drugiej, tak jakby problemy nigdy się nie zdarzały.
źródło
Dlatego nie możesz tutaj zgodzić się na wyjątek. Wyjątek natychmiast przerywa proces sprawdzania poprawności. Więc byłoby wiele do obejścia, aby to zrobić.
Zły przykład:
Metoda sprawdzania poprawności dla
Dog
klasy z wykorzystaniem wyjątków:Jak to nazwać:
Problem polega na tym, że proces sprawdzania poprawności, aby uzyskać wszystkie błędy, wymagałby pominięcia już znalezionych wyjątków. Powyższe może zadziałać, ale jest to wyraźne niewłaściwe użycie wyjątków . Rodzaj weryfikacji, o który zostałeś poproszony, powinien nastąpić przed dotknięciem bazy danych. Nie trzeba więc niczego wycofywać. I wyniki walidacji prawdopodobnie będą błędami walidacji (ale mam nadzieję, że zero).
Lepszym podejściem jest:
Wywołanie metody:
Metoda walidacji:
Dlaczego? Istnieje wiele powodów, a najwięcej powodów wskazano w innych odpowiedziach. Mówiąc prosto: inni czytają i rozumieją o wiele łatwiej . Po drugie, czy chcesz pokazać ślady stosu użytkowników, aby wytłumaczyć mu, że
dog
źle skonfigurował ?Jeśli podczas zatwierdzania w drugim przykładzie nadal pojawia się błąd , nawet pomimo tego, że twój walidator sprawdził
dog
zerowe problemy, wówczas zgłoszenie wyjątku jest właściwe . Podobnie jak: Brak połączenia z bazą danych, wpis bazy danych został w międzyczasie zmodyfikowany przez kogoś innego.źródło