Kiedy rzucić wyjątek?

435

Mam wyjątki utworzone dla każdego warunku, którego moja aplikacja nie oczekuje. UserNameNotValidException, PasswordNotCorrectExceptionItd.

Powiedziano mi jednak, że nie powinienem tworzyć wyjątków od tych warunków. W moim UML TO SĄ wyjątki od głównego przepływu, więc dlaczego nie miałby to być wyjątek?

Wszelkie wskazówki lub najlepsze praktyki dotyczące tworzenia wyjątków?

Kwan Cheng
źródło
58
Otwórz ponownie, to bardzo rozsądne i ważne pytanie. Każde pytanie wymaga pewnej ilości opinii, ale w tym przypadku podejrzewam, że jest to kwestia „najlepszych praktyk”.
Tim Long,
19
+1 za ponowne otwarcie. Podobnie jak wiele innych interesujących tematów, „zależy” i bardzo przydatna jest analiza kompromisów przy podejmowaniu decyzji. Fakt, że ludzie mylą opinie z faktami w odpowiedziach, nie neguje tego. Przesiewanie przez błoto jest ćwiczeniem, które należy pozostawić czytelnikowi.
aron
12
Zgadzam się również, że to pytanie powinno zostać ponownie otwarte, ponieważ dotyczy najlepszych praktyk. Nawiasem mówiąc, najlepsze praktyki to zawsze opinie, które mogą pomóc innym.
Ajay Sharma
6
Microsoft mówi: „Nie zwracaj kodów błędów. Wyjątki są podstawowym sposobem zgłaszania błędów w ramach”. oraz „... Jeśli członek nie może skutecznie wykonać tego, do czego został przeznaczony, należy to uznać za błąd wykonania i należy zgłosić wyjątek.”. msdn.microsoft.com/library/ms229030%28v=vs.100%29.aspx
Matsen75
4
Mogą to być całkowicie sensowne wyjątki, zależy to tylko od tego, które metody je rzucają. Wywołana metoda IsCredentialsValid(username,password)nie powinna zgłaszać wyjątku, jeśli nazwa użytkownika lub hasło są nieprawidłowe, ale zwraca wartość false. Ale powiedzmy, że metoda, która odczytuje dane z bazy danych, może legalnie wyrzucić taki wyjątek, jeśli uwierzytelnienie się nie powiedzie. W skrócie: Powinieneś zgłosić wyjątek, jeśli metoda nie jest w stanie wykonać zadania, które powinna wykonać.
JacquesB

Odpowiedzi:

629

Moja osobista wytyczna: wyjątek jest zgłaszany, gdy fundamentalne założenie dotyczące obecnego bloku kodu jest fałszywe.

Przykład 1: powiedz, że mam funkcję, która ma badać dowolną klasę i zwraca true, jeśli klasa ta dziedziczy z List <>. Ta funkcja zadaje pytanie: „Czy ten obiekt jest potomkiem Listy?” Ta funkcja nigdy nie powinna zgłaszać wyjątku, ponieważ w jej działaniu nie ma szarych obszarów - każda pojedyncza klasa dziedziczy z Listy <>, więc odpowiedź zawsze brzmi „tak” lub „nie”.

Przykład 2: powiedz, że mam inną funkcję, która bada listę <> i zwraca true, jeśli jej długość jest większa niż 50, i false, jeśli długość jest mniejsza. Ta funkcja zadaje pytanie: „Czy ta lista zawiera więcej niż 50 pozycji?” Ale to pytanie przyjmuje założenie - zakłada, że ​​obiekt, który mu podano, jest listą. Jeśli podam mu wartość NULL, to założenie jest fałszywe. W takim przypadku, jeśli funkcja zwraca albo prawdziwe albo fałszywe, to łamie własne zasady. Funkcja nie może niczego zwrócić i twierdzi, że poprawnie odpowiedziała na pytanie. Więc nie wraca - zgłasza wyjątek.

Jest to porównywalne z logicznym błędem „załadowanego pytania” . Każda funkcja zadaje pytanie. Jeśli podane dane wejściowe sprawiają, że to pytanie jest błędne, należy zgłosić wyjątek. Ta linia jest trudniejsza do narysowania za pomocą funkcji, które zwracają wartość void, ale dolna linia jest następująca: jeśli założenia funkcji dotyczące jej danych wejściowych zostaną naruszone, powinna ona wygenerować wyjątek zamiast normalnego powrotu.

Drugą stroną tego równania jest: jeśli często zdarza się, że funkcje generują wyjątki, prawdopodobnie trzeba doprecyzować ich założenia.

The Digital Gabeg
źródło
15
Dokładnie! Jest wyjątek wtedy i tylko wtedy, gdy warunki wstępne funkcyjne (założenia dotyczące argumentów) są podzielone !
Lightman
11
W językoznawstwie jest to czasami nazywane niepowodzeniem z góry . Klasyczny przykład pochodzi od Bertranda Russella: „Czy król Francji jest łysy” nie można odpowiedzieć tak lub nie, (odpowiednio. „Król Francji jest łysy” nie jest ani prawdą ani fałszem), ponieważ zawiera fałszywe założenie , a mianowicie, że istnieje król Francji. Niepowodzenie przy założeniu, że często pojawia się w określonych opisach, i jest to powszechne podczas programowania. Np. „Na początku listy” występuje błąd przy założeniu, że lista jest pusta, a następnie należy zgłosić wyjątek.
Mohan
To prawdopodobnie najlepsze wytłumaczenie!
gaurav,
Dziękuję Ci. Więc. Wiele. „Czy król Francji jest łysy”. Słyszałem to już podczas badania dżungli meinonga .... :) Dziękuję. @Mohan
ErlichBachman
285

