W Javie, do czego sprawdzane są wyjątki? [Zamknięte]


14

Sprawdzone wyjątki Javy zyskały złą prasę przez lata. Znamiennym znakiem jest to, że jest to dosłownie jedyny język na świecie, który je ma (nawet inne języki JVM, takie jak Groovy i Scala). Wybitne biblioteki Java, takie jak Spring i Hibernate, również ich nie używają.

Osobiście znalazłem dla nich jedno zastosowanie ( w logice biznesowej między warstwami), ale poza tym jestem wyjątkowym sprawdzonym wyjątkiem.

Czy są inne zastosowania, o których nie wiem?


Wszystkie biblioteki podstawowe Java używają sprawdzonych wyjątków dość szeroko.
Joonas Pulakka

3
@Joonas Wiem, i to jest ból!
Brad Cupit

Odpowiedzi:


22

Przede wszystkim, jak każdy inny paradygmat programowania, musisz zrobić to dobrze, aby działał dobrze.

Dla mnie zaletą sprawdzonych wyjątków jest to, że autorzy biblioteki wykonawczej Java JUŻ zdecydowali dla mnie, jakie typowe problemy można racjonalnie oczekiwać w punkcie wywoływania (w przeciwieństwie do najwyższego poziomu catch-print- die block) i jak najwcześniej zastanowić się, jak poradzić sobie z tymi problemami.

Lubię sprawdzone wyjątki, ponieważ zwiększają one niezawodność mojego kodu, zmuszając mnie do jak najwcześniejszego zastanowienia się nad odzyskiwaniem błędów.

Aby być bardziej precyzyjne, aby mi to sprawia, że mój kod bardziej wydajny, ponieważ zmusza mnie do rozważenia dziwne przypadki narożne bardzo wcześnie w procesie, w przeciwieństwie do powiedzenie „Ups, mój kod nie poradzić, jeśli plik nie istnieje jeszcze” na podstawie błąd w produkcji, który następnie trzeba przerobić kod, aby go obsłużyć. Dodanie obsługi błędów do istniejącego kodu może być trywialnym zadaniem - a zatem kosztownym - podczas przeprowadzania konserwacji, a nie tylko od samego początku.

Może się okazać, że brakuje pliku jest śmiertelną rzeczą i powinno spowodować, że program przestanie działać w płomieniach, ale potem ty podjąć tę decyzję z

} catch (FileNotFoundException e) {
  throw new RuntimeException("Important file not present", e);
}

To także pokazuje bardzo ważny efekt uboczny. Jeśli wstawisz wyjątek, możesz dodać wyjaśnienie, które znajduje się w pliku śledzenia ! Jest to bardzo potężne, ponieważ możesz dodać informacje np. O nazwie brakującego pliku, parametrach przekazanych do tej metody lub innych informacjach diagnostycznych, a informacje te są obecne bezpośrednio w śladzie stosu, który często jest jedną rzeczą dostać się, gdy program się zawiesił.

Ludzie mogą powiedzieć „możemy po prostu uruchomić to w debuggerze w celu odtworzenia”, ale odkryłem, że bardzo często błędy produkcyjne nie mogą być później odtworzone i nie możemy uruchamiać debugerów w produkcji, z wyjątkiem bardzo paskudnych przypadków, w których w grę wchodzi Twoja praca.

Im więcej informacji w stosie śledzenia, tym lepiej. Sprawdzone wyjątki pomagają mi zdobyć te informacje tam wcześnie.


EDYCJA: Dotyczy to również projektantów bibliotek. Jedna biblioteka, z której korzystam na co dzień, zawiera wiele sprawdzonych wyjątków, które można by zaprojektować o wiele lepiej, dzięki czemu korzystanie z niej jest mniej uciążliwe.


+1. Projektanci Spring wykonali kawał dobrej roboty, tłumacząc wiele wyjątków Hibernacji na niesprawdzone wyjątki.
Fil

FYI: Frank Sommers wspomniał w tym wątku, że oprogramowanie odporne na uszkodzenia często opiera się na tym samym mechanizmie. Być może możesz podzielić swoją odpowiedź na przypadek możliwy do odzyskania i przypadek śmiertelny.
rwong

