zgłaszanie wyjątku czasu wykonywania w aplikacji Java

12

Pracuję jako wykonawca projektujący korporacyjną aplikację Java dla mojego klienta w roli lidera technicznego. Z aplikacji będą korzystać użytkownicy końcowi, a zespół wsparcia będzie obsługiwał aplikację, gdy wyjdziemy.

Inni techniczni potencjalni klienci, z którymi pracuję, mają wrażenie, że obsługa wyjątków spowoduje, że kod będzie brudny. System powinien zgłaszać sprawdzone wyjątki tylko z warstwy usługi, a reszta kodu powinna generować wyjątki czasu wykonywania ze wszystkich innych warstw, aby nie trzeba było obsługiwać niezaznaczonych wyjątków.

Jaka jest potrzeba rzucenia niesprawdzonego wyjątku w aplikacji biznesowej?

Z moich wcześniejszych doświadczeń na temat wyjątków czasu wykonywania:

1) Niesprawdzone wyjątki powodują, że kod jest nieprzewidywalny, ponieważ nie pojawiają się nawet w Javadoc.

2) Zgłaszanie niesprawdzonych wyjątków w aplikacji biznesowej nie ma sensu, ponieważ kiedy je rzucisz i trafi prosto w twarz użytkowników, jak wyjaśnisz to użytkownikowi? Widziałem dość aplikacji internetowej, która pokazuje, 500 -Internal Error. Contact Administratorże nic nie znaczy dla użytkownika końcowego lub zespołu wsparcia zarządzającego aplikacją.

3) Zgłaszanie wyjątków środowiska wykonawczego zmusza użytkowników klasy zgłaszających wyjątek do przejścia przez kod źródłowy w celu debugowania i sprawdzenia, dlaczego wyjątek został zgłoszony. Można tego uniknąć tylko wtedy, gdy Javadoc wyjątku środowiska wykonawczego jest doskonale udokumentowany, co moim zdaniem nigdy nie jest przypadkiem.

randominstanceOfLivingThing
źródło
Czy możesz wyjaśnić, co to jest poziom usługi? Czy warstwa jest najdalej od użytkownika, czy najbliżej użytkownika?
Manoj R
Dlaczego to pytanie nie pasuje do SO?
Bjarke Freund-Hansen
@Manoj R: Żadne. Warstwa lub warstwa usługi to miejsce, w którym sprawdzane są reguły biznesowe i logika, oddzielone od szczegółów technologii DB i prezentacji klienta.
Michael Borgwardt,
6
Niezaznaczone wyjątki [...] nie pojawiają się nawet w Javadoc. => pojawią się, jeśli udokumentujesz je @throwstagiem, co jest dobrą praktyką.
assylias,
4
@SureshKoya Effective Java, pozycja 62: Udokumentuj wszystkie wyjątki zgłaszane przez każdą metodę. Wyodrębnij: użyj @throwsznacznika Javadoc, aby udokumentować każdy niesprawdzony wyjątek, który może zgłosić metoda, ale nie używaj słowa kluczowego throws, aby uwzględnić niesprawdzone wyjątki w deklaracji metody.
assylias,

Odpowiedzi:

13

Sprawdzone wyjątki są nieudanym przykładem projektu językowego. Zmuszają cię do posiadania bardzo nieszczelnych abstrakcji i brudnego kodu. Należy ich unikać w jak największym stopniu.

Co do twoich punktów:

1) Sprawdzone wyjątki powodują, że kod jest brudny i nie mniej nieprzewidywalny, ponieważ pojawiają się wszędzie .

2) W jaki sposób poprawiony wyjątek jest pokazywany użytkownikowi? Jedyna rzeczywista różnica między sprawdzonymi i niezaznaczonymi wyjątkami jest techniczna i wpływa tylko na kod źródłowy.

3) Czy słyszałeś kiedyś o śladach stosu? Mówią dokładnie, gdzie został zgłoszony wyjątek, bez względu na to, czy jest zaznaczony, czy niezaznaczony. W rzeczywistości sprawdzone wyjątki są zwykle gorsze przy debugowaniu, ponieważ często są zawijane, co prowadzi do dłuższych i brzydszych śladów na stosie, lub nawet całkowicie tracone, ponieważ zawijanie zostało wykonane nieprawidłowo.

