Szczegółowość wyjątków

9

Natknąłem się na debatę między kilkoma przyjaciółmi i mną. Wolą ogólne wyjątki, takie jak ClientErrorExceptioni ServerErrorExceptionze szczegółami jako pola wyjątku, podczas gdy ja wolę sprecyzować rzeczy. Na przykład mogę mieć kilka wyjątków, takich jak:

  • BadRequestException
  • AuthenticationFailureException
  • ProductNotFoundException

Każdy z nich zbudowany w oparciu o kod błędu zwrócony z interfejsu API.

Po zaletach wyjątków wydaje się to idiomatyczne dla Javy. Jednak opinia moich przyjaciół nie jest niczym niezwykłym.

Czy istnieje preferowany sposób pod względem czytelności kodu i użyteczności interfejsu API, czy naprawdę sprowadza się to do preferencji?


źródło
Strona, do której prowadzą linki, jest prawdopodobnie najlepszą ostateczną odpowiedzią na to pytanie. Naprawdę pytasz o opinię. Mogę odpowiedzieć na podstawie mojego doświadczenia i opinii, ale to nie jest obiektywna odpowiedź.
marstato,
@marstato to jest sprawiedliwe. Chyba szukam uzasadnienia na moim stanowisku. Wolę trzymać się tego, czego ludzie oczekują w bibliotekach, które piszę, niż podążać za przewodnikiem, jeśli to oznacza, że ​​dzięki temu moje rzeczy są łatwiejsze w użyciu, wiesz?
Absolutnie się zgadzam. Moje klasy wyjątków również są szczegółowe. Ponadto można zdefiniować abstracti uogólnić klasy wyjątków za pomocą metod pobierających, a następnie sprawić, by te szczegółowe były rozszerzane o ogólne. Np AuthenticationFaliureException extends ClientErrorException. W ten sposób każdy użytkownik może wybrać sposób postępowania z wyjątkami. Oczywiście, to działa więcej. Jednak podczas pisania aplikacji (zamiast biblioteki) jest to inna sytuacja IMHO. W takim przypadku wyjątki nie byłyby bardziej szczegółowe niż potrzebujesz, dla uproszczenia.
marstato,
@marstato, tak właściwie to teraz go wdrażam. Cieszę się, że się zgadzasz. Zostawię pytanie otwarte przez noc, ale proszę skonsoliduj je w poście, abym mógł przynajmniej sprawdzić ciebie

Odpowiedzi:

15

Główną różnicą między posiadaniem wielu różnych klas wyjątków, a posiadaniem tylko kilku, z bardziej szczegółowymi informacjami w tekście błędu (na przykład), jest to, że wiele różnych klas wyjątków pozwala kodowi wywołującemu reagować w różny sposób na różne rodzaje błędów, mając jednocześnie tylko kilka klas ułatwia obsługę wszystkich wyjątków w jednolity sposób.

Jest to zazwyczaj kompromis. Do pewnego stopnia można go ograniczyć, stosując dziedziczenie (ogólną podstawową klasę wyjątków dla osób dzwoniących, które chcą łapać i rejestrować wszystko ogólnie, oraz wyprowadzając wyjątki od tej klasy podstawowej dla osób wywołujących, które potrzebują różnych reakcji), ale nawet to może dać dużo niepotrzebnej złożoności, jeśli nie będziesz ostrożny i trzymasz się zasady YAGNI. Tak więc główne pytanie powinno być tutaj:

  • Czy naprawdę oczekujesz, że osoba wywołująca Twój kod będzie reagować inaczej, z innym przepływem kontroli, na różne rodzaje błędów?

Nie ma na to jednego uniwersalnego rozwiązania, nie ma „najlepszej praktyki” braindead, którą można zastosować wszędzie. Odpowiedź na to pytanie zależy w dużej mierze od rodzaju projektowanego oprogramowania lub komponentu:

  • jakąś aplikację, w której ty lub zespół masz całą bazę kodu pod kontrolą?

  • lub jakiś element wielokrotnego użytku dla stron trzecich, w którym nie znasz wszystkich potencjalnych rozmówców?

  • długo działająca aplikacja serwerowa, w której różnego rodzaju błędy nie powinny natychmiast uszkodzić całego systemu i mogą wymagać różnego rodzaju ograniczania błędów?

  • krótkotrwały proces aplikacji, w którym w przypadku błędu wystarczy wyświetlić komunikat o błędzie użytkownikowi, a następnie ponownie uruchomić proces?

Im więcej wiesz o potencjalnych dzwoniących komponencie, tym lepiej możesz wybrać odpowiedni poziom szczegółowości wyjątków.

Doktor Brown
źródło
3
„Czy naprawdę oczekujesz, że osoba wywołująca twój kod będzie reagować inaczej, z innym przepływem kontroli, na różne rodzaje błędów?” to świetne pytanie. Dziękuję, zamierzam to zapamiętać.
Myślę, że to dobra odpowiedź. Chciałbym jedynie jeszcze bardziej podkreślić potrzebę umożliwienia aplikacji klienta sterowania projektem zgłaszania wyjątków. Nawet uogólnienia, które mogą być rozsądne dla wbudowania w hierarchię wyjątków, mogą nie pasować do sposobów, w jakie aplikacja kliencka (jeśli nie jest to twoja własna osoba) może zdecydować się na uogólnienie programu obsługi. Jeśli masz więcej niż jedną aplikację kliencką o różnych potrzebach, to oczywiście od Ciebie zależy, czy je zrównoważysz.
magicduncan
1

Odpowiedź zależy od tego, o jakim poziomie raportowania błędów mówimy.

Zasadniczo zgadzam się z przyjaciółmi, nie powinieneś nadawać im bardziej szczegółowego charakteru, niż jest to konieczne do wyjaśnienia przyczyny problemu.

Jeśli istnieje powszechny, dobrze znany wyjątek odwoływania się do wartości null (NullReferenceException), nie należy tworzyć własnego MyObjectIsNullException. To tylko dodałoby warstwę zamieszania dla ludzkiego tłumacza, dodatkową rzecz do nauczenia, która niczego nie wyjaśnia.

Tylko wtedy, gdy wyjątek jest wyjątkowy, że nie ma żadnego predefiniowanego, który obejmowałby główną przyczynę, jeśli należy utworzyć własny.

Nie musisz jednak na tym poprzestawać. Typowy błąd może wystąpić w jednym z twoich komponentów i możesz chcieć przekazać, że wystąpił problem z twoim komponentem . Więc nie tylko to, co poszło nie tak, ale także gdzie. Wtedy właściwe byłoby zawinięcie pierwszego wyjątku w MyComponentException. To da ci najlepsze z obu światów.

Najpierw będzie jasne, że twój komponent wpadł w kłopoty. Na dolnej dźwigni konkretną przyczyną byłby wewnętrzny wyjątek.

Martin Maat
źródło
To uczciwe. Więc sugerujesz, aby nie wynajdować nowego wyjątku, gdy już istnieje dobry?
@rec Tak, ponieważ wszyscy już znają predefiniowane. Po to są.
Martin Maat,