@rwong, czy mógłbyś bardziej szczegółowo wyjaśnić, co masz na myśli`?

11

Masz dwie dobre odpowiedzi, które wyjaśniają, jakie sprawdzone wyjątki stały się w praktyce. (+1 do obu.) Ale warto byłoby również zbadać, do czego były przeznaczone w teorii, ponieważ intencja jest naprawdę opłacalna.

Zaznaczone wyjątki mają na celu zwiększenie bezpieczeństwa języka. Rozważ prostą metodę, taką jak mnożenie liczb całkowitych. Możesz myśleć, że typem wyniku tej metody byłaby liczba całkowita, ale, ściśle mówiąc, wynikiem jest albo liczba całkowita, albo wyjątek przepełnienia. Biorąc pod uwagę wynik całkowity sam w sobie, ponieważ typ zwracany przez metodę nie wyraża pełnego zakresu funkcji.

Patrząc w tym świetle, nie jest prawdą stwierdzenie, że sprawdzone wyjątki nie trafiły do ​​innych języków. Po prostu nie przyjęli formy używanej przez Javę. W aplikacjach Haskell często stosuje się algebraiczne typy danych, aby rozróżnić pomyślne i nieudane zakończenie funkcji. Chociaż nie jest to sam w sobie wyjątek, intencja jest bardzo podobna do sprawdzonego wyjątku; jest to interfejs API zaprojektowany, aby zmusić konsumenta API do obsługi zarówno sukcesu w przypadku niepowodzenia, np .:

data Foo a =
     Success a
   | DidNotWorkBecauseOfA
   | DidNotWorkBecauseOfB

To informuje programistę, że funkcja ma dwa dodatkowe możliwe wyniki oprócz sukcesu.


+1 za sprawdzone wyjątki dotyczące bezpieczeństwa typu. Nigdy wcześniej nie słyszałem o tym pomyśle, ale ma to tyle sensu.
Ixrec,

11

IMO, sprawdzone wyjątki są wadliwą implementacją czegoś, co mogłoby być całkiem przyzwoitym pomysłem (w zupełnie innych okolicznościach - ale tak naprawdę nie jest nawet dobrym pomysłem w obecnych okolicznościach).

Dobrym pomysłem jest to, że dobrze byłoby, gdyby kompilator sprawdził, czy wszystkie wyjątki, które program może wyrzucić, zostaną wychwycone. Wadliwa implementacja polega na tym, że aby to zrobić, Java zmusza cię do radzenia sobie z wyjątkiem bezpośrednio w dowolnej klasie wywołującej wszystko, co zadeklarowano, aby zgłosić wyjątek sprawdzony. Jednym z najbardziej podstawowych pomysłów (i zalet) obsługi wyjątków jest to, że w przypadku błędu pozwala pośrednim warstwom kodu, które nie mają nic wspólnego z konkretnym błędem, całkowicie zignorować jego istnienie. Sprawdzone wyjątki (zaimplementowane w Javie) nie pozwalają na to, a zatem niszczą główną (podstawową?) Zaletę obsługi wyjątków na początek.

Teoretycznie mogliby sprawić, by sprawdzone wyjątki były przydatne, wymuszając je tylko na poziomie całego programu - tzn. Podczas „budowania” programu buduj drzewo wszystkich ścieżek sterowania w programie. Różne węzły w drzewie będą opatrzone adnotacjami z 1) wyjątkami, które mogą wygenerować, i 2) wyjątkami, które mogą wychwycić. Aby upewnić się, że program był poprawny, chodzisz po drzewie, sprawdzając, czy każdy wyjątek zgłoszony na dowolnym poziomie drzewa został przechwycony na wyższym poziomie w drzewie.

Powodem, dla którego tego nie zrobili (i tak sądzę), jest to, że byłoby to niezwykle trudne - w rzeczywistości wątpię, aby ktokolwiek napisał system kompilacji, który mógłby to zrobić dla czegoś innego niż niezwykle proste (graniczy z „zabawką” „) języki / systemy. W przypadku Javy rzeczy takie jak dynamiczne ładowanie klas czynią to jeszcze trudniejszym niż w wielu innych przypadkach. Co gorsza (w przeciwieństwie do C) Java nie jest (przynajmniej normalnie) statycznie kompilowana / powiązana z plikiem wykonywalnym. Oznacza to, że nic w systemie nie ma tak naprawdę globalnej świadomości, aby nawet próbować wykonać takie egzekwowanie, dopóki użytkownik końcowy nie zacznie faktycznie ładować programu, aby go uruchomić.

Wykonanie egzekucji w tym momencie jest niepraktyczne z kilku powodów. Przede wszystkim, nawet w najlepszym przypadku wydłużyłoby czas uruchamiania, które są już jedną z największych, najczęstszych skarg na Javę. Po drugie, aby zrobić wiele dobrego, naprawdę musisz wykryć problem wystarczająco wcześnie, aby programista mógł go naprawić. Gdy użytkownik ładuje program, jest już za późno, aby zrobić wiele dobrego - jedyne, co możesz zrobić, to zignorować problem lub w ogóle odmówić załadowania / uruchomienia programu, ponieważ istnieje ryzyko, że może istnieć wyjątek to nie zostało złapane.

W tej chwili sprawdzone wyjątki są gorsze niż bezużyteczne, ale alternatywne projekty sprawdzonych wyjątków są jeszcze gorsze. Chociaż podstawowy pomysł na sprawdzone wyjątki wydaje się dobry, to naprawdę nie jest zbyt dobry i okazuje się, że jest szczególnie nieodpowiedni dla Javy. W przypadku czegoś takiego jak C ++, który zwykle jest kompilowany / łączony w kompletny plik wykonywalny z niczym innym, jak odbiciem / dynamicznym ładowaniem klas, miałbyś trochę większą szansę (choć, szczerze mówiąc, nawet wtedy egzekwując je poprawnie bez tego samego bałaganu, jaki oni obecnie przyczyna w Javie prawdopodobnie nadal wykraczałaby poza obecny stan wiedzy).


Twoje pierwsze wstępne stwierdzenie o konieczności bezpośredniej obsługi wyjątku jest już fałszywe; możesz po prostu dodać instrukcję throws, która może być typu nadrzędnego. Ponadto, jeśli naprawdę nie uważasz, że musisz sobie z tym poradzić, możesz po prostu przekonwertować go na RuntimeException. IDE często pomaga w obu zadaniach.
Maarten Bodewes

2
@owlstead: Dzięki - prawdopodobnie powinienem był być bardziej ostrożny z tym sformułowaniem. Nie chodziło o to, że trzeba było złapać wyjątek, ale o to, że każdy pośredni poziom kodu musi być świadomy każdego wyjątku, który może przez niego przepłynąć. Co do reszty: posiadanie narzędzia, które pozwala szybko i łatwo zrobić bałagan w kodzie, nie zmienia faktu, że robi bałagan w kodzie.
Jerry Coffin

Sprawdzone wyjątki były przeznaczone na nieprzewidziane sytuacje - przewidywalne i możliwe do odzyskania wyniki inne niż sukces, w odróżnieniu od awarii - nieprzewidywalne problemy niskiego poziomu lub systemowe. FileNotFound lub błąd podczas otwierania połączenia JDBC zostały poprawnie uznane za nieprzewidziane. Niestety projektanci bibliotek Java popełnili całkowity błąd i przypisali wszystkie inne wyjątki IO i SQL również do klasy „sprawdzonej”; pomimo tego, że są prawie całkowicie niemożliwe do odzyskania. literatejava.com/exceptions/…
Thomas W

@owlstead: Zaznaczone wyjątki powinny przenikać przez warstwy pośrednie stosu wywołań tylko wtedy, gdy zostaną wyrzucone w przypadkach , których oczekuje pośredni kod wywołujący . IMHO, sprawdzone wyjątki byłyby dobre, gdyby dzwoniący albo je złapali, albo wskazali, czy są spodziewani ; nieoczekiwane wyjątki powinny być automatycznie zawijane w UnexpectedExceptiontyp pochodzący z RuntimeException. Zauważ, że „rzuty” i „nie oczekują” nie są sprzeczne; jeśli foorzuca BozExceptioni sprawdza bar, to nie znaczy, że oczekuje barrzutu BozException.
supercat

10

Są naprawdę dobre dla irytujących programistów i zachęcania do połykania wyjątków. Połowa punktów wyjątków polega na tym, że masz rozsądny domyślny sposób obsługi błędów i nie musisz myśleć o jawnym propagowaniu warunków błędów, z którymi nie możesz sobie poradzić. (Rozsądnym domyślnym jest fakt, że jeśli nigdy nie poradzisz sobie z błędem nigdzie w swoim kodzie, skutecznie zapewniłeś, że nie może się to zdarzyć, i masz rozsądne wyjście i ślad stosu, jeśli się mylisz.) Sprawdzone wyjątki pokonane to. Czują, że muszą jawnie propagować błędy w C.


4
Easy Fix: throws FileNotFoundException. Hard fix:catch (FileNotFoundException e) { throw RuntimeException(e); }
Thomas Eding,

5

Myślę, że można śmiało powiedzieć, że sprawdzone wyjątki były trochę nieudanym eksperymentem. To, co popełnili błąd, to to, że zerwali warstwy:

  • Moduł A może być zbudowany na module B, gdzie
  • Moduł B może być zbudowany na module C.
  • Moduł C może wiedzieć, jak rozpoznać błąd, oraz
  • Moduł A może wiedzieć, jak obsłużyć błąd.

Ładne rozdzielenie zainteresowanych zostało zerwane, ponieważ teraz wiedza o błędach, które mogły być ograniczone do A i C, teraz musi zostać rozrzucona na B.

Jest miła dyskusja sprawdzonych wyjątków na Wikipedia.

A Bruce Eckel również ma fajny artykuł na ten temat. Oto cytat:

[T] im więcej losowych reguł nakładasz na programistę, reguły, które nie mają nic wspólnego z rozwiązaniem danego problemu, tym wolniej programista może produkować. I to nie wydaje się być czynnikiem liniowym, ale wykładniczym

Uważam, że w przypadku C ++, jeśli wszystkie metody mają throwsklauzulę specyfikacji, możesz mieć jakieś fajne optymalizacje. Nie sądzę jednak, żeby Java mogła mieć te zalety, ponieważ RuntimeExceptionsnie są sprawdzane. W każdym razie nowoczesny JIT powinien być w stanie zoptymalizować kod.

Jedną z fajnych cech AspectJ jest zmiękczanie wyjątków: możesz zamienić sprawdzone wyjątki w niesprawdzone wyjątki, szczególnie w celu zwiększenia warstw.

Być może to ironia losu, że Java przeszła przez te wszystkie kłopoty, gdy mogła nullzamiast tego sprawdzić , co mogło być o wiele bardziej wartościowe .


haha, nigdy nie myślałem o sprawdzaniu wartości zerowej jako decyzji, której nie podjęli ... Ale w końcu zrozumiałem, że NIE jest to wrodzony problem po pracy w Objective-C, który pozwala na wywołanie metod zerowych.
Dan Rosenstark,

3
sprawdzanie wartości zerowej jest znacznie większym bólem - ZAWSZE musisz to sprawdzać - a ponadto nie wyjaśnia przyczyny, co robi dobrze nazwany wyjątek.

5

Kocham wyjątki.

Ciągle jednak jestem oszołomiony ich niewłaściwym użyciem.

  1. Nie używaj ich do obsługi przepływu. Nie chcesz kodu, który próbuje coś zrobić i używa wyjątku jako wyzwalacza do zrobienia czegoś innego, ponieważ Wyjątki to obiekty, które trzeba utworzyć i użyć pamięci itp. Tylko dlatego, że nie mogłeś mieć kłopotów z napisaniem czegoś rodzaj sprawdzania if () przed wykonaniem połączenia. To jest po prostu leniwe i nadaje chwalebnemu wyjątkowi złe imię.

  2. Nie połykaj ich. Zawsze. Pod żadnym pozorem. Jako absolutne minimum należy umieścić coś w pliku dziennika, aby wskazać, że wystąpił wyjątek. Próba śledzenia drogi przez kod, który połyka wyjątki, jest koszmarem. Ponownie, przeklnijcie połykaczy wyjątków za sprowadzenie potężnego wyjątku na kompromitację!

  3. Traktuj wyjątki w czapce OO. Twórz własne obiekty wyjątków z sensownymi hierarchiami. Nakładaj na siebie logikę biznesową i wyjątki. Kiedyś odkrywałem, że gdybym zaczął na nowym obszarze systemu, potrzebowałbym podklasy wyjątków w ciągu pół godziny od kodowania, więc teraz zaczynam od pisania klas wyjątków.

  4. Jeśli piszesz „ramowe” bity kodu, upewnij się, że wszystkie początkowe interfejsy są napisane, aby w razie potrzeby tworzyć własne wyjątki ... i upewnij się, że kodery mają jakiś przykładowy kod w miejscu, co należy zrobić, gdy ich kod zgłasza wyjątek IOException, ale interfejs, do którego piszą, chce zgłosić wyjątek „ReportingApplicationException”.

Po zastanowieniu się, jak sprawić, by Wyjątki działały dla Ciebie, nigdy nie będziesz oglądać się za siebie.


2
+1 za dobre instrukcje. W stosownych przypadkach użyj initCauselub zainicjuj wyjątek z wyjątkiem, który go spowodował. Przykład: Masz narzędzie we / wy, którego potrzebujesz tylko w przypadku niepowodzenia zapisu. Istnieje jednak wiele powodów, dla których zapis może się nie powieść (nie można uzyskać dostępu, błąd zapisu itp.). Zrzuć niestandardowy wyjątek z tego powodu, aby oryginalny problem nie został połknięty.
Michael K,

3
Nie chcę być niegrzeczny, ale co to ma wspólnego z SPRAWDZONYMI wyjątkami? :)
palto

Można przepisać, aby napisać własną hierarchię sprawdzonych wyjątków.
Maarten Bodewes

4

Sprawdzone wyjątki są również w ADA.

(Uwaga, ten post zawiera silne przekonania, z którymi możesz się spotkać.)

Programiści ich nie lubią i narzekają lub piszą kod połykania wyjątków.

Istnieją sprawdzone wyjątki, ponieważ rzeczy mogą nie tylko nie działać, możesz przeprowadzić analizę trybu / efektów awarii i ustalić to z góry.

Odczyty plików mogą się nie powieść. Wywołania RPC mogą zakończyć się niepowodzeniem. Sieć IO może zawieść. Dane mogą być źle sformatowane podczas analizowania.

„Szczęśliwa ścieżka” dla kodu jest łatwa.

Znałem faceta z uniwersytetu, który potrafiłby napisać świetny kod „happy path”. Żaden z przypadków na krawędzi nigdy nie zadziałał. Obecnie robi Python dla firmy open source. Powiedział Nuff.

Jeśli nie chcesz obsługiwać sprawdzonych wyjątków, naprawdę mówisz

While I'm writing this code, I don't want to consider obvious failure modes.

The User will just have to like the program crashing or doing weird things.

But that's okay with me because 

I'm so much more important than the people who will have to use the software

in the real, messy, error-prone world.

After all, I write the code once, you use it all day long.

Tak więc sprawdzone wyjątki nie będą spodobały się programistom, ponieważ oznacza to więcej pracy.

Oczywiście inni ludzie mogliby chcieć, aby ta praca została wykonana.

Być może chcieli właściwej odpowiedzi, nawet jeśli serwer plików ulegnie awarii / pamięć USB umrze.

Dziwne jest przekonanie w społeczności programistów, że powinieneś używać języka programowania, który ułatwia ci życie i sprawia ci przyjemność, gdy twoja praca polega na pisaniu oprogramowania. Twoim zadaniem jest rozwiązanie czyjegoś problemu, nie dopuszczenie do zautomatyzowanej jazzowej improwizacji.

Jeśli jesteś programistą amatorem (nie programujesz dla pieniędzy), możesz programować w C # lub innym języku bez sprawdzonych wyjątków. Heck, wytnij środkowego mężczyznę i program w Logo. Za pomocą żółwia możesz rysować ładne wzory na podłodze.


6
Miło, że tam dotarłeś.
Adam Lear

2
+1 „Żadna z tych spraw nie zadziałała. Obecnie robi Python dla firmy open source. Nuff powiedział.” Klasyczny
NimChimpsky

1
@palto: Java zmusza cię do rozważenia trudnej ścieżki. To duża różnica. W przypadku C # możesz powiedzieć: „Wyjątek jest udokumentowany” i umyj ręce. Ale programiści są podatni na błędy. Zapominają o rzeczach. Kiedy piszę kod, chcę być w 100% przypominany o wszelkich pęknięciach, które kompilator może mi poinformować.
Thomas Eding,

2
@ThomasEding Java zmusza mnie do obsługi wyjątku, w którym wywoływana jest metoda Bardzo rzadko chcę coś z tym zrobić w kodzie wywołującym i zwykle chcę wykreślić wyjątek od warstwy obsługi błędów w mojej aplikacji. Następnie muszę temu zaradzić, dodając wyjątek do wyjątku Runtime lub muszę utworzyć własny wyjątek. Leniwi programiści mają trzecią opcję: zignoruj ​​ją, bo mnie to nie obchodzi. W ten sposób nikt nie wie, że wystąpił błąd, a oprogramowanie ma naprawdę dziwne błędy. Ten trzeci nie dzieje się z niesprawdzonymi wyjątkami.
palto,

3
@ThomasEding W ten sposób zmieni się interfejs wszystkiego powyżej, co niepotrzebnie ujawnia szczegóły implementacji każdej warstwie aplikacji. Możesz to zrobić tylko wtedy, gdy wyjątek jest czymś, co chcesz odzyskać i ma to sens w interfejsie. Nie chcę dodawać wyjątku IOException do mojej metody getPeople, ponieważ może brakować jakiegoś pliku konfiguracyjnego. To po prostu nie ma sensu.
palto

4

Sprawdzone wyjątki powinny zachęcać ludzi do radzenia sobie z błędami w pobliżu źródła. Im dalej od źródła, tym więcej funkcji zakończyło się w trakcie działania i tym bardziej prawdopodobne jest, że system przejdzie w niespójny stan. Ponadto jest bardziej prawdopodobne, że przypadkowo złapiesz niewłaściwy wyjątek.

Niestety ludzie lubili ignorować wyjątki lub dodawać coraz wyższe specyfikacje rzutów. Oba nie mają sensu zachęcać sprawdzone wyjątki. Są jak transformacja kodu proceduralnego, aby korzystać z wielu funkcji Gettera i Settera, co rzekomo czyni go OO.

Zasadniczo sprawdzone wyjątki próbują wykazać pewien stopień poprawności w kodzie. Jest to prawie taki sam pomysł jak pisanie statyczne, ponieważ próbuje udowodnić, że pewne rzeczy są poprawne, zanim nawet uruchomi się kod. Problem polega na tym, że cały ten dodatkowy dowód wymaga wysiłku i czy wysiłek jest opłacalny?


2
„Ignorowanie wyjątków” jest często poprawne - wielokrotnie najlepszą odpowiedzią na wyjątek jest spowodowanie awarii aplikacji. Podstawę teoretyczną tego punktu widzenia można znaleźć w obiektowej konstrukcji oprogramowania Bertrand Meyer . Pokazuje, że jeśli wyjątki są właściwie stosowane (w przypadku naruszeń umów), istnieją zasadniczo dwie prawidłowe odpowiedzi: (1) pozwól aplikacji się zawiesić lub (2) napraw problem, który spowodował wyjątek, i zrestartuj proces. Przeczytaj Meyer, aby uzyskać szczegółowe informacje; trudno jest streścić w komentarzu.
Craig Stuntz

1
@Craig Stuntz, ignorując wyjątki, miałem na myśli ich połknięcie.
Winston Ewert

Ach, to jest inne. Zgadzam się, że nie powinieneś ich połykać.
Craig Stuntz

„Ignorowanie wyjątków jest często poprawne - wielokrotnie najlepszą odpowiedzią na wyjątek jest pozwolenie na awarię aplikacji.”: To naprawdę zależy od aplikacji. Jeśli masz niewyłapany wyjątek w aplikacji sieciowej, najgorsze, co może się zdarzyć, to to, że Twój klient zgłosi komunikat o błędzie na stronie internetowej i możesz wdrożyć naprawę błędu w ciągu kilku minut. Jeśli masz wyjątek podczas lądowania samolotu, lepiej sobie z nim poradzić i spróbuj wyzdrowieć.
Giorgio

@Giorgio, bardzo prawda. Chociaż zastanawiam się, czy w przypadku samolotu lub podobnego, najlepszym sposobem na odzyskanie jest ponowne uruchomienie systemu.
Winston Ewert
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.