Ponieważ są to rzeczy, które będą się normalnie dziać. Wyjątkami nie są mechanizmy kontroli przepływu. Użytkownicy często mylą hasła, nie jest to wyjątkowy przypadek. Wyjątki powinny być naprawdę rzadkie, UserHasDiedAtKeyboardsytuacje typowe.

blowdart
źródło
3
Hmm, nie Wyjątki mogą być stosowane jako mechanizmy kontroli przepływu, jeśli maksymalna wydajność nie jest wymagana, co dotyczy większości aplikacji internetowych. Python używa wyjątku „StopIteration” do zakończenia iteratorów i działa bardzo dobrze. Koszt jest niczym w porównaniu do IO itp.
Seun Osewa
9
+1 doskonała odpowiedź. Jestem tak sfrustrowany przez programistów pracujących nad interfejsami API, że muszę konsumować i rzucać wyjątki dla każdej drobiazgi. BARDZO kilka przypadków naprawdę wymaga wyjątków. Jeśli zdefiniowano 25 różnych rodzajów wyjątków, spójrz ponownie na swój projekt, być może robisz to źle.
7wp
1
czy powinien istnieć wyjątek, gdy użytkownik próbuje wykonać nielegalną akcję, której nie wolno mu manipulować kodem strony internetowej, na przykład usuwając czyjeś posty tutaj na StackOverflow?
Rajat Gupta
30
Wyjątkami są mechanizmy kontroli przepływu. Możesz je rzucić. Możesz je złapać. Przesunąłeś kontrolę na inny fragment kodu. To jest przepływ kontrolny. Jedynym powodem, dla którego języki mają wyjątki, jest to, że możesz pisać prosty kod bez pytania „czy to się nie udało?” po wszystkim, co robisz. Na przykład Haskell nie ma wyjątków, ponieważ monady i notacja mogą zautomatyzować sprawdzanie błędów.
Jesse
2
Wyjątki to coś więcej niż mechanizmy kontroli przepływu. Dostarczają (metodzie) klientowi użytecznych informacji o wyjątkowych wynikach, których muszą być świadomi i którym należy się zająć . Oznacza to, że właściwie stosowane wyjątki sprawiają, że interfejsy API są bardziej niezawodne
idelvall
67

Na moje małe wytyczne duży wpływ ma wspaniała książka „Kod zakończony”:

  • Użyj wyjątków, aby powiadomić o rzeczach, których nie należy ignorować.
  • Nie używaj wyjątków, jeśli błąd można rozwiązać lokalnie
  • Upewnij się, że wyjątki są na tym samym poziomie abstrakcji, co reszta rutyny.
  • Wyjątki powinny być zastrzeżone dla tego, co naprawdę wyjątkowe .
Dowódca Keen
źródło
35

NIE jest wyjątkiem, jeśli nazwa użytkownika jest niepoprawna lub hasło jest nieprawidłowe. Tego należy się spodziewać w normalnym trybie pracy. Wyjątkami są rzeczy, które nie są częścią normalnego działania programu i są raczej rzadkie.

EDYCJA: Nie lubię używać wyjątków, ponieważ nie można stwierdzić, czy metoda zgłasza wyjątek, patrząc tylko na wywołanie. Właśnie dlatego wyjątków należy używać tylko wtedy, gdy nie da się poradzić sobie z sytuacją w przyzwoity sposób (pomyśl „brak pamięci” lub „komputer się pali”).

EricSchaefer
źródło
„Nie lubię używać wyjątków, ponieważ nie można stwierdzić, czy metoda zgłasza wyjątek, patrząc tylko na wywołanie.” Dlatego sprawdzane są wyjątki dla języków, które je obsługują.
Newtopian
1
Sprawdzone wyjątki mają własny zestaw problemów. Nadal wolałbym stosować wyjątki „wyjątkowych okoliczności”, a nie rzeczy, które są częścią normalnego przepływu pracy.
EricSchaefer,
2
W odpowiedzi na Twoją edycję. Zawsze umieszczam w moich dokumentach XML na końcu sekcji podsumowania wyjątki, które funkcja generuje, dzięki czemu mogę zobaczyć te informacje w intellisense.
Matthew Vines,
1
czy powinien istnieć wyjątek, gdy użytkownik próbuje wykonać nielegalną akcję, której nie wolno mu manipulować kodem strony internetowej, na przykład usuwając czyjeś posty tutaj na StackOverflow?
Rajat Gupta
1
Wygląda na to, że mówisz o obsłudze błędów (nasza pamięć, komputer w ogniu) w porównaniu do obsługi wyjątków kodu (brak rekordu, nieprawidłowy typ danych wejściowych itp.). Myślę, że istnieje wyraźna różnica między tymi dwoma.
Chuck Burgess,
28

Jedną z praktycznych zasad jest stosowanie wyjątków w przypadku czegoś, czego normalnie nie można przewidzieć. Przykładami są połączenia z bazą danych, brakujący plik na dysku itp. W przypadku scenariuszy, które można przewidzieć, np. Użytkownicy próbujący zalogować się przy użyciu złego hasła, powinieneś używać funkcji zwracających wartości logiczne i wiedzieć, jak z wdzięcznością poradzić sobie z sytuacją. Nie chcesz nagle przerywać wykonywania, zgłaszając wyjątek tylko dlatego, że ktoś pomylił hasło.

japollock
źródło
6
nie musisz zatrzymywać wykonywania programu na wyjątku ... wyrzuć wyjątek, wywołujący następnie przechwytuje wyjątek i powinien go obsłużyć, jeśli to możliwe, zaloguj się i błąd i przejdź dalej. „Złą formą” jest po prostu rzucanie wyjątków na stos wywołań - łapanie go tam, gdzie ma miejsce, i radzenie sobie z nim tam
Krakkos,
2
ale po co je rzucać, jeśli potrafisz sobie z tym poradzić. jeśli hasło jest niepoprawne lub coś jest nie tak, pozwól tylko, jeśli zwróci fałsz i daję błąd
My1
brakujący plik na dysku ” Większość środowisk językowych, np. .NET, zapewnia interfejsy API do sprawdzania istnienia plików. Dlaczego nie skorzystać z nich przed uzyskaniem bezpośredniego dostępu do pliku!
user1451111
23