Istnieją dwa rodzaje wyjątków: te, które występują „normalnie” i zwykle są obsługiwane bardzo blisko miejsca ich wystąpienia, oraz te, które są naprawdę wyjątkowe i mogą być obsługiwane ogólnie w bardzo wysokiej warstwie (po prostu przerwij bieżącą akcję i zaloguj ją / pokaż błąd).

Sprawdzone wyjątki stanowiły próbę umieszczenia tego rozróżnienia w składni języka w momencie zdefiniowania wyjątków. Problemy z tym są

  • Rozróżnienie zależy od osoby dzwoniącej , a nie od kodu, który zgłasza wyjątek
  • Jest całkowicie ortogonalny względem semantycznego znaczenia wyjątku, ale powiązanie go z hierarchią klas zmusza do pomieszania dwóch
  • Cały wyjątek polega na tym, że możesz zdecydować, na jakim poziomie je złapać, nie ryzykując po cichu błędu ani nie zanieczyszczając kodu na poziomach pośrednich; sprawdzone wyjątki tracą tę drugą przewagę.
Michael Borgwardt
źródło
1. Cóż, w przypadku niezaznaczonego wyjątku, nie będziesz nawet wiedział, czy połączenie może spowodować niezaznaczone. 2. Kiedy powiedziałeś „Jest całkowicie ortogonalny w stosunku do semantycznego znaczenia wyjątku, ale powiązanie go z hierarchią klas zmusza cię do pomieszania dwóch”, przypuszczam, że miałeś na myśli hierarchię wywołań zamiast hierarchii klas.
randominstanceOfLivingThing 25.01.2013
2
@Suresh Koya: Nie, miałem na myśli hierarchię klas, fakt, że deklaracja SQLException extends Exceptionoznacza, że ​​wybranie rzutu, SQLExceptionponieważ błąd ma związek z dostępem do bazy danych automatycznie oznacza, że ​​wyjątek jest sprawdzany. I możesz udokumentować niezaznaczone wyjątki w komentarzach Javadoc. Działa dobrze dla wszystkich innych języków.
Michael Borgwardt,
1
@MichaelBorgwardt „Sprawdzone wyjątki to nieudany eksperyment w projektowaniu języka”, czy to Twoja osobista opinia, jeśli nie, chciałbym przeczytać więcej na ten temat. Każdy autentyczny link byłby bardzo pomocny.
Vipin,
2
@Vipin: To moja osobista opinia, ale ta, którą podziela wiele innych osób, np. Bruce Eckel: mindview.net/Etc/Discussions/CheckedExceptions - i fakt, że żaden inny język nie przyjął sprawdzonych wyjątków w ciągu 20 lat od wprowadzenia Java dla siebie ...
Michael Borgwardt,
2

Moim zdaniem, rodzaj zgłaszanego wyjątku zależy od tego, co robi twój kod.

Zgłaszam sprawdzone wyjątki, gdy spodziewam się, że mogą się zdarzać dość często (na przykład użytkownicy wprowadzają podejrzane dane) i oczekuję, że kod wywołujący zajmie się sytuacją.

Zgłaszam wyjątki od niesprawdzonych / uruchomieniowych wyjątków (nie tak często, jak sprawdzane), gdy mam rzadką sytuację, w której nie oczekuję obsługi kodu wywołującego. Przykładem może być jakiś dziwny błąd związany z pamięcią, którego nigdy się nie spodziewam. Niezaznaczone są rodzaje błędów, które mogą sprowadzić aplikację.

Przed użytkownikiem nie powinien pojawiać się żaden wyjątek bez pewnego poziomu wsparcia. Nawet jeśli jest to tylko „proszę wyciąć i wkleić ten zrzut błędu do wiadomości e-mail”. Nic nie jest bardziej irytujące dla użytkownika niż informacja o błędzie, ale nie podano mu żadnych szczegółów ani działań, które można podjąć, aby go naprawić.

