Od wielu lat nie jestem w stanie uzyskać porządnej odpowiedzi na następujące pytanie: dlaczego niektórzy programiści tak sprawdzają wyjątki? Przeprowadziłem wiele rozmów, czytałem rzeczy na blogach, czytałem, co powiedział Bruce Eckel (pierwsza osoba, którą widziałem, wypowiadała się przeciwko nim).
Obecnie piszę nowy kod i zwracam szczególną uwagę na sposób postępowania z wyjątkami. Próbuję zobaczyć punkt widzenia tłumu „nie lubimy sprawdzonych wyjątków” i nadal go nie widzę.
Każda rozmowa kończy się tym samym pytaniem, na które nie ma odpowiedzi ... pozwól, że skonfiguruję:
Ogólnie (na podstawie projektu Java),
Error
dotyczy rzeczy, które nigdy nie powinny zostać złapane (VM ma alergię na orzeszki ziemne i ktoś upuścił na nią słoik orzeszków ziemnych)RuntimeException
dotyczy rzeczy, które programista zrobił źle (programista zszedł z końca tablicy)Exception
(z wyjątkiemRuntimeException
) dotyczy rzeczy, które są poza kontrolą programisty (dysk zapełnia się podczas zapisu do systemu plików, limit uchwytu pliku dla procesu został osiągnięty i nie można otworzyć więcej plików)Throwable
jest po prostu rodzicem wszystkich typów wyjątków.
Często słyszę argument, że jeśli zdarzy się wyjątek, to programista zamierza tylko wyjść z programu.
Innym częstym argumentem, jaki słyszę, jest to, że sprawdzone wyjątki utrudniają refaktoryzację kodu.
W przypadku argumentu „wszystko, co zamierzam zrobić, to wyjść” mówię, że nawet jeśli wychodzisz, musisz wyświetlić rozsądny komunikat o błędzie. Jeśli po prostu zastanawiasz się nad obsługą błędów, użytkownicy nie będą zbytnio zadowoleni, gdy program zostanie zamknięty bez wyraźnego wskazania przyczyny.
Dla tłumu „utrudnia refaktoryzację” oznacza to, że nie wybrano właściwego poziomu abstrakcji. Zamiast deklarować, że metoda zgłasza an IOException
, IOException
powinna zostać przekształcona w wyjątek, który jest bardziej odpowiedni dla tego, co się dzieje.
Nie mam problemu z opakowaniem Main za pomocą catch(Exception)
(lub w niektórych przypadkach, catch(Throwable)
aby program mógł wyjść z gracją z wdziękiem - ale zawsze wychwytuję określone wyjątki, których potrzebuję. W ten sposób mogę przynajmniej wyświetlić odpowiedni Komunikat o błędzie.
Pytanie, na które ludzie nigdy nie odpowiadają, brzmi:
Jeśli rzucasz
RuntimeException
podklasy zamiastException
podklas, to skąd wiesz, co masz złapać?
Jeśli odpowiedź jest złapana, Exception
masz również do czynienia z błędami programisty w taki sam sposób jak wyjątki systemowe. Wydaje mi się to złe.
Jeśli złapiesz Throwable
, traktujesz wyjątki systemowe i błędy maszyn wirtualnych (i tym podobne) w ten sam sposób. Wydaje mi się to złe.
Jeśli odpowiedź brzmi, że wychwytujesz tylko wyjątki, o których wiesz, że są zgłaszane, to skąd wiesz, jakie są zgłaszane? Co się stanie, gdy programista X zgłasza nowy wyjątek i zapomniał go złapać? To wydaje mi się bardzo niebezpieczne.
Powiedziałbym, że program, który wyświetla ślad stosu, jest zły. Czy ludzie, którzy nie lubią sprawdzonych wyjątków, nie czują się w ten sposób?
Jeśli więc nie lubisz sprawdzonych wyjątków, czy możesz wyjaśnić, dlaczego NIE ORAZ odpowiedzieć na pytanie, na które nie ma odpowiedzi, proszę?
Edycja: Nie szukam porady, kiedy użyć któregoś z modeli, szukam tego, dlaczego ludzie się przedłużają, RuntimeException
ponieważ nie lubią przedłużać się Exception
i / lub dlaczego wychwytują wyjątek, a następnie ponownie RuntimeException
rzucają, a nie dodają rzuty do ich metoda. Chcę zrozumieć motywację, by nie lubić sprawdzonych wyjątków.
źródło
Odpowiedzi:
Myślę, że przeczytałem ten sam wywiad z Brucem Eckelem, który przeprowadziłeś - i zawsze mnie to denerwowało. W rzeczywistości argument został wysunięty przez rozmówcę (jeśli rzeczywiście jest to post, o którym mówisz) Anders Hejlsberg, genialny MS za .NET i C #.
Choć jestem fanem Hejlsberga i jego twórczości, ten argument zawsze wydawał mi się fałszywy. Zasadniczo sprowadza się do:
Przez „inaczej przedstawione użytkownikowi” mam na myśli to, że jeśli użyjesz wyjątku środowiska wykonawczego, leniwy programista po prostu go zignoruje (zamiast złapać go z pustym blokiem catch) i użytkownik go zobaczy.
Podsumowanie argumentu jest takie, że „programiści nie będą ich używać poprawnie, a ich niewłaściwe użycie jest gorsze niż ich brak” .
Argument ten zawiera pewną prawdę i podejrzewam, że motywacja Goslingów do nieprzekazywania operatorów w Javie wynika z podobnego argumentu - mylą programistę, ponieważ są często wykorzystywani.
Ale ostatecznie uważam, że jest to fałszywy argument Hejlsberga i być może post-hoc stworzony w celu wyjaśnienia braku, a nie przemyślana decyzja.
Twierdziłbym, że podczas gdy nadmierne wykorzystanie sprawdzonych wyjątków jest złą rzeczą i prowadzi do niechlujnej obsługi przez użytkowników, ale ich właściwe użycie pozwala programistom API dać ogromne korzyści programistom klienta API.
Teraz programista API musi uważać, aby nie rzucać sprawdzonymi wyjątkami w inne miejsce, w przeciwnym razie po prostu drażnią programistę klienta. Bardzo leniwy programista kliencki
(Exception) {}
ucieknie się do złapania, gdy Hejlsberg ostrzega, a wszelkie korzyści zostaną utracone i nastąpi piekło. Ale w niektórych okolicznościach dobry sprawdzony wyjątek po prostu nie zastąpi.Dla mnie klasycznym przykładem jest interfejs API do otwierania plików. Każdy język programowania w historii języków (przynajmniej w systemach plików) ma gdzieś interfejs API, który pozwala otworzyć plik. I każdy programista kliencki korzystający z tego interfejsu API wie, że musi sobie poradzić ze sprawą, że plik, który próbują otworzyć, nie istnieje. Pozwólcie, że sformułuję to: Każdy programista klienta korzystający z tego interfejsu API powinien wiedzieć , że musi poradzić sobie z tą sprawą. I jest rub: czy programista API może pomóc im wiedzieć, że powinni sobie z tym poradzić, komentując samemu, czy też może nalegać aby klient zajął się tym.
W języku C idiom wygląda mniej więcej tak
gdzie
fopen
oznacza niepowodzenie, zwracając 0, a C (głupio) pozwala traktować 0 jako wartość logiczną i ... Zasadniczo uczysz się tego idiomu i wszystko w porządku. Ale co jeśli jesteś noobem i nie nauczyłeś się tego idiomu. Potem oczywiście zaczynaszi uczyć się na własnej skórze.
Zauważ, że mówimy tu tylko o silnie typowanych językach: Istnieje jasne pojęcie o tym, czym jest interfejs API w silnie typowanym języku: To jest zestaw funkcji (metod) do użycia z jasno zdefiniowanym protokołem dla każdego z nich.
Ten jasno zdefiniowany protokół jest zazwyczaj definiowany przez sygnaturę metody. Tutaj fopen wymaga, abyś przekazał mu ciąg (lub znak * w przypadku C). Jeśli podasz coś innego, pojawi się błąd kompilacji. Nie przestrzegałeś protokołu - nie używasz poprawnie interfejsu API.
W niektórych (niejasnych) językach typ zwracany jest również częścią protokołu. Jeśli spróbujesz wywołać odpowiednik
fopen()
w niektórych językach bez przypisywania go do zmiennej, otrzymasz również błąd czasu kompilacji (możesz to zrobić tylko przy użyciu funkcji void).Chodzi mi o to, że: w języku o typie statycznym programista API zachęca klienta do właściwego używania interfejsu API, uniemożliwiając kompilację kodu klienta, jeśli popełni oczywiste błędy.
(W dynamicznie pisanym języku, takim jak Ruby, możesz przekazać wszystko, na przykład float, jako nazwę pliku - i to się skompiluje. Po co męczyć użytkownika sprawdzonymi wyjątkami, jeśli nawet nie zamierzasz kontrolować argumentów metody. przedstawione tutaj argumenty dotyczą tylko języków o typie statycznym).
A co ze sprawdzonymi wyjątkami?
Oto jeden z interfejsów API Java, których można użyć do otwarcia pliku.
Widzisz ten haczyk? Oto podpis tej metody API:
Uwaga:
FileNotFoundException
jest to sprawdzony wyjątek.Programista API mówi ci: „Możesz użyć tego konstruktora, aby utworzyć nowy FileInputStream, ale Ty
a) musi przekazać nazwę pliku jako ciąg
b) musi zaakceptować możliwość, że pliku nie można znaleźć w czasie wykonywania „
I o to mi chodzi.
Kluczem jest w zasadzie to, co pytanie brzmi: „Rzeczy, które są poza kontrolą programisty”. Moja pierwsza myśl była taka, że on / ona ma na myśli rzeczy, które są poza API kontrolą programistów . Ale tak naprawdę sprawdzone wyjątki, gdy są właściwie używane, powinny naprawdę dotyczyć rzeczy, które są poza kontrolą programisty klienta i programisty API. Myślę, że to klucz do nie nadużywania sprawdzonych wyjątków.
Myślę, że otwieranie plików dobrze ilustruje tę kwestię. Programista API wie, że możesz nadać im nazwę pliku, która okaże się nieistniejąca w momencie wywołania API i że nie będzie w stanie zwrócić ci tego, co chciałeś, ale będzie musiał zgłosić wyjątek. Wiedzą również, że zdarza się to dość regularnie i że programista klienta może oczekiwać, że nazwa pliku będzie poprawna w momencie, w którym napisał wywołanie, ale może być niepoprawny w czasie wykonywania z powodów niezależnych od nich.
Interfejs API wyraźnie to wyjaśnia: będą przypadki, w których ten plik nie będzie istniał w momencie, gdy do mnie zadzwonisz, a ty lepiej sobie z nim poradzić.
Byłoby to wyraźniejsze w przypadku kontr-przypadku. Wyobraź sobie, że piszę interfejs API tabeli. Mam gdzieś model tabeli z interfejsem API zawierającym tę metodę:
Teraz jako programista API wiem, że będą przypadki, w których niektórzy klienci przekażą wartość ujemną dla wiersza lub wartości wiersza poza tabelą. Mogę więc kusić się, aby rzucić sprawdzony wyjątek i zmusić klienta do zajęcia się nim:
(Oczywiście nie nazwałbym tego „sprawdzonym”).
Jest to złe użycie sprawdzonych wyjątków. Kod klienta będzie pełen wywołań do pobierania danych wierszy, z których każde będzie musiało użyć try / catch i po co? Czy zamierzają zgłosić użytkownikowi, że szukano niewłaściwego wiersza? Prawdopodobnie nie - ponieważ niezależnie od interfejsu użytkownika otaczającego mój widok tabeli, nie powinien pozwolić użytkownikowi wejść w stan, w którym żądany jest nielegalny wiersz. Jest to więc błąd programisty klienta.
Programista API nadal może przewidzieć, że klient będzie kodować takie błędy i powinien obsłużyć je z wyjątkiem środowiska wykonawczego takiego jak
IllegalArgumentException
.Z zaznaczonym wyjątkiem
getRowData
jest to wyraźnie przypadek, który doprowadzi leniwego programistę Hejlsberga do dodania pustych połowów. Gdy tak się stanie, niedozwolone wartości wierszy nie będą oczywiste nawet dla debugującego testera lub dewelopera klienta, a raczej doprowadzą do błędów domieszkowych, których trudno jest wskazać źródło. Rakiety Arianne wybuchną po starcie.Okej, więc jest problem: mówię, że sprawdzony wyjątek
FileNotFoundException
jest nie tylko dobrą rzeczą, ale niezbędnym narzędziem w zestawie narzędzi programistów API do definiowania API w najbardziej użyteczny sposób dla programisty klienta. JestCheckedInvalidRowNumberException
to jednak duża niedogodność, która prowadzi do złego programowania i należy jej unikać. Ale jak odróżnić.Wydaje mi się, że nie jest to nauka ścisła i sądzę, że leży ona u podstaw i być może uzasadnia do pewnego stopnia argument Hejlsberga. Ale nie cieszę się, że tutaj wylewam dziecko z kąpielą, więc pozwólcie mi wyodrębnić kilka zasad, aby odróżnić sprawdzone wyjątki od złych:
Poza kontrolą klienta lub Zamknięty kontra Otwarty:
Zaznaczone wyjątki należy stosować tylko wtedy, gdy przypadek błędu wymyka się spod kontroli zarówno API, jak i programisty klienta. Ma to związek z tym, jak otwarty lub zamknięty jest system. W ograniczonym interfejsie użytkownika, w którym programista klienta ma kontrolę, powiedzmy, nad wszystkimi przyciskami, poleceniami klawiatury itp., Które dodają i usuwają wiersze z widoku tabeli (system zamknięty), jest to błąd programistyczny klienta, jeśli próbuje pobrać dane z nieistniejący rząd. W systemie operacyjnym opartym na plikach, w którym dowolna liczba użytkowników / aplikacji może dodawać i usuwać pliki (system otwarty), możliwe jest, że plik, o który prosi klient, został usunięty bez ich wiedzy, dlatego należy się spodziewać, że poradzi sobie z tym .
Wszechobecność:
Sprawdzone wyjątki nie powinny być używane w wywołaniach API często wykonywanych przez klienta. Przez często mam na myśli wiele miejsc w kodzie klienta - niezbyt często na czas. Więc kod klienta nie próbuje często otwierać tego samego pliku, ale mój widok tabeli jest
RowData
wszędzie z różnych metod. W szczególności zamierzam pisać dużo kodui bolesne będzie za każdym razem owijanie w try / catch.
Informowanie użytkownika:
Sprawdzone wyjątki należy stosować w przypadkach, w których można sobie wyobrazić przydatny komunikat o błędzie wyświetlany użytkownikowi końcowemu. To jest „i co zrobisz, kiedy to się stanie?” pytanie, które zadałem powyżej. Odnosi się to również do punktu 1. Ponieważ możesz przewidzieć, że coś poza twoim systemem klienta-API może spowodować, że pliku nie będzie, możesz rozsądnie poinformować o tym użytkownika:
Ponieważ twój nielegalny numer wiersza został spowodowany wewnętrznym błędem i nie z winy użytkownika, naprawdę nie możesz podać żadnych przydatnych informacji. Jeśli Twoja aplikacja nie przepuści wyjątków środowiska wykonawczego do konsoli, prawdopodobnie skończy się to brzydkim komunikatem:
Krótko mówiąc, jeśli nie uważasz, że programista klienta może wyjaśnić twój wyjątek w sposób, który pomaga użytkownikowi, prawdopodobnie nie powinieneś używać sprawdzonego wyjątku.
To są moje zasady. Nieco wymyślone i bez wątpienia będą wyjątki (proszę pomóż mi je ulepszyć, jeśli chcesz). Ale moim głównym argumentem jest to, że istnieją przypadki, w
FileNotFoundException
których sprawdzany wyjątek jest równie ważny i użyteczny jako część umowy API, jak typy parametrów. Nie powinniśmy więc rezygnować z tego tylko dlatego, że jest niewłaściwie używane.Przepraszam, nie chciałem robić tego tak długo i głupio. Zakończę dwiema sugestiami:
Odp .: Programiści API: oszczędnie używają sprawdzonych wyjątków, aby zachować ich użyteczność. W razie wątpliwości użyj niezaznaczonego wyjątku.
B: Programiści klienccy: we wczesnym etapie tworzenia nawiniętego wyjątku (google it). JDK 1.4 i nowsze zawierają w
RuntimeException
tym celu konstruktora , ale możesz też łatwo stworzyć własny. Oto konstruktor:Następnie przyzwyczaj się do tego, gdy musisz obsłużyć sprawdzony wyjątek i czujesz się leniwy (lub myślisz, że programista API nadgorliwie używał sprawdzonego wyjątku w pierwszej kolejności), nie połykaj wyjątku, zawiń go i wrzućcie go ponownie.
Umieść to w jednym z małych szablonów kodu IDE i używaj go, gdy czujesz się leniwy. W ten sposób, jeśli naprawdę chcesz obsłużyć sprawdzony wyjątek, będziesz zmuszony wrócić i rozwiązać problem po zobaczeniu problemu w czasie wykonywania. Ponieważ, wierzcie mi (i Andersowi Hejlsbergowi), nigdy nie wrócicie do tego TODO w swoim
źródło
Sprawdzone wyjątki polegają na tym, że tak naprawdę nie są wyjątkami w zwykłym rozumieniu tego pojęcia. Zamiast tego są alternatywnymi wartościami zwracanymi przez API.
Cała idea wyjątków polega na tym, że błąd zgłoszony gdzieś w dół łańcucha połączeń może pęknąć i zostać obsłużony przez kod gdzieś wyżej, bez ingerencji kodu. Z drugiej strony sprawdzone wyjątki wymagają, aby każdy poziom kodu między rzucającym a łapaczem deklarował, że wie o wszystkich formach wyjątków, które mogą przez nie przejść. To naprawdę niewiele różni się w praktyce od tego, czy sprawdzone wyjątki były po prostu specjalnymi wartościami zwracanymi, które sprawdzający musiał sprawdzić. np. [pseudokod]:
Ponieważ Java nie może wykonywać alternatywnych wartości zwracanych ani prostych krotek wbudowanych jako wartości zwracanych, sprawdzone wyjątki są rozsądną odpowiedzią.
Problem polega na tym, że wiele kodu, w tym wielkie zbiory standardowej biblioteki, niewłaściwie używało sprawdzonych wyjątków dla naprawdę wyjątkowych warunków, które możesz chcieć złapać kilka poziomów wyżej. Dlaczego IOException nie jest RuntimeException? W każdym innym języku mogę pozwolić, aby wystąpił wyjątek we / wy, a jeśli nie zrobię nic, aby go obsłużyć, moja aplikacja zatrzyma się i dostanę przydatny ślad stosu do obejrzenia. To najlepsza rzecz, jaka może się zdarzyć.
Może dwie metody powyżej przykładu, w której chcesz wychwycić wszystkie wyjątki IO z całego procesu zapisu do strumienia, przerwać proces i przejść do kodu raportowania błędów; w Javie nie można tego zrobić bez dodawania „rzuca wyjątek IOException” na każdym poziomie połączenia, nawet na poziomach, które same nie wykonują IO. Takie metody nie powinny wymagać wiedzy na temat obsługi wyjątków; dodając wyjątki do swoich podpisów:
I jest jeszcze wiele niedorzecznych wyjątków bibliotecznych, takich jak:
Kiedy musisz zaśmiecać swój kod tak absurdalnym dowcipem, nic dziwnego, że sprawdzone wyjątki są pełne nienawiści, chociaż tak naprawdę jest to po prostu kiepski projekt API.
Innym szczególnym złym skutkiem jest odwrócenie kontroli, gdzie komponent A dostarcza wywołanie zwrotne do ogólnego komponentu B. Komponent A chce być w stanie pozwolić wyjątkowi wyrzucić swój zwrot z powrotem do miejsca, w którym nazwał komponent B, ale nie może ponieważ zmieniłoby to interfejs wywołania zwrotnego, który jest naprawiony przez B. A może to zrobić tylko poprzez zawinięcie prawdziwego wyjątku w RuntimeException, który jest jeszcze bardziej wymagający do zapisania.
Sprawdzone wyjątki zaimplementowane w Javie i jej standardowej bibliotece oznaczają płytę kotłową, płytę kotłową, płytę kotłową. W już pełnym języku to nie jest wygrana.
źródło
Zamiast ponownie przeanalizować wszystkie (wiele) powodów przeciwko sprawdzonym wyjątkom, wybiorę tylko jeden. Nie pamiętam, ile razy napisałem ten blok kodu:
99% czasu nie mogę nic na to poradzić. Wreszcie bloki wykonują wszelkie niezbędne porządki (a przynajmniej powinny).
Straciłem również liczbę wyświetleń:
Dlaczego? Ponieważ ktoś musiał sobie z tym poradzić i był leniwy. Czy to było złe? Pewnie. Czy to sie zdarza Absolutnie. Co jeśli byłby to niesprawdzony wyjątek? Aplikacja właśnie umarła (co jest lepsze niż połknięcie wyjątku).
A potem mamy irytujący kod, który wykorzystuje wyjątki jako formę kontroli przepływu, tak jak robi to java.text.Format . Bzzzt. Źle. Użytkownik wstawiający „abc” w pole liczbowe w formularzu nie jest wyjątkiem.
Ok, chyba trzy przyczyny.
źródło
Wiem, że to stare pytanie, ale spędziłem trochę czasu walcząc ze sprawdzonymi wyjątkami i muszę coś dodać. Proszę wybacz mi jej długość!
Moją główną wołowiną ze sprawdzonymi wyjątkami jest to, że rujnują polimorfizm. Niemożliwe jest, aby grały ładnie z interfejsami polimorficznymi.
Weź dobry stary
List
interfejs Java . Mamy wspólne implementacje w pamięci, takie jakArrayList
iLinkedList
. Mamy również klasę szkieletową,AbstractList
która ułatwia projektowanie nowych typów list. W przypadku listy tylko do odczytu musimy zaimplementować tylko dwie metody:size()
iget(int index)
.Ta przykładowa
WidgetList
klasa odczytuje niektóre obiekty o stałym rozmiarze typuWidget
(nie pokazano) z pliku:Udostępniając widżety za pomocą znanego
List
interfejsu, możesz pobierać elementy (list.get(123)
) lub iterować listę (for (Widget w : list) ...
) bez konieczności posiadania wiedzy oWidgetList
sobie. Tę listę można przekazać dowolnej standardowej metodzie, która korzysta z list ogólnych, lub zawinąć ją w plikCollections.synchronizedList
. Kod, który go używa, nie musi wiedzieć ani dbać o to, czy „widżety” są tworzone na miejscu, pochodzą z tablicy, czy są odczytywane z pliku, bazy danych, z sieci lub z przyszłego przekaźnika podprzestrzeni. Będzie nadal działać poprawnie, ponieważList
interfejs jest poprawnie zaimplementowany.Tyle że nie jest. Powyższa klasa nie kompiluje się, ponieważ metody dostępu do plików mogą zgłaszać
IOException
sprawdzony wyjątek, który należy „złapać lub określić”. Nie możesz podać go jako wyrzuconego - kompilator ci na to nie pozwoli, ponieważ naruszałoby to umowęList
interfejsu. I nie ma użytecznego sposobu, któryWidgetList
mógłby sam poradzić sobie z wyjątkiem (jak wyjaśnię później).Najwyraźniej jedyną rzeczą do zrobienia jest złapanie i ponowne sprawdzenie sprawdzonych wyjątków jako jakiś niezaznaczony wyjątek:
((Edycja: Java 8 dodała
UncheckedIOException
klasę do dokładnie tego przypadku: do łapania i ponownego rzucaniaIOException
s przez granice metod polimorficznych. To potwierdza mój punkt!)Sprawdzone wyjątki po prostu nie działają w takich przypadkach. Nie możesz ich wyrzucić. To samo dotyczy sprytnego
Map
wsparcia bazy danych lub implementacjijava.util.Random
podłączonej do źródła kwantowej entropii za pośrednictwem portu COM. Gdy tylko spróbujesz zrobić coś nowego z implementacją interfejsu polimorficznego, koncepcja sprawdzonych wyjątków zawiedzie. Ale sprawdzone wyjątki są tak podstępne, że nadal nie pozostawiają cię w spokoju, ponieważ nadal musisz złapać i odrzucić dowolną z metod niższego poziomu, zaśmiecając kod i zaśmiecając stos.Uważam, że wszechobecny
Runnable
interfejs jest często cofany w tym rogu, jeśli wywołuje coś, co rzuca sprawdzone wyjątki. Nie może rzucać wyjątku w takiej formie, w jakiej jest, więc wszystko, co może zrobić, to zaśmiecać kod, przechwytując i ponownie pisząc jakoRuntimeException
.W rzeczywistości możesz rzucać niezadeklarowane sprawdzone wyjątki, jeśli uciekasz się do hacków. JVM w czasie wykonywania nie przejmuje się sprawdzonymi regułami wyjątków, dlatego musimy oszukać tylko kompilator. Najprostszym sposobem na to jest nadużywanie leków generycznych. Oto moja metoda (nazwa klasy pokazana, ponieważ (przed Java 8) jest wymagana w składni wywołującej dla metody ogólnej):
Hurra! Za pomocą tego możemy rzucić sprawdzony wyjątek na dowolną głębokość stosu bez deklarowania go, bez owijania go
RuntimeException
i bez zaśmiecania śladu stosu! Korzystając ponownie z przykładu „WidgetList”:Niestety, ostatnią zniewagą sprawdzonych wyjątków jest to, że kompilator odmawia zezwolenia na złapanie sprawdzonego wyjątku, jeśli jego błędna opinia nie mogła zostać wyrzucona. (Niezaznaczone wyjątki nie mają tej reguły.) Aby złapać podstępnie zgłoszony wyjątek, musimy to zrobić:
Jest to nieco niezręczne, ale na plus jest wciąż nieco prostsze niż kod do wyodrębnienia sprawdzonego wyjątku, który został zawinięty w a
RuntimeException
.Na szczęście
throw t;
instrukcja jest tutaj legalna, nawet jeśli typt
jest sprawdzany, dzięki dodanej w Javie 7 regule dotyczącej ponownego zgłaszania przechwyconych wyjątków.Gdy sprawdzone wyjątki spotykają polimorfizm, problemem jest również odwrotny przypadek: gdy metoda jest określona jako potencjalnie rzucająca sprawdzony wyjątek, ale zastąpiona implementacja nie. Na przykład, klasa abstrakcyjna
OutputStream
„swrite
metody wszystkim określićthrows IOException
.ByteArrayOutputStream
jest podklasą, która zapisuje do tablicy w pamięci zamiast prawdziwego źródła we / wy. Jego przesłoniętewrite
metody nie mogą powodowaćIOException
s, więc nie mająthrows
klauzuli i można je wywoływać, nie martwiąc się o wymaganie catch-or-spec.Z wyjątkiem nie zawsze. Załóżmy, że
Widget
istnieje metoda zapisania go w strumieniu:Właściwe jest zadeklarowanie tej metody jako akceptacji zwykłego
OutputStream
, więc można jej używać polimorficznie z wszelkiego rodzaju wyjściami: plikami, bazami danych, siecią i tak dalej. I tablice w pamięci. W przypadku tablicy w pamięci istnieje jednak fałszywy wymóg obsługi wyjątku, który w rzeczywistości nie może się zdarzyć:Jak zwykle przeszkadzają sprawdzone wyjątki. Jeśli twoje zmienne są zadeklarowane jako typ podstawowy, który ma więcej otwartych wymagań dotyczących wyjątków, musisz dodać procedury obsługi tych wyjątków, nawet jeśli wiesz, że nie wystąpią one w Twojej aplikacji.
Ale czekaj, sprawdzone wyjątki są w rzeczywistości tak irytujące, że nawet nie pozwolą ci zrobić odwrotnie! Wyobraź sobie, że obecnie łapiesz wszystkie
IOException
wyrzucane przezwrite
połączenia naOutputStream
, ale chcesz zmienić typ deklarowanej zmiennej na aByteArrayOutputStream
, kompilator będzie cię krytykować za próbę złapania sprawdzonego wyjątku, o którym mówi, że nie można go wyrzucić.Ta reguła powoduje pewne absurdalne problemy. Na przykład jedna z trzech
write
metod nieOutputStream
jest zastępowana przez . W szczególności jest to wygodna metoda, która zapisuje pełną tablicę, wywołując z przesunięciem 0 i długością tablicy. przesłania metodę trzyargumentową, ale dziedziczy metodę argumentacji wygodnej dla jednego argumentu taką, jaka jest. Dziedziczona metoda robi dokładnie to, co należy, ale zawiera niepożądaną klauzulę. Być może był to niedopatrzenie w projekcie , ale nigdy nie mogą go naprawić, ponieważ złamałoby to kompatybilność źródła z dowolnym kodem, który przechwytuje wyjątek - wyjątek, który nigdy nie jest, nigdy nie będzie i nigdy nie zostanie zgłoszony!ByteArrayOutputStream
write(byte[] data)
write(byte[] data, int offset, int length)
ByteArrayOutputStream
throws
ByteArrayOutputStream
Ta zasada jest irytująca podczas edycji i debugowania. Na przykład, czasami tymczasowo skomentuję wywołanie metody, a jeśli mogłoby ono zgłosić sprawdzony wyjątek, kompilator będzie teraz narzekał na istnienie lokalnego
try
icatch
bloków. Więc też muszę je skomentować, a teraz podczas edycji kodu IDE będzie wciskać do niewłaściwego poziomu, ponieważ{
i}
są komentowane. Gah! To niewielka skarga, ale wygląda na to, że jedyną rzeczą sprawdzoną w wyjątkach są kłopoty.Już prawie skończyłem. Moja ostatnia frustracja sprawdzonymi wyjątkami polega na tym, że w większości witryn z telefonami nie ma nic użytecznego, co można z nimi zrobić. Idealnie, gdy coś pójdzie nie tak, dysponujemy kompetentnym, specyficznym dla aplikacji programem obsługi, który może poinformować użytkownika o problemie i / lub zakończyć lub ponowić operację w razie potrzeby. Może to zrobić tylko przewodnik z wysokiego stosu, ponieważ tylko on zna ogólny cel.
Zamiast tego otrzymujemy następujący idiom, który jest szalony jako sposób na zamknięcie kompilatora:
W GUI lub automatycznym programie drukowana wiadomość nie będzie widoczna. Co gorsza, leci wraz z resztą kodu po wyjątku. Czy wyjątek nie jest tak naprawdę błędem? Więc nie drukuj. W przeciwnym razie za chwilę wybuchnie coś innego, do tego czasu oryginalny obiekt wyjątku zniknie. Ten idiom nie jest lepszy niż BASIC
On Error Resume Next
lub PHPerror_reporting(0);
.Wywołanie jakiejś klasy loggera nie jest dużo lepsze:
Jest to tak samo leniwe, jak
e.printStackTrace();
i nadal działa z kodem w nieokreślonym stanie. Ponadto wybór konkretnego systemu rejestrowania lub innej procedury obsługi jest specyficzny dla aplikacji, więc utrudnia to ponowne użycie kodu.Ale poczekaj! Istnieje łatwy i uniwersalny sposób na znalezienie modułu obsługi specyficznego dla aplikacji. Jest wyżej w stosie wywołań (lub jest ustawiony jako nieprzechwycony moduł obsługi wyjątków w wątku ). Więc w większości miejsc wszystko, co musisz zrobić, to rzucić wyjątek wyżej na stos . Np
throw e;
. Sprawdzane wyjątki po prostu przeszkadzają.Jestem pewien, że sprawdzone wyjątki wydawały się dobrym pomysłem, gdy język został zaprojektowany, ale w praktyce uważam, że wszystkie one przeszkadzają i nie mają żadnej korzyści.
źródło
RuntimeException
. Zauważ, że procedura może być jednocześnie zadeklarowana jako,throws IOException
a jednocześnie określać, że wszelkieIOException
wyrzucone z zagnieżdżonego wywołania należy uznać za nieoczekiwane i opakowane.if (false)
do tego używać . Pozwala to uniknąć problemu z klauzulą rzucania, a ostrzeżenie pomaga mi szybciej nawigować wstecz. +++ Powiedziawszy, zgadzam się ze wszystkim, co napisałeś. Sprawdzone wyjątki mają pewną wartość, ale ta wartość jest nieistotna w porównaniu do ich kosztu. Prawie zawsze przeszkadzają.Cóż, nie chodzi o wyświetlanie śladu stosu lub dyskretne zawieszanie się. Chodzi o możliwość komunikowania błędów między warstwami.
Problem ze sprawdzonymi wyjątkami polega na tym, że zachęcają ludzi do połykania ważnych szczegółów (mianowicie klasy wyjątków). Jeśli zdecydujesz się nie połykać tego szczegółu, musisz ciągle dodawać deklaracje dotyczące rzutów w całej aplikacji. Oznacza to 1), że nowy typ wyjątku wpłynie na wiele sygnatur funkcji, i 2) możesz pominąć konkretną instancję wyjątku, który faktycznie chcesz - złapać (powiedz, że otwierasz plik pomocniczy dla funkcji, która zapisuje dane do plik pomocniczy jest opcjonalny, więc możesz zignorować jego błędy, ale ponieważ podpis
throws IOException
jest łatwy do przeoczenia).Naprawdę mam teraz do czynienia z tą sytuacją w aplikacji. Prawie wyjątki przepakowaliśmy jako AppSpecificException. Dzięki temu podpisy były naprawdę czyste i nie musieliśmy się martwić eksplozją
throws
podpisów.Oczywiście teraz musimy specjalizować się w obsłudze błędów na wyższych poziomach, wdrażając logikę ponownych prób i tym podobne. Wszystko jest jednak AppSpecificException, więc nie możemy powiedzieć „Jeśli zostanie zgłoszony wyjątek IOExe, spróbuj ponownie” lub „Jeśli zgłoszony zostanie ClassNotFound, przerwij całkowicie”. Nie mamy niezawodnego sposobu dotarcia do prawdziwego wyjątku, ponieważ rzeczy są ponownie pakowane, gdy przechodzą między naszym kodem a kodem strony trzeciej.
Dlatego jestem wielkim fanem obsługi wyjątków w Pythonie. Możesz złapać tylko to, co chcesz i / lub poradzić sobie. Wszystko inne pęka, jakbyś sam go rzucił (co i tak zrobiłeś).
Od czasu do czasu zauważyłem, że w całym projekcie, o którym wspominałem, obsługa wyjątków dzieli się na 3 kategorie:
źródło
throws
deklaracji.SNR
Po pierwsze, sprawdzone wyjątki zmniejszają „stosunek sygnału do szumu” dla kodu. Anders Hejlsberg mówi także o programowaniu imperatywnym i deklaratywnym, które jest podobną koncepcją. W każdym razie rozważ następujące fragmenty kodu:
Zaktualizuj interfejs użytkownika z nie-wątku interfejsu użytkownika w Javie:
Zaktualizuj interfejs użytkownika z wątku innego niż interfejs użytkownika w języku C #:
Co wydaje mi się o wiele wyraźniejsze. Kiedy zaczniesz robić coraz więcej pracy z interfejsem użytkownika w Swing, sprawdzone wyjątki stają się naprawdę denerwujące i bezużyteczne.
Jail Break
Aby wdrożyć nawet najbardziej podstawowe implementacje, takie jak interfejs listy Java, zaznaczono wyjątki jako narzędzie do projektowania według umowy. Rozważ listę, która jest wspierana przez bazę danych, system plików lub inną implementację, która zgłasza sprawdzony wyjątek. Jedyną możliwą implementacją jest złapanie zaznaczonego wyjątku i ponowne zwrócenie go jako niesprawdzonego wyjątku:
A teraz musisz zapytać, jaki jest sens tego całego kodu? Sprawdzone wyjątki po prostu powodują hałas, wyjątek został wychwycony, ale nie został obsłużony, a projekt na podstawie umowy (pod względem sprawdzonych wyjątków) się zepsuł.
Wniosek
Pisałem o tym wcześniej .
źródło
Artima opublikował wywiad z jednym z architektów .NET, Andersiem Hejlsbergiem, który ostro omawia argumenty przeciwko sprawdzonym wyjątkom. Krótki degustator:
źródło
Początkowo zgodziłem się z tobą, ponieważ zawsze byłem za sprawdzonymi wyjątkami i zacząłem zastanawiać się, dlaczego nie lubię nie sprawdzać wyjątków w .Net. Ale potem zdałem sobie sprawę, że nie mam wpływu na sprawdzone wyjątki.
Aby odpowiedzieć na twoje pytanie, tak, podoba mi się, że moje programy pokazują ślady stosu, najlepiej naprawdę brzydkie. Chcę, aby aplikacja eksplodowała w straszną stertę najbrzydszych komunikatów o błędach, jakie kiedykolwiek chciałeś zobaczyć.
A powodem jest to, że jeśli to zrobi, muszę to naprawić i muszę to naprawić od razu. Chcę natychmiast wiedzieć, że jest problem.
Ile razy faktycznie zajmujesz się wyjątkami? Nie mówię o wyłapywaniu wyjątków - mówię o postępowaniu z nimi? Zbyt łatwo jest napisać:
Wiem, że możesz powiedzieć, że to zła praktyka i że „odpowiedzią” jest zrobienie czegoś z wyjątkiem (niech zgadnę, zaloguj się?), Ale w świecie rzeczywistym większość programistów po prostu tego nie robi. to.
Więc tak, nie chcę wychwytywać wyjątków, jeśli nie muszę tego robić, i chcę, aby mój program wybuchł spektakularnie, gdy coś spieprzę. Ciche niepowodzenie to najgorszy możliwy wynik.
źródło
RuntimeExceptions
naprawdę nie do wyłapania i zgadzam się, że powinieneś pozwolić, aby program wybuchł. Wyjątki, które zawsze należy wychwytywać i obsługiwać, to sprawdzone wyjątki, takie jakIOException
. Jeśli dostanieszIOException
, nie ma nic do naprawienia w kodzie; twój program nie powinien wybuchnąć tylko dlatego, że wystąpiła czkawka sieciowa.Artykuł Skuteczne wyjątki Java ładnie wyjaśnia, kiedy należy używać niezaznaczonych, a kiedy sprawdzonych wyjątków. Oto kilka cytatów z tego artykułu, aby podkreślić główne punkty:
(SO nie zezwala na tabele, więc możesz przeczytać poniższe informacje na oryginalnej stronie ...)
źródło
args[]
? Czy to przypadek, czy awaria?File#openInputStream
powrótEither<InputStream, Problem>
- jeśli o to ci chodzi, to możemy się zgodzić.W skrócie:
Wyjątki stanowią pytanie projektowe API. -- Nie więcej nie mniej.
Argument dla sprawdzonych wyjątków:
Aby zrozumieć, dlaczego sprawdzone wyjątki mogą nie być dobrą rzeczą, odwróćmy to pytanie i zapytaj: Kiedy lub dlaczego sprawdzane wyjątki są atrakcyjne, tj. Dlaczego chcesz, aby kompilator wymuszał deklarację wyjątków?
Odpowiedź jest oczywista: czasami trzeba złapać wyjątek, a jest to możliwe tylko wtedy, gdy wywoływany kod oferuje określoną klasę wyjątków dla interesującego cię błędu.
Dlatego argumentem za sprawdzonymi wyjątkami jest to, że kompilator zmusza programistów do zadeklarowania, które wyjątki są zgłaszane, i mam nadzieję, że programista następnie udokumentuje również określone klasy wyjątków i błędy, które je powodują.
W rzeczywistości jednak pakiet zbyt często
com.acme
rzucaAcmeException
raczej podklasy niż określone. Dzwoniący muszą wtedy obsłużyć, zadeklarować lub ponownie zasygnalizowaćAcmeExceptions
, ale nadal nie mogą być pewni, czyAcmeFileNotFoundError
zdarzyło się, czy nieAcmePermissionDeniedError
.Więc jeśli jesteś zainteresowany tylko
AcmeFileNotFoundError
, rozwiązaniem jest złożenie wniosku o funkcję do programistów ACME i powiedzenie im, aby zaimplementowali, zadeklarowali i udokumentowali tę podklasęAcmeException
.Więc po co zawracać sobie głowę?
Dlatego nawet przy sprawdzonych wyjątkach kompilator nie może zmusić programistów do zgłaszania użytecznych wyjątków. To wciąż tylko kwestia jakości interfejsu API.
W rezultacie języki bez zaznaczonych wyjątków zwykle nie mają się gorzej. Programiści mogą mieć pokusę, by rzucić niespecyficzne wystąpienia
Error
klasy ogólnej zamiast anAcmeException
, ale jeśli w ogóle troszczą się o swoją jakość API, nauczą się wprowadzaćAcmeFileNotFoundError
.Ogólnie rzecz biorąc, specyfikacja i dokumentacja wyjątków nie różni się zbytnio od specyfikacji i dokumentacji, powiedzmy, zwykłych metod. Te także są pytaniami projektowymi API i jeśli programista zapomniał zaimplementować lub wyeksportować użyteczną funkcję, interfejs API musi zostać ulepszony, aby można było z nim użytecznie pracować.
Postępując zgodnie z tym tokiem rozumowania, powinno być oczywiste, że „kłopot” z deklarowaniem, wyłapywaniem i ponownym zgłaszaniem wyjątków, który jest tak powszechny w językach takich jak Java, często wnosi niewielką wartość.
Warto również zauważyć, że maszyna wirtualna Java nie ma sprawdzonych wyjątków - sprawdza je tylko kompilator Java, a pliki klas ze zmienionymi deklaracjami wyjątków są kompatybilne w czasie wykonywania. Bezpieczeństwo VM VM nie jest poprawiane przez sprawdzone wyjątki, tylko styl kodowania.
źródło
AcmeException
zamiast zAcmeFileNotFoundError
powodzeniem, aby dowiedzieć się, co zrobiłeś źle i gdzie musisz to złapać. Sprawdzone wyjątki zapewniają programistom odrobinę ochrony przed złym projektem API.W ciągu ostatnich trzech lat współpracowałem z kilkoma programistami w stosunkowo złożonych aplikacjach. Mamy bazę kodu, która dość często używa sprawdzonych wyjątków z odpowiednią obsługą błędów, a niektóre inne nie.
Do tej pory łatwiej mi było pracować z bazą kodu za pomocą sprawdzonych wyjątków. Kiedy korzystam z czyjegoś interfejsu API, miło jest widzieć dokładnie, jakiego rodzaju warunków błędu mogę się spodziewać, gdy wywołam kod i odpowiednio je obsłużę, logując się, wyświetlając lub ignorując (Tak, istnieją uzasadnione przypadki ignorowania wyjątki, takie jak implementacja ClassLoader). To daje kod, który piszę, możliwość odzyskania. Wszystkie wyjątki środowiska wykonawczego propaguję do momentu, aż zostaną zapisane w pamięci podręcznej i obsłużone jakimś ogólnym kodem obsługi błędów. Kiedy znajduję sprawdzony wyjątek, którego tak naprawdę nie chcę obsłużyć na określonym poziomie lub który rozważam błąd logiki programowania, to zawijam go w wyjątek RuntimeException i pozwalam, aby wystąpił błąd. Nigdy, nigdy nie połykaj wyjątku bez uzasadnionego powodu (a dobrych powodów, aby to zrobić, jest raczej mało)
Kiedy pracuję z bazą kodu, która nie ma sprawdzonych wyjątków, nieco trudniej mi jest wiedzieć z wyprzedzeniem, czego mogę się spodziewać podczas wywoływania funkcji, co może strasznie zepsuć niektóre rzeczy.
Jest to oczywiście kwestia preferencji i umiejętności programistycznych. Oba sposoby programowania i obsługi błędów mogą być równie skuteczne (lub nieskuteczne), więc nie powiedziałbym, że istnieje One Way.
Podsumowując, łatwiej mi pracować ze sprawdzonymi wyjątkami, szczególnie w dużych projektach z dużą liczbą programistów.
źródło
Kategorie wyjątków
Mówiąc o wyjątkach, zawsze powracam do artykułu Erica Lipperta na blogu wyjątków Vexing . Stawia wyjątki w tych kategoriach:
OutOfMemoryError
lubThreadAbortException
.ArrayIndexOutOfBoundsException
,NullPointerException
czy każdyIllegalArgumentException
.NumberFormatException
zeInteger.parseInt
zamiast zapewnienieInteger.tryParseInt
metody, która zwraca wartość logiczną FALSE w przypadku błędu parsowania.FileNotFoundException
.Użytkownik interfejsu API:
Sprawdzone wyjątki
Fakt, że użytkownik interfejsu API musi obsłużyć określony wyjątek, jest częścią umowy dotyczącej metody między dzwoniącym a odbiorcą. Umowa określa między innymi: liczbę i rodzaje argumentów, których oczekuje osoba odbierająca, rodzaj wartości zwracanej, której może oczekiwać osoba dzwoniąca, oraz wyjątki, z którymi osoba dzwoniąca ma się zapoznać .
Ponieważ w interfejsie API nie powinny istnieć dokuczliwe wyjątki, tylko te wyjątki egzogeniczne muszą być sprawdzone, aby były częścią kontraktu metody. Stosunkowo niewiele wyjątków jest egzogennych , więc każdy interfejs API powinien mieć stosunkowo niewiele sprawdzonych wyjątków.
Zaznaczony wyjątek to wyjątek, który należy obsłużyć . Obsługa wyjątku może być tak prosta, jak jego przełknięcie. Tam! Obsługiwany jest wyjątek. Kropka. Jeśli deweloper chce sobie z tym poradzić, dobrze. Ale nie może zignorować wyjątku i został ostrzeżony.
Problemy z interfejsem API
Ale każdy interfejs API, który sprawdził irytujące i krytyczne wyjątki (np. JCL), niepotrzebnie obciąży użytkowników interfejsu API. Takie wyjątki muszą zostać obsłużone, ale albo wyjątek jest tak powszechny, że nie powinien był być wyjątkiem, ani nic nie można zrobić przy jego obsłudze. A to powoduje, że programiści Java nienawidzą sprawdzonych wyjątków.
Ponadto wiele interfejsów API nie ma odpowiedniej hierarchii klas wyjątków, co powoduje, że wszystkie rodzaje egzogennych wyjątków są reprezentowane przez pojedynczą sprawdzoną klasę wyjątków (np.
IOException
.). A to powoduje, że programiści Java nienawidzą sprawdzonych wyjątków.Wniosek
Egzogenne wyjątki to te, które nie są twoją winą, których nie można było zapobiec i którym należy się zająć. Stanowią one niewielki podzbiór wszystkich wyjątków, które mogą zostać zgłoszone. Interfejsy API powinny sprawdzać wyłącznie wyjątki egzogeniczne , a wszystkie pozostałe wyjątki niezaznaczone. Spowoduje to lepsze interfejsy API, zmniejszy obciążenie użytkownika interfejsu API, a tym samym zmniejszy potrzebę wychwytywania wszystkich, połykania lub ponownego odrzucania niezaznaczonych wyjątków.
Więc nie nienawidzę Java i jej sprawdzonych wyjątków. Zamiast tego nienawidzą interfejsów API, które nadużywają sprawdzonych wyjątków.
źródło
Ok ... Sprawdzone wyjątki nie są idealne i mają pewne zastrzeżenie, ale służą celowi. Podczas tworzenia interfejsu API występują określone przypadki awarii, które są umowne dla tego interfejsu API. Jeśli w kontekście silnie statycznego języka, takiego jak Java, jeśli nie używa się sprawdzonych wyjątków, należy polegać na dokumentacji i konwencji ad hoc, aby przekazać możliwość wystąpienia błędu. Takie postępowanie usuwa wszelkie korzyści, które kompilator może przynieść w obsłudze błędów, i pozostajesz całkowicie w dobrej woli programistów.
Tak więc, usuwa się sprawdzony wyjątek, tak jak to zrobiono w języku C #, jak zatem można programowo i strukturalnie przekazać możliwość błędu? Jak poinformować kod klienta, że takie i takie błędy mogą wystąpić i muszą zostać usunięte?
Słyszę różnego rodzaju okropności, gdy mamy do czynienia ze sprawdzonymi wyjątkami, są niewłaściwie wykorzystywane, to pewne, ale tak samo nie są zaznaczone wyjątki. Mówię, poczekaj kilka lat, gdy interfejsy API zostaną ułożone w stos wiele warstw głęboko, a będziesz błagać o zwrot jakiegoś uporządkowanego środka do przenoszenia awarii.
Weźmy przypadek, gdy wyjątek został zgłoszony gdzieś u dołu warstw API i po prostu pojawił się bąbelek, ponieważ nikt nie wiedział, że może nawet wystąpić ten błąd, mimo że był to rodzaj błędu, który był bardzo prawdopodobny, gdy kod wywołujący wyrzuciłem go (na przykład FileNotFoundException w przeciwieństwie do VogonsTrashingEarthExcept ... w takim przypadku nie miałoby znaczenia, czy będziemy to robić, czy nie, ponieważ nie ma już nic do obsługi).
Wielu twierdziło, że niemożność załadowania pliku była prawie zawsze końcem świata dla tego procesu i musi umrzeć straszną i bolesną śmiercią. Więc tak ... pewnie ... ok ... budujesz API dla czegoś i w pewnym momencie ładuje on plik ... Ja jako użytkownik wspomnianego API mogę tylko odpowiedzieć ... "Kim do diabła jesteś, kiedy decydujesz program powinien ulec awarii! ” Pewnie Biorąc pod uwagę wybór, w którym wyjątki są pochłaniane i nie pozostawiają śladu, lub wyjątek EletroFlabbingChunkFluxManifoldChuggingException ze śladem stosu głębszym niż rów Marianna, wybrałbym to ostatnie bez wahania, ale czy to oznacza, że jest to pożądany sposób radzenia sobie z wyjątkiem ? Czy nie możemy być gdzieś pośrodku, gdzie wyjątek zostałby przekształcony i zawinięty za każdym razem, gdy przejdzie on na nowy poziom abstrakcji, aby rzeczywiście coś znaczył?
Wreszcie większość argumentów, które widzę, brzmi: „Nie chcę zajmować się wyjątkami, wiele osób nie chce zajmować się wyjątkami. Sprawdzone wyjątki zmuszają mnie do radzenia sobie z nimi, dlatego nienawidzę sprawdzanego wyjątku” Aby całkowicie wyeliminować taki mechanizm i sprowadzenie go do otchłani goto piekła jest po prostu głupie i pozbawione żonglowania i wizji.
Jeśli wyeliminujemy sprawdzony wyjątek, możemy również wyeliminować typ zwracany przez funkcje i zawsze zwracać zmienną „anytype” ... To uczyniłoby życie o wiele prostszym, prawda?
źródło
Rzeczywiście, sprawdzone wyjątki z jednej strony zwiększają niezawodność i poprawność programu (jesteś zmuszony do składania poprawnych deklaracji swoich interfejsów - wyjątki, które zgłasza metoda, są w zasadzie specjalnym rodzajem zwrotu). Z drugiej strony napotykasz problem polegający na tym, że skoro wyjątki „wybuchają”, bardzo często musisz zmienić wiele metod (wszystkich dzwoniących, dzwoniących i tak dalej) po zmianie wyjątków. metoda rzuca.
Sprawdzone wyjątki w Javie nie rozwiązują tego drugiego problemu; C # i VB.NET wyrzucają dziecko z kąpielą.
Ładne podejście, które kroczy środkową drogą, zostało opisane w tym dokumencie OOPSLA 2005 (lub w powiązanym raporcie technicznym ).
Krótko mówiąc, pozwala powiedzieć:,
method g(x) throws like f(x)
co oznacza, że g wyrzuca wszystkie wyjątki. Voila, sprawdzone wyjątki bez problemu ze zmianami kaskadowymi.Chociaż jest to praca naukowa, zachęcam do przeczytania (jej części), ponieważ dobrze wyjaśnia, jakie są zalety i wady sprawdzonych wyjątków.
źródło
To nie jest argument przeciwko czystej koncepcji sprawdzonych wyjątków, ale hierarchia klas, którą wykorzystuje dla nich Java, to dziwaczny pokaz. Zawsze nazywamy to po prostu „wyjątkiem” - co jest poprawne, ponieważ specyfikacja języka nazywa je również - ale jak nazywa się i reprezentuje wyjątek w systemie typów?
Przez klasę
Exception
można sobie wyobrazić? Cóż, nie, ponieważException
s są wyjątkami i podobnie wyjątki sąException
s, z wyjątkiem wyjątków, które nie sąException
, ponieważ inne wyjątki są faktycznieError
s, które są innym rodzajem wyjątku, rodzajem wyjątkowego wyjątku, który nigdy nie powinien się zdarzyć, z wyjątkiem kiedy tak się dzieje i których nigdy nie powinieneś łapać, chyba że czasem musisz. Tyle że to nie wszystko, ponieważ możesz także zdefiniować inne wyjątki, które nie są aniException
s, aniError
zwykłymiThrowable
wyjątkami.Które z nich to „sprawdzone” wyjątki?
Throwable
s są sprawdzanymi wyjątkami, z wyjątkiem tychError
, które są również wyjątkami odznaczonymi, a następnie sąException
s, które są równieżThrowable
s i są głównym rodzajem sprawdzanego wyjątku, z wyjątkiem tego, że jest też jeden wyjątek, to znaczy, że jeśli są równieżRuntimeException
s, ponieważ jest to inny rodzaj niesprawdzonego wyjątku.Po co są
RuntimeException
? Cóż, jak sama nazwa wskazuje, są wyjątkami, jak wszystkieException
s, i zdarzają się w czasie wykonywania, tak jak wszystkie wyjątki, z wyjątkiem tego, żeRuntimeException
są wyjątkowe w porównaniu z innymi,Exception
ponieważ nie powinny się zdarzyć, z wyjątkiem kiedy popełnisz jakiś głupi błąd, chociażRuntimeException
nigdy nie sąError
, więc dotyczą one rzeczy wyjątkowo błędnych, ale tak naprawdę nie sąError
. Z wyjątkiemRuntimeErrorException
, co naprawdę jestRuntimeException
zaError
. Ale czy nie wszystkie wyjątki mają reprezentować błędne okoliczności? Tak, wszystkie z nich. Z wyjątkiemThreadDeath
wyjątkowo wyjątkowego wyjątku, ponieważ dokumentacja wyjaśnia, że jest to „normalne zjawisko” i że „Error
W każdym razie, ponieważ dzielimy wszystkie wyjątki na środku na
Error
s (które są wyjątkami wyjątkowego wykonania, więc niezaznaczone) iException
s (które dotyczą mniej wyjątkowych błędów wykonania, więc sprawdzane, z wyjątkiem sytuacji, gdy nie są), potrzebujemy teraz dwóch różne rodzaje każdego z kilku wyjątków. Potrzebujemy więcIllegalAccessError
iIllegalAccessException
, i,InstantiationError
iInstantiationException
,NoSuchFieldError
iNoSuchFieldException
, iNoSuchMethodError
iNoSuchMethodException
iZipError
iZipException
.Tyle że nawet gdy sprawdzany jest wyjątek, zawsze istnieją (dość łatwe) sposoby oszukiwania kompilatora i wyrzucania go bez sprawdzania. Jeśli tak, że można uzyskać
UndeclaredThrowableException
, oprócz innych przypadkach, gdy mogłoby to zwymiotować jakoUnexpectedException
, alboUnknownException
(co nie ma związkuUnknownError
, który jest tylko dla „poważnych wyjątków”), alboExecutionException
, alboInvocationTargetException
, alboExceptionInInitializerError
.Aha, i nie możemy zapomnieć o niesamowitej nowości Javy 8
UncheckedIOException
, która jestRuntimeException
wyjątkiem zaprojektowanym, aby umożliwić ci wyrzucenie przez okno koncepcji sprawdzania wyjątków przez zawijanie sprawdzonychIOException
wyjątków spowodowanych błędami we / wy (które nie powodująIOError
wyjątków, chociaż takie istnieją) też), które są wyjątkowo trudne w obsłudze, dlatego nie trzeba ich sprawdzać.Dzięki Java!
źródło
Problem
Najgorszy problem, jaki widzę z mechanizmem obsługi wyjątków, polega na tym, że wprowadza on duplikację kodu na dużą skalę ! Bądźmy szczerzy: w większości projektów w 95% przypadków programiści naprawdę muszą zrobić wyjątek, aby w jakiś sposób przekazać je użytkownikowi (aw niektórych przypadkach także zespołowi programistycznemu, np. Wysyłając e -mail ze śladem stosu). Tak więc zwykle w każdym miejscu obsługiwanym wyjątkiem jest używana ta sama linia / blok kodu.
Załóżmy, że wykonujemy proste logowanie w każdym bloku catch dla pewnego rodzaju sprawdzonego wyjątku:
Jeśli jest to częsty wyjątek, może istnieć nawet kilkaset takich bloków try-catch w większej bazie kodu. Załóżmy teraz, że zamiast logowania do konsoli musimy wprowadzić obsługę wyjątków opartą na wyskakującym oknie dialogowym lub rozpocząć dodatkowe wysyłanie wiadomości e-mail do zespołu programistów.
Chwileczkę ... czy naprawdę będziemy edytować wszystkie te kilkaset lokalizacji w kodzie ?! Dostajesz mój punkt :-).
Rozwiązanie
Aby rozwiązać ten problem, wprowadziliśmy koncepcję modułów obsługi wyjątków (które będę dalej określać jako EH) w celu scentralizowania obsługi wyjątków. Do każdej klasy, która musi obsłużyć wyjątki, instancja procedury obsługi wyjątków jest wstrzykiwana przez naszą strukturę Dependency Injection . Typowy wzorzec obsługi wyjątków wygląda teraz tak:
Teraz, aby dostosować obsługę wyjątków, musimy zmienić kod tylko w jednym miejscu (kod EH).
Oczywiście w bardziej złożonych przypadkach możemy zaimplementować kilka podklas EH i funkcji dźwigni, które zapewnia nam nasza platforma DI. Zmieniając konfigurację frameworku DI, możemy łatwo przełączać implementację EH globalnie lub dostarczać konkretne implementacje EH do klas o specjalnych potrzebach obsługi wyjątków (na przykład za pomocą adnotacji Guice @Named).
W ten sposób możemy odróżnić zachowanie obsługi wyjątków w programowaniu i wydaniu wersji aplikacji (np. Programowanie - rejestrowanie błędu i zatrzymywanie aplikacji, prod - rejestrowanie błędu z większą ilością szczegółów i pozwalanie aplikacji na kontynuowanie działania) bez wysiłku.
Ostatnia rzecz
Wreszcie może się wydawać, że ten sam rodzaj centralizacji można uzyskać, przekazując nasze wyjątki „do góry”, aż dotrą one do klasy obsługi wyjątków najwyższego poziomu. Prowadzi to jednak do zaśmiecania kodu i podpisów naszych metod oraz wprowadza problemy konserwacyjne wspomniane przez innych w tym wątku.
źródło
catch
bloków w tym prawdziwym projekcie robi coś naprawdę „użytecznego” z wyjątkiem wyjątków? 10% byłoby dobrze. Zwykłe problemy, które generują wyjątki, to próba odczytania konfiguracji z pliku, który nie istnieje, błędy OutOfMemoryErrors, wyjątki NullPointerException, błędy integralności ograniczenia bazy danych itp. Czy naprawdę próbujesz odzyskać wszystkie funkcje z wdziękiem? Nie wierzę ci :). Często nie ma sposobu na odzyskanie.Anders mówi o pułapkach sprawdzonych wyjątków i o tym, dlaczego zostawił je poza C # w odcinku 97 radia Inżynieria oprogramowania.
źródło
Mój zapis w witrynie c2.com jest nadal w większości niezmieniony w stosunku do oryginalnej wersji: CheckedExceptionsAreIncompatibleWithVisitorPattern
W podsumowaniu:
Wzorzec gościa i jego krewni są klasą interfejsów, w których zarówno pośredni wywołujący, jak i implementacja interfejsu wiedzą o wyjątku, ale interfejs i bezpośredni wywołujący tworzą bibliotekę, która nie może o tym wiedzieć.
Podstawowym założeniem CheckedExceptions jest to, że wszystkie deklarowane wyjątki mogą być zgłaszane z dowolnego punktu, który wywołuje metodę z tą deklaracją. VisitorPattern ujawnia, że założenie to jest błędne.
Ostatecznym rezultatem sprawdzonych wyjątków w takich przypadkach jest dużo niepotrzebnego kodu, który zasadniczo usuwa ograniczenie sprawdzanego wyjątku kompilatora w czasie wykonywania.
Jeśli chodzi o podstawowy problem:
Moją ogólną ideą jest to, że moduł obsługi najwyższego poziomu musi interpretować wyjątek i wyświetlać odpowiedni komunikat o błędzie. Prawie zawsze widzę albo wyjątki we / wy, wyjątki komunikacyjne (z jakiegoś powodu rozróżniają interfejsy API) lub krytyczne błędy (błędy programu lub poważny problem na serwerze kopii zapasowej), więc nie powinno to być zbyt trudne, jeśli pozwolimy na śledzenie stosu dla poważnego problem z serwerem.
źródło
Aby spróbować odpowiedzieć tylko na pytanie bez odpowiedzi:
Pytanie zawiera podstępne uzasadnienie IMHO. To, że interfejs API mówi ci, co wyrzuca, nie oznacza, że postępujesz z nim w ten sam sposób we wszystkich przypadkach. Innymi słowy, wyjątki, które musisz wychwycić, różnią się w zależności od kontekstu, w którym używasz komponentu zgłaszającego wyjątek.
Na przykład:
Jeśli piszę tester połączenia dla bazy danych lub coś, aby sprawdzić poprawność wpisanego przez użytkownika XPath, prawdopodobnie chciałbym wychwycić i zgłosić wszystkie sprawdzone i niezaznaczone wyjątki zgłaszane przez operację.
Jeśli jednak piszę silnik przetwarzania, prawdopodobnie potraktuję wyjątek XPath (zaznaczony) w ten sam sposób, co NPE: Pozwoliłbym mu przejść na szczyt wątku roboczego, pomiń resztę tej partii, zaloguj się problem (lub wyślij go do działu pomocy technicznej w celu diagnozy) i zostaw opinię użytkownikowi, aby skontaktował się z pomocą techniczną.
źródło
Ten artykuł jest najlepszym tekstem na temat obsługi wyjątków w Javie, jaki kiedykolwiek czytałem.
Opowiada się za niesprawdzonymi sprawdzonymi wyjątkami, ale wybór ten jest bardzo dokładnie wyjaśniony i oparty na mocnych argumentach.
Nie chcę tutaj przytaczać zbyt wiele treści artykułu (najlepiej czytać go jako całość), ale obejmuje on większość argumentów niezaznaczonych zwolenników wyjątków z tego wątku. Szczególnie omawiany jest ten argument (który wydaje się dość popularny):
Autor „odpowiedzi”:
A główną myślą lub artykułem jest:
Jeśli więc „ nikt nie wiedział, że wystąpił ten błąd ”, coś jest nie tak z tym projektem. Taki wyjątek powinien być obsługiwany przez przynajmniej najbardziej ogólną procedurę obsługi wyjątków (np. Taką, która obsługuje wszystkie wyjątki nieobsługiwane przez bardziej szczegółowe procedury obsługi), jak sugeruje autor.
Tak smutne, że niewielu ludzi odkrywa ten wspaniały artykuł :-(. Z całego serca polecam każdemu, kto się waha, które podejście lepiej poświęcić trochę czasu i go przeczytać.
źródło
Sprawdzone wyjątki były, w pierwotnej formie, raczej próbą radzenia sobie z przypadkami niż z awariami. Chwalącym celem było wyróżnienie określonych przewidywalnych punktów (niemożność połączenia, nie znaleziono pliku itp.) I zapewnienie, że programiści sobie z nimi poradzą.
To, co nigdy nie było zawarte w pierwotnej koncepcji, polegało na wymuszeniu szerokiej gamy systemowych i niemożliwych do naprawienia awarii, które należy zadeklarować. Te awarie nigdy nie były poprawne, aby mogły zostać zadeklarowane jako sprawdzone wyjątki.
Błędy są generalnie możliwe w kodzie, a kontenery EJB, web & Swing / AWT już to obsługują, zapewniając najbardziej zewnętrzny program obsługi wyjątków „nieudane żądanie”. Najbardziej podstawową prawidłową strategią jest wycofanie transakcji i zwrócenie błędu.
Jednym z kluczowych punktów jest to, że środowisko wykonawcze i sprawdzone wyjątki są funkcjonalnie równoważne.Nie ma obsługi ani odzyskiwania, które sprawdzałyby wyjątki, czego nie mogą zrobić wyjątki środowiska wykonawczego.
Największym argumentem przeciwko „sprawdzonym” wyjątkom jest to, że większości wyjątków nie można naprawić. Prostym faktem jest to, że nie jesteśmy właścicielami kodu / podsystemu, który się zepsuł. Nie widzimy implementacji, nie jesteśmy za nią odpowiedzialni i nie możemy tego naprawić.
Jeśli nasza aplikacja nie jest DB, nie powinniśmy próbować naprawiać DB. Naruszyłoby to zasadę hermetyzacji .
Szczególnie problematyczne były obszary JDBC (SQLException) i RMI dla EJB (RemoteException). Zamiast identyfikowania możliwych do naprawienia nieprzewidzianych sytuacji zgodnie z pierwotną koncepcją „sprawdzonego wyjątku”, wymuszono powszechne zgłaszanie powszechnych problemów z niezawodnością systemową, których nie da się naprawić.
Inną poważną wadą w projekcie Java było to, że obsługa wyjątków powinna być poprawnie umieszczona na najwyższym możliwym poziomie „biznesowym” lub „żądanie”. Zasada jest taka: „rzucaj wcześnie, łapaj późno”. Sprawdzone wyjątki niewiele dają, ale przeszkadzają.
W Javie mamy oczywisty problem z wymaganiem tysięcy bloków typu try-catch typu „nic nie rób”, przy czym znaczna część (40% +) jest błędnie kodowana. Prawie żadne z nich nie implementuje żadnej autentycznej obsługi ani niezawodności, ale nakłada znaczne koszty kodowania.
Wreszcie „sprawdzone wyjątki” są prawie niezgodne z programowaniem funkcjonalnym FP.
Ich nacisk na „natychmiastowe postępowanie” stoi w sprzeczności zarówno z najlepszymi praktykami „wychwytywania opóźnień” w wyjątkach, jak i dowolną strukturą FP, która wyodrębnia pętle / przepływ kontroli.
Wiele osób mówi o „obsłudze” sprawdzonych wyjątków, ale rozmawia przez czapki. Kontynuowanie po niepowodzeniu z zerowymi, niekompletnymi lub niepoprawnymi danymi w celu udawania sukcesu nie jest niczym. To błąd w inżynierii / niezawodności najniższej formy.
Niepowodzenie czysto jest najbardziej podstawową poprawną strategią obsługi wyjątku. Wycofywanie transakcji, rejestrowanie błędu i zgłaszanie użytkownikowi odpowiedzi „niepowodzenie” to dobra praktyka - a co najważniejsze, zapobieganie przypisywaniu nieprawidłowych danych biznesowych do bazy danych.
Inne strategie obsługi wyjątków to „ponów”, „ponownie połącz” lub „pomiń” na poziomie biznesowym, podsystemu lub żądania. Wszystkie są ogólnymi strategiami niezawodności i działają dobrze / lepiej z wyjątkami środowiska wykonawczego.
Wreszcie zdecydowanie lepiej jest zawieść, niż uruchamiać z niepoprawnymi danymi. Kontynuowanie spowoduje albo błędy wtórne, odległe od pierwotnej przyczyny i trudniejsze do debugowania; lub ostatecznie spowoduje popełnienie błędnych danych. Ludzie zostają do tego zwolnieni.
Zobacz:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
źródło
Jak już powiedzieli ludzie, sprawdzone wyjątki nie istnieją w kodzie bajtowym Java. Są po prostu mechanizmem kompilatora, podobnie jak inne kontrole składniowe. Widzę sprawdzane wyjątki dużo jak widzę kompilator narzeka zbędne warunkowe:
if(true) { a; } b;
. Jest to pomocne, ale mogłem to zrobić celowo, więc pozwól mi zignorować twoje ostrzeżenia.Faktem jest, że nie będziesz w stanie zmusić każdego programisty do „robienia właściwej rzeczy”, jeśli egzekwujesz sprawdzone wyjątki, a wszyscy inni są teraz szkodami ubocznymi, którzy po prostu nienawidzą cię za regułę, którą podjąłeś.
Napraw złe programy tam! Nie próbuj poprawiać języka, aby im na to nie pozwolić! Dla większości ludzi „robienie czegoś z wyjątkiem” to tak naprawdę po prostu mówienie o tym użytkownikowi. Mogę również poinformować użytkownika o niezaznaczonym wyjątku, więc trzymaj sprawdzone klasy wyjątków poza moim API.
źródło
Problem ze sprawdzonymi wyjątkami polega na tym, że wyjątki są często dołączane do metod interfejsu, jeśli używa go nawet jedna implementacja tego interfejsu.
Innym problemem związanym ze sprawdzonymi wyjątkami jest to, że są one niewłaściwie wykorzystywane. Idealnym przykładem jest
java.sql.Connection
„sclose()
metody. Może rzucićSQLException
, nawet jeśli już wyraźnie powiedziałeś , że skończyłeś z Connection. Jakie informacje można zamknąć (), które mogą Cię zainteresować?Zwykle kiedy zamykam () połączenie
*
, wygląda to tak:Nie zaczynaj też od różnych metod analizy i wyjątku NumberFormatException ... TryParse .NET, który nie rzuca wyjątków, jest o wiele łatwiejszy w użyciu, bolesne jest powrót do Java (używamy zarówno Java, jak i C # gdzie pracuję).
*
Jako dodatkowy komentarz, Connection.close () z PooledConnection nawet nie zamyka połączenia, ale nadal musisz wychwycić wyjątek SQLEx, ponieważ jest to sprawdzony wyjątek.źródło
Programista musi znać wszystkie wyjątki, które metoda może zgłosić, aby móc z niej poprawnie korzystać. Tak więc pokonanie go nad głową z kilkoma wyjątkami niekoniecznie pomaga nieostrożnemu programistowi uniknąć błędów.
Zaletą niewielkiej korzyści jest uciążliwy koszt (szczególnie w przypadku większych, mniej elastycznych baz kodu, w których ciągłe modyfikowanie podpisów interfejsu nie jest praktyczne).
Analiza statyczna może być przyjemna, ale prawdziwie niezawodna analiza statyczna często sztywno wymaga od programisty ścisłej pracy. Istnieje kalkulacja kosztów i korzyści, a pasek musi być ustawiony wysoko dla kontroli, która prowadzi do błędu czasu kompilacji. Byłoby bardziej pomocne, gdyby IDE przyjęło rolę komunikowania, które wyjątki może zgłosić metoda (w tym które są nieuniknione). Chociaż być może nie byłby tak niezawodny bez wymuszonych deklaracji wyjątków, większość wyjątków byłaby nadal deklarowana w dokumentacji, a wiarygodność ostrzeżenia IDE nie jest tak istotna.
źródło
Myślę, że jest to doskonałe pytanie i wcale nie jest argumentacyjne. Myślę, że biblioteki stron trzecich powinny (generalnie) zgłaszać niesprawdzone wyjątki. Oznacza to, że możesz odizolować swoje zależności od biblioteki (tj. Nie musisz ponownie rzucać ich wyjątkami ani rzucać
Exception
- zwykle zła praktyka). Wiosenna warstwa DAO jest tego doskonałym przykładem.Z drugiej strony, wyjątki od podstawowego API Java powinny być ogólnie sprawdzane, czy można je kiedykolwiek obsłużyć. Weź
FileNotFoundException
lub (mój ulubiony)InterruptedException
. Warunki te prawie zawsze należy traktować konkretnie (tzn. Reakcja na anęInterruptedException
nie jest taka sama jak reakcja na anIllegalArgumentException
). Fakt sprawdzania wyjątków zmusza programistów do zastanowienia się, czy dany warunek jest w stanie obsłużyć, czy nie. (To powiedziawszy, rzadko widywanoInterruptedException
się odpowiednio!)Jeszcze jedno - a
RuntimeException
nie zawsze „tam, gdzie deweloper coś źle zrozumiał”. Zgłaszany jest niedozwolony wyjątek argumentu podczas próby utworzeniaenum
użyciavalueOf
i nie maenum
takiej nazwy. To niekoniecznie pomyłka dewelopera!źródło
enum
nazw członków, tylko dlatego, żeenum
zamiast tego używają obiektów. Zła nazwa może więc pochodzić tylko z zewnątrz, niezależnie od tego, czy jest to plik importu, czy cokolwiek innego. Jednym z możliwych sposobów radzenia sobie z takimi nazwami jest dzwonienieMyEnum#valueOf
i łapanie IAE. Innym sposobem jest użycie wstępnie wypełnionegoMap<String, MyEnum>
, ale są to szczegóły implementacji.Oto jeden argument przeciwko sprawdzonym wyjątkom (z joelonsoftware.com):
źródło
goto
słowo kluczowe. Za pomocą pętli można zobaczyć nawias zamykającybreak
lubcontinue
słowo kluczowe lub . Wszyscy przeskakują do punktu w bieżącej metodzie. Ale nie zawsze można to zobaczyćthrow
, ponieważ często nie jest to w obecnej metodzie, ale w innej metodzie, którą wywołuje (być może pośrednio)Dobrym dowodem na to, że sprawdzone wyjątki nie są potrzebne są:
Byłem szczęśliwy, jeśli Java zapewni mi wybór , którego mam użyć podczas pracy z podstawowymi bibliotekami lib, takimi jak I / O. Like udostępnia dwie kopie tych samych klas - jedna zapakowana w RuntimeEception. Następnie możemy porównać, z czego korzystaliby ludzie . Na razie jednak wiele osób lepiej byłoby wybrać jakieś frameworki na java lub inny język. Jak Scala, JRuby cokolwiek. Wielu uważa, że SUN miał rację.
źródło
RuntimeException
z odpowiednim wewnętrznym wyjątkiem). To niefortunne, że bardziej zwięzłe jest, aby metoda zewnętrznathrows
stanowiła wyjątek od metody wewnętrznej, niż owijać wyjątki od metody wewnętrznej, gdy ten drugi sposób działania byłby częściej prawidłowy.Jedną ważną rzeczą, o której nikt nie wspominał, jest to, w jaki sposób zakłóca interfejsy i wyrażenia lambda.
Powiedzmy, że definiujesz
MyAppException extends Exception
. Jest to wyjątek najwyższego poziomu dziedziczony przez wszystkie wyjątki zgłoszone przez aplikację. Każda metoda deklaruje, żethrows MyAppException
jest to trochę niuansowe, ale możliwe do opanowania. Procedura obsługi wyjątków rejestruje wyjątek i w jakiś sposób powiadamia użytkownika.Wszystko wygląda OK, dopóki nie chcesz wdrożyć interfejsu, który nie jest twój. Oczywiście nie deklaruje zamiaru rzucenia
MyApException
, więc kompilator nie pozwala na rzucenie stamtąd wyjątku.Jeśli jednak twój wyjątek się rozszerzy
RuntimeException
, nie będzie problemu z interfejsami. Możesz dobrowolnie wspomnieć o wyjątku w JavaDoc, jeśli chcesz. Ale poza tym po prostu bezgłośnie przepuszcza wszystko, aby zostać złapanym w warstwę obsługi wyjątków.źródło
Widzieliśmy kilka odniesień do głównego architekta C #.
Oto alternatywny punkt widzenia faceta Javy, kiedy należy stosować sprawdzone wyjątki. Uznaje wiele negatywnych stron, o których wspominali inni: skuteczne wyjątki
źródło
Dużo czytałem o obsłudze wyjątków, nawet jeśli (przez większość czasu) naprawdę nie mogę powiedzieć, że jestem szczęśliwy lub smutny z powodu istnienia sprawdzonych wyjątków, oto moje zdanie: sprawdzone wyjątki w kodzie niskiego poziomu (IO, networking) , OS itp.) Oraz odznaczone wyjątki w interfejsach API wysokiego poziomu / poziomie aplikacji.
Nawet jeśli nie jest tak łatwo narysować linię między nimi, uważam, że naprawdę denerwujące / trudne jest zintegrowanie kilku interfejsów API / bibliotek pod tym samym dachem bez owijania przez cały czas wiele sprawdzonych wyjątków, ale z drugiej strony, czasem jest użyteczny / lepiej zostać zmuszonym do złapania jakiegoś wyjątku i podania innego, który ma większy sens w obecnym kontekście.
Projekt, nad którym pracuję, bierze wiele bibliotek i integruje je pod tym samym API, API, które jest całkowicie oparte na niesprawdzonych wyjątkach. Te ramy zapewniają API wysokiego poziomu, które na początku było pełne sprawdzonych wyjątków i miało tylko kilka niesprawdzonych wyjątki (wyjątek inicjalizacji, wyjątek konfiguracji itp.) i muszę powiedzieć, że nie był zbyt przyjazny . Przez większość czasu musiałeś wychwytywać lub ponownie rzucać wyjątki, których nie wiesz, jak sobie z nimi poradzić, lub nawet ci to nie przeszkadza (nie mylić z tobą, należy zignorować wyjątki), szczególnie po stronie klienta, gdzie jeden kliknięcie może wygenerować 10 możliwych (zaznaczonych) wyjątków.
Obecna wersja (trzecia) korzysta tylko z niezaznaczonych wyjątków i ma globalny moduł obsługi wyjątków, który jest odpowiedzialny za obsługę wszystkiego, czego nie wykryto. Interfejs API umożliwia rejestrowanie procedur obsługi wyjątków, które decydują, czy wyjątek jest uważany za błąd (przez większość czasu tak jest), co oznacza rejestrowanie i powiadamianie kogoś lub może oznaczać coś innego - na przykład wyjątek AbortException, co oznacza zerwanie bieżącego wątku wykonania i nie rejestrowanie żadnego błędu, ponieważ jest pożądane, aby tego nie robić. Oczywiście, aby wypracować, wszystkie niestandardowe wątki muszą obsługiwać metodę run () za pomocą try {...} catch (all).
public void run () {
}
Nie jest to konieczne, jeśli używasz WorkerService do planowania zadań (Runnable, Callable, Worker), który obsługuje wszystko za Ciebie.
Oczywiście to tylko moja opinia i może nie być właściwa, ale wygląda na dobre podejście do mnie. Zobaczę, kiedy wydam projekt, jeśli to, co moim zdaniem jest dobre dla mnie, jest również dobre dla innych ... :)
źródło