Inni sugerują, że nie należy stosować wyjątków, ponieważ złego logowania należy się spodziewać w normalnym przepływie, jeśli użytkownik pomyli się. Nie zgadzam się i nie rozumiem. Porównaj go z otwarciem pliku. Jeśli plik nie istnieje lub z jakiegoś powodu nie jest dostępny, środowisko zgłosi wyjątek. Zastosowanie powyższej logiki było błędem firmy Microsoft. Powinny zwrócić kod błędu. To samo dotyczy analizowania, żądań internetowych itp. Itp.

Nie uważam złego logowania za część normalnego przepływu, jest wyjątkowy. Zwykle użytkownik wpisuje poprawne hasło, a plik istnieje. Wyjątkowe przypadki są wyjątkowe i można w nich doskonale zastosować wyjątki. Komplikowanie kodu przez propagowanie zwracanych wartości przez n poziomów w górę stosu jest marnotrawstwem energii i spowoduje bałagan w kodzie. Zrób najprostszą rzecz, która może działać. Nie przedwcześnie optymalizuj przy użyciu kodów błędów, rzadko zdarza się wyjątkowa sytuacja, a wyjątki nic nie kosztują, chyba że je wyrzucisz.

Bjorn Reppen
źródło
Tyle że możesz sprawdzić, czy plik istnieje, zanim zadzwonisz do open (w zależności od twojej struktury). Tak więc ta funkcja istnieje, a więc jeśli plik znika między sprawdzeniem a próbą jego otwarcia, jest to wyjątek.
blowdart
7
Istniejący plik nie oznacza, że ​​użytkownik może na przykład zapisać plik. Sprawdzanie każdego możliwego problemu jest naprawdę żmudne i podatne na błędy. + kopiujesz kod (DRY).
Bjorn Reppen
Jeden punkt z niepoprawnym hasłem Wyjątkiem jest to, że jakakolwiek spowolnienie w porównaniu z rozwiązaniem z kodem powrotu nie będzie zauważalne dla użytkownika wpisującego hasło.
paperhorse
7
„Komplikowanie kodu przez propagowanie zwracanych wartości przez n poziomów w górę stosu jest marnotrawstwem energii i spowoduje bałagan w kodzie”. To jest dla mnie bardzo silny powód do korzystania z wyjątków. Dobry kod składa się zwykle z małych funkcji. Nie chcesz przesyłać tego kodu błędu w kółko z jednej małej funkcji do drugiej.
beluchin
Myślę, że zamieszanie wynika z założenia, że ​​być może przewidywalnym rezultatem loginmetody typu może być niepoprawne hasło, może być ono faktycznie użyte do ustalenia tego i nie chciałby mieć wyjątku w tym przypadku; podczas gdy w file openscenariuszu typu, który ma określony pożądany wynik - jeśli system nie jest w stanie dostarczyć wyniku z powodu niepoprawnych parametrów wejściowych lub jakiegoś czynnika zewnętrznego, jest to całkowicie logiczne zastosowanie wyjątku.
maja
17

Wyjątki są dość kosztownym efektem, jeśli na przykład masz użytkownika, który podaje nieprawidłowe hasło, zazwyczaj lepiej jest przekazać flagę błędu lub inny wskaźnik, że jest on nieprawidłowy.

Wynika to ze sposobu, w jaki obsługiwane są wyjątki, naprawdę złych danych wejściowych i unikalnych elementów zatrzymania krytycznego powinny być wyjątki, ale nie informacje o niepowodzeniu logowania.

Sprzedawcy Mitchel
źródło
14

Myślę, że powinieneś rzucić wyjątek tylko wtedy, gdy nie możesz nic zrobić, aby wyjść z obecnego stanu. Na przykład, jeśli przydzielasz pamięć, ale nie masz nic do przydzielenia. W przypadkach, o których wspominasz, możesz wyraźnie odzyskać te stany i odpowiednio zwrócić kod błędu z powrotem do dzwoniącego.


Zobaczysz wiele porad, w tym w odpowiedziach na to pytanie, że należy wprowadzać wyjątki tylko w „wyjątkowych” okolicznościach. Wydaje się to pozornie uzasadnione, ale jest błędną radą, ponieważ zastępuje jedno pytanie („kiedy powinienem rzucić wyjątek”) innym subiektywnym pytaniem („co jest wyjątkowego”). Zamiast tego postępuj zgodnie z radą Herb Sutter (dla C ++, dostępną w artykule Dr Dobbs Kiedy i jak korzystać z wyjątków , a także w jego książce z Andrei Alexandrescu, Standardy kodowania C ++ ): zrzuć wyjątek, jeśli i tylko jeśli

  • warunek wstępny nie jest spełniony (co zazwyczaj uniemożliwia spełnienie jednego z poniższych warunków) lub
  • alternatywa nie spełnia warunku końcowego lub
  • alternatywa nie utrzymałaby niezmiennika.