Domyślam się, że filozofia, o której wspominasz, pochodzi z jednego z dwóch źródeł:

  • Leniwi programiści starają się unikać pracy.
  • Lub programiści, którzy musieli obsługiwać kod, który poszedł w drugą stronę. tj. obsługa nadmiernego błędu. Ten rodzaj kodu, który zawiera duże ilości obsługi wyjątków, z których wiele nie robi nic, a nawet gorzej, jest wykorzystywany do kontroli przepływu i innych niepoprawnych celów.
drekka
źródło
Jeśli coś może się zdarzyć dość często, nie jest to wyjątkiem. Ale zgodził się, że błąd nie powinien być zgłaszany, o czym użytkownik nie ma pojęcia.
Manoj R
Zgadzam się z twoją filozofią. W przypadku aplikacji, którą opracowujesz dla użytkowników, będzie potrzebne zgłoszenie wyjątku Runtime?
randominstanceOfLivingThing 25.01.2013
Ponadto, nawet jeśli zostanie zgłoszony wyjątek środowiska wykonawczego, wolę konwencję wskazaną przez @assylias.
randominstanceOfLivingThing 25.01.2013
1

Jeśli nie chcesz odznaczonych wyjątków czasu wykonywania, dlaczego używasz Java? Istnieje możliwość zastosowania wyjątków czasu wykonywania prawie wszędzie - w szczególności NullPointerException, ArithmeticException, ArrayIndexOutOfBounds itp.

A kiedy raz przeczytasz pliki dziennika jakiegoś systemu J2EE, na przykład instalacji SAP NetWeaver, zobaczysz, że takie wyjątki zdarzają się dosłownie cały czas.

Ingo
źródło
Ze specyfikacji języka Java: „Klasy wyjątków środowiska wykonawczego (RuntimeException i jego podklasy) są zwolnione z kontroli czasu kompilacji, ponieważ w opinii projektantów języka programowania Java konieczność deklarowania takich wyjątków nie pomogłaby znacząco w ustaleniu poprawności programów. Wiele operacji i konstrukcji języka programowania Java może powodować wyjątki czasu wykonywania. ”
randominstanceOfLivingThing 25.01.2013
1
Wyjątkiem środowiska uruchomieniowego, o którym mówisz, są te, które powstają w Javie, ponieważ system wykonawczy Java nie ma pojęcia, dlaczego próbujesz wykonać określone wywołanie. Np .: Wystąpiłby wyjątek NullPointerException, jeśli wywołujesz metodę o wartości null. W takim przypadku system wykonawczy Java nie ma odpowiedniego słowa dla głupców programistów i uderzyłby w osobę dzwoniącą wyjątkiem NullPointerException. Smutne jest to, że dzwoniący sprzedał Ci produkt Netweaver, a Ty jako użytkownik jesteś teraz ofiarą złego programowania. Równie winni są testerzy, ponieważ nie wykonali wszystkich testów granicznych.
randominstanceOfLivingThing
1

Jest kilka zasad postępowania z wyjątkami, o których należy pamiętać. Ale najpierw musisz pamiętać, że wyjątki są częścią interfejsu ujawnionego przez kod; udokumentuj je . Jest to szczególnie ważne, gdy interfejs jest publiczny, ale jest to bardzo dobry pomysł również w przypadku prywatnych interfejsów.

Wyjątki powinny być obsługiwane tylko w punkcie, w którym kod może zrobić z nimi coś sensownego. Najgorszą opcją obsługi jest nic nie robić na ich temat, co należy zrobić tylko wtedy, gdy jest to dokładnie właściwa opcja. (Kiedy mam taką sytuację w swoim kodzie, dołączam komentarz do tego efektu, aby nie martwić się o puste ciało).

Drugą najgorszą opcją jest zgłoszenie niepowiązanego wyjątku bez dołączenia oryginału jako przyczyny. Problem polega na tym, że informacje zawarte w oryginalnym wyjątku, które umożliwiłyby zdiagnozowanie problemu, zostały utracone; tworzysz coś, z czym nikt nie może nic zrobić (poza narzekaniem, że „to nie działa” i wszyscy wiemy, jak nienawidzimy tych zgłoszeń błędów).

Znacznie lepiej jest rejestrować wyjątek. To pozwala komuś dowiedzieć się, na czym polega problem i go naprawić, ale należy rejestrować wyjątek tylko w punkcie, w którym zostałby utracony lub zgłoszony przez połączenie zewnętrzne. Nie dzieje się tak dlatego, że częstsze rejestrowanie jest poważnym problemem jako takim, ale dlatego, że nadmierne rejestrowanie oznacza, że ​​dziennik zajmuje więcej miejsca i nie zawiera więcej informacji. Po zarejestrowaniu wyjątku możesz z czystym sumieniem zgłosić klientowi / klientowi précis (o ile podasz również czas wygenerowania - lub inny identyfikator korelacji) w tym raporcie, aby można było dopasować krótką wersję w razie potrzeby ze szczegółami).

Najlepszą opcją jest oczywiście całkowite obsłużenie wyjątku, zajmując się sytuacją błędu w całości. Jeśli możesz to zrobić, zrób to! Może to nawet oznaczać, że można uniknąć konieczności rejestrowania wyjątku.

Jednym ze sposobów obsługi wyjątku jest zgłoszenie innego wyjątku, który zapewnia opis problemu na wyższym poziomie (np. „ failed to initialize” Zamiast „ index out of bounds”). Jest to dobry wzorzec, o ile nie stracisz informacji o przyczynie wyjątku; użyj szczegółowego wyjątku, aby zainicjować wyjątek causewyższego poziomu lub zapisz szczegóły (jak omówiono powyżej). Rejestrowanie jest najbardziej odpowiednie, gdy masz zamiar przekroczyć granicę międzyprocesową, na przykład wywołanie IPC, ponieważ nie ma gwarancji, że klasa wyjątków niskiego poziomu będzie w ogóle obecna na drugim końcu połączenia. Zachowanie się jako dołączonej przyczyny jest najbardziej odpowiednie podczas przekraczania wewnętrznej granicy.

Kolejny wzorzec, który widzisz, to catch-and-release:

try {
    // ...
} catch (FooException e) {
    throw e;
}

Jest to anty-wzorzec, chyba że masz ograniczenia typu z innych catchklauzul, co oznacza, że ​​nie możesz po prostu pozwolić, by wyjątek przeszedł sam. To po prostu brzydka funkcja Java.

Nie ma prawdziwej różnicy między sprawdzonymi wyjątkami a niesprawdzonymi, oprócz faktu, że musisz zadeklarować sprawdzone wyjątki, które przekraczają granice metod. Nadal dobrym pomysłem jest dokumentowanie niesprawdzonych wyjątków (z @throwskomentarzem javadoc), jeśli wiesz, że są one celowo wyrzucane przez Twój kod. Nie celowo wrzucaj java.lang.Errorani jego podklas (chyba że piszesz implementację JVM).

Opinia: Nieoczekiwany przypadek błędu zawsze oznacza błąd w kodzie. Sprawdzone wyjątki są sposobem na zarządzanie tym zagrożeniem, a tam gdzie programiści celowo wykorzystują niesprawdzone wyjątki jako sposób na uniknięcie kłopotów z obsługą przypadków błędów, narastają spory dług techniczny, który będziesz musiał oczyścić przez pewien czas jeśli chcesz solidny kod. Obsługa niechlujnych błędów jest nieprofesjonalna (a spojrzenie na obsługę błędów jest dobrym sposobem na określenie, jak dobry jest naprawdę programista).

Donal Fellows
źródło
0

Myślę, że powinieneś rzucić TYLKO sprawdzony wyjątek, gdy aplikacja może odzyskać. Użytkownicy twojej biblioteki muszą więc wychwycić te wyjątki i zrobić wszystko, co trzeba, aby je odzyskać. Cokolwiek innego powinno być odznaczone.

Jako przykład

Jeśli aplikacja ładuje dane z pliku lub bazy danych. Następnie,..

try {
  File data = new File(...);
  // parse file here
} catch (Exception ex) {
  throw new MissingDataFileException("data file not found");
}

osoba dzwoniąca zawsze może złapać zaznaczony MissingDataFileException, a następnie spróbować załadować dane z bazy danych.

try {
  Connection con = DriverManager.getConnection( host, username, password );
  // query data here
} catch (Exception ex) {
  throw new RuntimeException("better call Saul!");
}
Hendrix
źródło
1
Saul zmarł w wypadku samochodowym 3 lata temu. Zawieść! ;-)
Donal Fellows