Dlaczego to jest lepsze? Czy to nie zastępuje pytania kilkoma pytaniami o warunki wstępne, warunki dodatkowe i niezmienniki? Jest to lepsze z kilku powiązanych powodów.

  • Warunki wstępne, warunki końcowe i niezmienniki są cechami projektowymi naszego programu (jego wewnętrznego interfejsu API), podczas gdy decyzja throwjest szczegółowa. Zmusza nas to do pamiętania, że ​​musimy rozważyć projekt i jego wdrożenie osobno, a naszym zadaniem przy wdrażaniu metody jest stworzenie czegoś, co spełnia ograniczenia projektowe.
  • Zmusza nas do myślenia w kategoriach warunków wstępnych, końcowych i niezmiennych, które są jedynymi założeniami, które osoby wywołujące naszą metodę powinny przyjąć i są wyrażone precyzyjnie, umożliwiając luźne sprzężenie między składnikami naszego programu.
  • To luźne sprzężenie pozwala nam, jeśli to konieczne, zreformować implementację.
  • Warunki końcowe i niezmienniki są testowalne; skutkuje to kodem, który można łatwo przetestować jednostkowo, ponieważ warunki końcowe są predykatami, które nasz kod testu jednostkowego może sprawdzić (potwierdzić).
  • Myślenie w kategoriach warunków końcowych w naturalny sposób tworzy projekt, który odnosi sukcesy jako warunek końcowy , co jest naturalnym stylem stosowania wyjątków. Normalna („szczęśliwa”) ścieżka wykonania programu jest ułożona liniowo, a cały kod obsługi błędów przeniesiony do catchklauzul.
Raedwald
źródło
10

Powiedziałbym, że nie ma twardych i szybkich zasad określających, kiedy należy stosować wyjątki. Istnieją jednak dobre powody, aby z nich korzystać lub nie:

Powody korzystania z wyjątków:

  • Przepływ kodu dla zwykłego przypadku jest wyraźniejszy
  • Może zwrócić złożone informacje o błędzie jako obiekt (chociaż można to również osiągnąć za pomocą parametru błędu „out” przekazanego przez referencję)
  • Języki zazwyczaj zapewniają pewne narzędzie do zarządzania porządkiem w przypadku wyjątku (spróbuj / wreszcie w Javie, używając w C #, RAII w C ++)
  • Jeśli wyjątek nie zostanie zgłoszony, wykonanie może czasem być szybsze niż sprawdzenie kodów powrotu
  • W Javie zaznaczone wyjątki muszą zostać zadeklarowane lub wyłapane (chociaż może to być powód)

Powody, dla których nie należy używać wyjątków:

  • Czasami jest to przesada, jeśli obsługa błędów jest prosta
  • Jeśli wyjątki nie są udokumentowane lub zadeklarowane, mogą nie zostać przechwycone przez kod wywołujący, co może być gorsze niż gdyby kod wywołujący po prostu zignorował kod powrotu (wyjście aplikacji a cicha awaria - co gorsze może zależeć od scenariusza)
  • W C ++ kod używający wyjątków musi być bezpieczny w wyjątkach (nawet jeśli ich nie rzucasz ani nie łapiesz, ale wywołujesz funkcję rzucania pośrednio)
  • W C ++ trudno jest powiedzieć, kiedy funkcja może rzucić, dlatego musisz być paranoikiem na temat bezpieczeństwa wyjątkowego, jeśli go używasz
  • Rzucanie i łapanie wyjątków jest zasadniczo znacznie droższe w porównaniu do sprawdzania flagi powrotu

Ogólnie rzecz biorąc, byłbym bardziej skłonny do stosowania wyjątków w Javie niż w C ++ lub C #, ponieważ jestem zdania, że ​​wyjątek, zadeklarowany lub nie, jest zasadniczo częścią formalnego interfejsu funkcji, ponieważ zmiana gwarancji wyjątku może zerwać kod wywoławczy. Największą zaletą korzystania z nich w Javie IMO jest to, że wiesz, że Twój rozmówca MUSI obsłużyć wyjątek, a to zwiększa szansę na prawidłowe zachowanie.

Z tego powodu w każdym języku zawsze czerpałbym wszystkie wyjątki w warstwie kodu lub interfejsu API ze wspólnej klasy, aby kod wywołujący zawsze gwarantował wychwycenie wszystkich wyjątków. Również uważam, że źle jest rzucać klasy wyjątków, które są specyficzne dla implementacji, podczas pisania interfejsu API lub biblioteki (tj. Zawijanie wyjątków z niższych warstw, aby wyjątek otrzymany przez program wywołujący był zrozumiały w kontekście interfejsu).

Zauważ, że Java rozróżnia wyjątki ogólne i wyjątki wykonawcze, ponieważ nie trzeba ich deklarować. Używałbym klas wyjątków środowiska wykonawczego tylko wtedy, gdy wiesz, że błąd jest wynikiem błędu w programie.

Robert
źródło
5

Klasy wyjątków są jak „normalne” klasy. Tworzysz nową klasę, gdy „jest” innym typem obiektu, z różnymi polami i różnymi operacjami.

Zasadniczo należy wypróbować równowagę między liczbą wyjątków a ich szczegółowością. Jeśli twoja metoda generuje więcej niż 4-5 różnych wyjątków, możesz prawdopodobnie połączyć niektóre z nich w bardziej „ogólne” wyjątki (np. W twoim przypadku „AuthenticationFailedException”) i użyć komunikatu wyjątku, aby szczegółowo opisać, co poszło nie tak. O ile Twój kod nie obsługuje każdego z nich inaczej, nie musisz tworzyć wielu klas wyjątków. A jeśli tak, to powinieneś po prostu zwrócić wyliczenie z wystąpieniem błędu. W ten sposób jest trochę czystszy.

Shachar
źródło
5

Jeśli kod działa w pętli, co prawdopodobnie spowoduje ciągły wyjątek, wówczas generowanie wyjątków nie jest dobrą rzeczą, ponieważ są one dość powolne dla dużych N. Jednak nie ma nic złego w rzucaniu niestandardowych wyjątków, jeśli wydajność nie jest problem. Upewnij się tylko, że masz podstawowy wyjątek, który wszyscy dziedziczą, o nazwie BaseException lub coś w tym rodzaju. BaseException dziedziczy System.Exception, ale wszystkie wyjątki dziedziczą BaseException. Możesz nawet mieć drzewo typów wyjątków, aby pogrupować podobne typy, ale może to być lub nie przesada.

Krótka odpowiedź brzmi więc, że jeśli nie spowoduje to znacznego ograniczenia wydajności (czego nie powinno, chyba że wprowadzisz wiele wyjątków), to idź dalej.

Charles Graham
źródło
Naprawdę podobał mi się twój komentarz na temat wyjątków w pętli i sam postanowiłem spróbować. Napisałem przykładowy program działający w pętli int.MaxValuei generujący w niej wyjątek „dziel przez zero”. Wersja IF / ELSE, w której sprawdzałem, czy dywidenda nie jest zerowa przed operacją podziału , ukończona w 6082 ms i 15407722 zaznacza, podczas gdy wersja TRY / CATCH, w której generowałem wyjątek i łapałem wyjątek , ukończona w 28174385 ms i 71371326155 tyka: aż 4632 razy więcej niż wersja if / else.
user1451111,
3

Zgadzam się z tam japollock - wyślij akceptację, gdy nie masz pewności co do wyniku operacji. Połączenia z interfejsami API, dostęp do systemów plików, połączenia z bazami danych itp. Za każdym razem, gdy przekraczasz „granice” języków programowania.

Chciałbym dodać, możesz rzucić standardowy wyjątek. Chyba że masz zamiar zrobić coś „innego” (zignoruj, wyślij e-mailem, zaloguj, pokaż, że twitter wieloryb jest podobny, itp.), To nie przejmuj się niestandardowymi wyjątkami.

dclaysmith
źródło
3

ogólna zasada rzucania wyjątków jest dość prosta. robisz to, gdy Twój kod wejdzie w stan NIEODWRACALNY NIEPRAWIDŁOWY. jeśli dane zostaną naruszone lub nie możesz wycofać przetwarzania, które nastąpiło do tego momentu, musisz je zakończyć. co właściwie możesz zrobić? Twoja logika przetwarzania ostatecznie zawiedzie gdzie indziej. jeśli możesz jakoś odzyskać, zrób to i nie wyrzucaj wyjątków.

w twoim szczególnym przypadku, jeśli zostałeś zmuszony zrobić coś głupiego, na przykład zaakceptować wypłatę pieniędzy, a dopiero potem sprawdzić użytkownika / hasło, powinieneś zakończyć proces, zgłaszając wyjątek, aby powiadomić, że stało się coś złego i zapobiec dalszym szkodom.

Goran
źródło
2

Ogólnie rzecz biorąc, chcesz zgłosić wyjątek dla wszystkiego, co może się zdarzyć w Twojej aplikacji, co jest „Wyjątkowe”

W twoim przykładzie oba wyjątki wyglądają tak, jakbyś do nich dzwonił poprzez sprawdzenie poprawności hasła / nazwy użytkownika. W takim przypadku można argumentować, że tak naprawdę nie jest wyjątkowe, że ktoś pomyliłby nazwę użytkownika / hasło.

Są to „wyjątki” od głównego przepływu twojego UML, ale są bardziej „gałęziami” w przetwarzaniu.

Jeśli próbowałeś uzyskać dostęp do pliku passwd lub bazy danych i nie mógłbyś tego zrobić, byłby to wyjątkowy przypadek i uzasadniałby zgłoszenie wyjątku.

Gord
źródło
Gdybyś próbował uzyskać dostęp do pliku lub bazy danych passwd i nie mógł, byłby to wyjątkowy przypadek i uzasadniałby wyjątek. ” Większość frameworków językowych, np. .NET Framework, zapewnia interfejsy API do sprawdzania istnienia plików. Dlaczego nie skorzystać z nich przed uzyskaniem bezpośredniego dostępu do pliku!
user1451111
2

Po pierwsze, jeśli użytkownicy Twojego interfejsu API nie są zainteresowani konkretnymi, drobnoziarnistymi awariami, to posiadanie określonych wyjątków dla nich nie ma żadnej wartości.

Ponieważ często nie jest możliwe ustalenie, co może być przydatne dla użytkowników, lepszym podejściem jest posiadanie określonych wyjątków, ale upewnienie się, że dziedziczą one od wspólnej klasy (np. Std :: wyjątek lub jego pochodne w C ++). To pozwala Twojemu klientowi wyłapać określone wyjątki, jeśli tak zdecydują, lub bardziej ogólny wyjątek, jeśli ich to nie obchodzi.

Jason Etheridge
źródło
2

Wyjątki są przeznaczone dla zdarzeń, które są nietypowymi zachowaniami, błędami, awariami itp. Zachowanie funkcjonalne, błąd użytkownika itp. Powinny być obsługiwane przez logikę programu. Ponieważ złe konto lub hasło są oczekiwaną częścią logiki w procedurze logowania, powinno być w stanie poradzić sobie z tymi sytuacjami bez wyjątków.

Joe Skora
źródło
2

Mam filozoficzne problemy z używaniem wyjątków. Zasadniczo spodziewasz się wystąpienia określonego scenariusza, ale zamiast jawnego radzenia sobie z nim, odsuwasz problem, aby zająć się nim „gdzie indziej”. I gdzie to „gdzie indziej” może się domyślić.

Dan
źródło
2

Powiedziałbym, że ogólnie każdy fundamentalizm prowadzi do piekła.

Na pewno nie chciałbyś skończyć z przepływem opartym na wyjątkach, ale unikanie wyjątków jest również złym pomysłem. Musisz znaleźć równowagę między obiema metodami. Nie chciałbym stworzyć typu wyjątku dla każdej wyjątkowej sytuacji. To nie jest produktywne.

Generalnie wolę stworzyć dwa podstawowe typy wyjątków, które są używane w całym systemie: LogicalException i TechnicalException . W razie potrzeby można je dalej rozróżnić według podtypów, ale generalnie nie jest to konieczne.

Wyjątek techniczny oznacza naprawdę nieoczekiwany wyjątek, taki jak niedziałający serwer bazy danych, połączenie z usługą internetową spowodowało wyjątek IOException i tak dalej.

Z drugiej strony, logiczne wyjątki są wykorzystywane do propagowania mniej poważnej błędnej sytuacji do górnych warstw (ogólnie niektóre wyniki sprawdzania poprawności).

Należy pamiętać, że nawet wyjątek logiczny nie jest przeznaczony do regularnego używania do kontrolowania przebiegu programu, ale raczej do podkreślenia sytuacji, w której przepływ powinien się naprawdę skończyć. W Javie oba typy wyjątków są podklasami RuntimeException, a obsługa błędów jest wysoce zorientowana na aspekty.

Dlatego w przykładzie logowania rozsądne może być utworzenie czegoś takiego jak AuthenticationException i rozróżnienie konkretnych sytuacji według wartości wyliczeniowych, takich jak UsernameNotExisting , PasswordMismatch itp. Wtedy nie skończysz z ogromną hierarchią wyjątków i możesz utrzymać bloki catch na utrzymywalnym poziomie. . Możesz także z łatwością zastosować ogólny mechanizm obsługi wyjątków, ponieważ masz skategoryzowane wyjątki i dość dobrze wiesz, co i jak przekazać do użytkownika.

Nasze typowe użycie polega na zgłoszeniu wyjątku LogicalException podczas wywołania usługi sieci Web, gdy dane wejściowe użytkownika są nieprawidłowe. Wyjątek zostaje przydzielony do szczegółów SOAPFault, a następnie ponownie zostaje usunięty z wyjątku na kliencie, co powoduje wyświetlenie błędu sprawdzania poprawności w jednym polu wejściowym określonej strony internetowej, ponieważ wyjątek ma odpowiednie odwzorowanie na to pole.

Z pewnością nie jest to jedyna sytuacja: nie musisz uruchamiać usługi internetowej, aby zgłosić wyjątek. Możesz to zrobić w każdej wyjątkowej sytuacji (na przykład w przypadku awarii w trybie awaryjnym) - wszystko zależy od ciebie.

Petr Macek
źródło
2

dla mnie wyjątek powinien zostać zgłoszony w przypadku niepowodzenia wymaganej reguły technicznej lub biznesowej. na przykład, jeśli jednostka samochodu jest powiązana z tablicą 4 opon ... jeśli jedna lub więcej opon jest pustych ... należy zwolnić wyjątek „NotEnoughTiresException”, ponieważ można go złapać na innym poziomie systemu i mieć znaczny czyli poprzez logowanie. poza tym, jeśli tylko spróbujemy kontrolować przepływ zerowy i zapobiegać instynktowi samochodu. możemy nigdy nie znaleźć źródła problemu, ponieważ opona nie powinna być zerowa.

Genjuro
źródło
1

głównym powodem unikania zgłaszania wyjątku jest to, że generowanie wyjątku wiąże się z dużym nakładem pracy.

W poniższym artykule stwierdzono, że wyjątek dotyczy wyjątkowych warunków i błędów.

Zła nazwa użytkownika niekoniecznie jest błędem programu, ale błędem użytkownika ...

Oto dobry punkt wyjścia do wyjątków w .NET: http://msdn.microsoft.com/en-us/library/ms229030(VS.80).aspx

Sam
źródło
1

Zgłaszanie wyjątków powoduje, że stos się odpręża, co ma pewien wpływ na wydajność (przyznaje się, że nowoczesne środowiska zarządzane poprawiły to). Ciągle powtarzanie i łapanie wyjątków w sytuacji zagnieżdżonej byłoby złym pomysłem.

Prawdopodobnie ważniejsze niż to, wyjątki dotyczą wyjątkowych warunków. Nie należy ich używać do zwykłego przepływu sterowania, ponieważ wpłynie to na czytelność kodu.

Arno
źródło
1

Mam trzy rodzaje warunków, które łapię.

  1. Złe lub brakujące dane wejściowe nie powinny być wyjątkiem. Użyj zarówno wyrażeń regularnych po stronie klienta, jak i wyrażenia regularnego po stronie serwera, aby wykrywać, ustawiać atrybuty i przekazywać wiadomości z powrotem do tej samej strony.

  2. AppException. Jest to zwykle wyjątek wykrywany i wprowadzany do kodu. Innymi słowy, są to te, których oczekujesz (plik nie istnieje). Zaloguj się, ustaw komunikat i przejdź z powrotem do strony ogólnego błędu. Ta strona zazwyczaj zawiera trochę informacji o tym, co się stało.

  3. Nieoczekiwany wyjątek. To są te, o których nie wiesz. Zaloguj się ze szczegółami i prześlij je na stronę ogólnego błędu.

Mam nadzieję że to pomoże

Michał
źródło
1

Bezpieczeństwo jest powiązane z twoim przykładem: nie powinieneś informować atakującego, że istnieje nazwa użytkownika, ale hasło jest nieprawidłowe. To dodatkowe informacje, których nie musisz udostępniać. Po prostu powiedz „nazwa użytkownika lub hasło są nieprawidłowe”.

zaraz
źródło
1

Prosta odpowiedź brzmi: ilekroć operacja jest niemożliwa (z powodu dowolnej aplikacji LUB z powodu naruszenia logiki biznesowej). Jeśli metoda jest wywoływana i nie można zrobić tego, co metoda została napisana, wykonaj wyjątek. Dobrym przykładem jest to, że konstruktory zawsze zgłaszają wyjątki ArgumentException, jeśli instancji nie można utworzyć przy użyciu podanych parametrów. Innym przykładem jest InvalidOperationException, który jest zgłaszany, gdy operacja nie może zostać wykonana z powodu stanu innego członka lub członków klasy.

W twoim przypadku, jeśli wywoływana jest metoda taka jak Login (nazwa użytkownika, hasło), jeśli nazwa użytkownika jest niepoprawna, to rzeczywiście poprawne jest wygenerowanie UserNameNotValidException lub PasswordNotCorrectException, jeśli hasło jest niepoprawne. Nie można się zalogować przy użyciu dostarczonych parametrów (tj. Jest to niemożliwe, ponieważ naruszyłoby to uwierzytelnianie), więc zgłoś wyjątek. Chociaż mogę mieć dwa wyjątki dziedziczone z ArgumentException.

Powiedziawszy to, jeśli nie chcesz zgłaszać wyjątku, ponieważ niepowodzenie logowania może być bardzo częste, jedną ze strategii jest utworzenie metody zwracającej typy reprezentujące różne niepowodzenia. Oto przykład:

{ // class
    ...

    public LoginResult Login(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            return new UserInvalidLoginResult(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            return new PasswordInvalidLoginResult(user, password);
        }
        else
        {
            return new SuccessfulLoginResult();
        }
    }

    ...
}

public abstract class LoginResult
{
    public readonly string Message;

    protected LoginResult(string message)
    {
        this.Message = message;
    }
}

public class SuccessfulLoginResult : LoginResult
{
    public SucccessfulLogin(string user)
        : base(string.Format("Login for user '{0}' was successful.", user))
    { }
}

public class UserInvalidLoginResult : LoginResult
{
    public UserInvalidLoginResult(string user)
        : base(string.Format("The username '{0}' is invalid.", user))
    { }
}

public class PasswordInvalidLoginResult : LoginResult
{
    public PasswordInvalidLoginResult(string password, string user)
        : base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
    { }
}

Większość programistów uczy się unikać wyjątków ze względu na narzut związany z ich rzucaniem. Wspaniale jest być świadomym zasobów, ale zwykle nie kosztem projektowania aplikacji. To prawdopodobnie powód, dla którego kazano ci nie rzucać dwoma wyjątkami. To, czy użyć wyjątków, czy nie, zwykle sprowadza się do częstotliwości występowania wyjątku. Jeśli jest to dość powszechny lub dość oczekiwany wynik, wtedy większość programistów uniknie wyjątków i zamiast tego stworzy inną metodę sygnalizowania niepowodzenia z powodu domniemanego zużycia zasobów.

Oto przykład unikania używania wyjątków w scenariuszu opisanym właśnie przy użyciu wzorca Try ():

public class ValidatedLogin
{
    public readonly string User;
    public readonly string Password;

    public ValidatedLogin(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            throw new UserInvalidException(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            throw new PasswordInvalidException(password);
        }

        this.User = user;
        this.Password = password;
    }

    public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
    {
        if (IsInvalidUser(user) || 
            IsInvalidPassword(user, password))
        {
            return false;
        }

        validatedLogin = new ValidatedLogin(user, password);

        return true;
    }
}
Chris
źródło
1

Moim zdaniem fundamentalnym pytaniem powinno być to, czy można oczekiwać, że osoba dzwoniąca będzie chciała kontynuować normalny przebieg programu, jeśli wystąpi jakiś warunek. Jeśli nie wiesz, albo masz oddzielne metody doSomething i trySomething, przy czym pierwsza zwraca błąd, a druga nie, lub procedurę, która akceptuje parametr wskazujący, czy wyjątek powinien zostać zgłoszony, jeśli się nie powiedzie. Rozważ klasę, która wysyła polecenia do zdalnego systemu i zgłasza odpowiedzi. Niektóre polecenia (np. Restart) powodują, że system zdalny wysyła odpowiedź, ale przez pewien czas nie reaguje. Przydaje się zatem możliwość wysłania polecenia „ping” i sprawdzenia, czy zdalny system zareaguje w rozsądnym czasie bez konieczności zgłaszania wyjątku, jeśli nie „ t (osoba dzwoniąca prawdopodobnie oczekiwałaby, że pierwsze kilka prób „pingowania” zakończy się niepowodzeniem, ale w końcu jedna zadziała). Z drugiej strony, jeśli ktoś ma sekwencję poleceń takich jak:

  exchange_command („otwarty plik tymczasowy”);
  exchange_command („zapisz dane pliku tymczasowego {cokolwiek}”);
  exchange_command („zapisz dane pliku tymczasowego {cokolwiek}”);
  exchange_command („zapisz dane pliku tymczasowego {cokolwiek}”);
  exchange_command („zapisz dane pliku tymczasowego {cokolwiek}”);
  exchange_command („zamknij plik tymczasowy”);
  exchange_command („skopiuj plik tymczasowy do pliku rzeczywistego”);

chciałoby się, aby żadna operacja nie przerwała całej sekwencji. Chociaż można sprawdzić każdą operację, aby upewnić się, że się powiodła, bardziej pomocne jest, aby procedura exchange_command () zgłaszała wyjątek, jeśli polecenie się nie powiedzie.

W rzeczywistości w powyższym scenariuszu pomocne może być posiadanie parametru do wyboru wielu trybów obsługi awarii: nigdy nie zgłaszaj wyjątków, zgłaszaj wyjątki tylko w przypadku błędów komunikacji lub zgłaszaj wyjątki w przypadkach, gdy polecenie nie zwróci „sukcesu” „wskazanie.

supercat
źródło
1

„PasswordNotCorrectException” nie jest dobrym przykładem użycia wyjątków. Należy się spodziewać, że użytkownicy błędnie podają swoje hasła, więc nie jest to wyjątek IMHO. Prawdopodobnie nawet możesz się z niego zregenerować, wyświetlając ładny komunikat o błędzie, więc jest to tylko sprawdzenie poprawności.

Nieobsługiwane wyjątki ostatecznie zatrzymają wykonanie - co jest dobre. Jeśli zwracasz kody false, null lub error, musisz samodzielnie zająć się stanem programu. Jeśli zapomnisz sprawdzić gdzieś warunki, Twój program może nadal działać z nieprawidłowymi danymi i możesz mieć trudności z ustaleniem, co się stało i gdzie .

Oczywiście możesz wywoływać ten sam problem z pustymi instrukcjami catch, ale przynajmniej ich wykrycie jest łatwiejsze i nie wymaga zrozumienia logiki.

Zasadniczo:

Używaj ich tam, gdzie nie chcesz lub po prostu nie możesz naprawić błędu.

DanMan
źródło
0

Możesz użyć trochę ogólnych wyjątków dla tych warunków. Na przykład ArgumentException ma być stosowany, gdy coś pójdzie nie tak z parametrami metody (z wyjątkiem ArgumentNullException). Zasadniczo nie potrzebujesz wyjątków, takich jak LessThanZeroException, NotPrimeNumberException itp. Pomyśl o użytkowniku swojej metody. Liczba warunków, które konkretnie będzie chciała obsłużyć, jest równa liczbie typów wyjątków, które twoja metoda musi zgłosić. W ten sposób możesz określić, jak szczegółowe będą wyjątki.

Nawiasem mówiąc, zawsze staraj się zapewnić użytkownikom bibliotek pewne sposoby na uniknięcie wyjątków. TryParse jest dobrym przykładem, istnieje, więc nie musisz używać int.Parse i wychwytywać wyjątek. W twoim przypadku możesz podać pewne metody, aby sprawdzić, czy nazwa użytkownika jest poprawna lub hasło jest prawidłowe, aby Twoi użytkownicy (lub Ty) nie musieli wykonywać wielu wyjątków. Miejmy nadzieję, że spowoduje to bardziej czytelny kod i lepszą wydajność.

Serhat Ozgel
źródło
0

Ostatecznie decyzja sprowadza się do tego, czy bardziej przydatne jest radzenie sobie z błędami na poziomie aplikacji, takimi jak ta, za pomocą obsługi wyjątków, czy za pomocą własnego mechanizmu domowego, takiego jak zwracanie kodów stanu. Nie sądzę, żeby istniała twarda i szybka zasada, która jest lepsza, ale rozważyłbym:

  • Kto dzwoni do twojego kodu? Czy to jakiś publiczny interfejs API czy biblioteka wewnętrzna?
  • Jakiego języka używasz? Jeśli jest to na przykład Java, wówczas zgłoszenie wyjątku (zaznaczone) nakłada na osobę wywołującą wyraźne obciążenie, aby w jakiś sposób poradzić sobie z tym błędem, w przeciwieństwie do statusu powrotu, który można zignorować. To może być dobre lub złe.
  • Jak obsługiwane są inne warunki błędu w tej samej aplikacji? Dzwoniący nie będą chcieli zajmować się modułem, który obsługuje błędy w dziwaczny sposób, w przeciwieństwie do czegokolwiek innego w systemie.
  • Ile rzeczy może pójść nie tak z omawianą rutyną i jak można sobie z nimi poradzić inaczej? Rozważ różnicę między serią bloków catch, które obsługują różne błędy, a przełącznikiem kodu błędu.
  • Czy masz uporządkowane informacje o błędzie, który musisz zwrócić? Zgłoszenie wyjątku daje lepsze miejsce na umieszczenie tych informacji niż tylko przywrócenie statusu.
eli
źródło
0

Istnieją dwie główne klasy wyjątków:

1) Wyjątek systemowy (np. Utrata połączenia z bazą danych) lub 2) Wyjątek użytkownika. (np. sprawdzanie poprawności przez użytkownika, „hasło jest nieprawidłowe”)

Uznałem, że pomocne jest utworzenie własnej klasy wyjątków użytkownika, a kiedy chcę zgłosić błąd użytkownika, chcę być traktowany inaczej (tj. Błąd zasobów wyświetlany użytkownikowi), wtedy wszystko, co muszę zrobić w mojej głównej procedurze obsługi błędów, to sprawdzić typ obiektu :

            If TypeName(ex) = "UserException" Then
               Display(ex.message)
            Else
               DisplayError("An unexpected error has occured, contact your help  desk")                   
               LogError(ex)
            End If
Skorupiasty
źródło
0

Kilka przydatnych rzeczy do przemyślenia przy podejmowaniu decyzji, czy wyjątek jest odpowiedni:

  1. jaki poziom kodu chcesz uruchomić po wystąpieniu kandydata na wyjątek - to znaczy ile warstw stosu wywołań powinno się rozwinąć. Zasadniczo chcesz obsłużyć wyjątek jak najbliżej miejsca jego wystąpienia. W przypadku sprawdzania poprawności nazwy użytkownika / hasła normalnie radzisz sobie z awariami w tym samym bloku kodu, a nie pozwalasz na pojawienie się wyjątku. Dlatego wyjątek prawdopodobnie nie jest odpowiedni. (OTOH, po trzech nieudanych próbach logowania przepływ kontroli może się przenieść w inne miejsce, a tutaj może być odpowiedni wyjątek).

  2. Czy to zdarzenie jest czymś, co chciałbyś zobaczyć w dzienniku błędów? Nie każdy wyjątek jest zapisywany w dzienniku błędów, ale warto zapytać, czy ten wpis w dzienniku błędów byłby przydatny - tzn. Spróbowałbyś coś z tym zrobić, czy też byłby śmieciem, który ignorujesz.

Mike Kantor
źródło