Spis treści
Mechanizm wyjątków ma zarówno zalety, jak i wady.
Dlaczego używać mechanizmu wyjątków?¶
Mechanizm wyjątków ma dwie podstawowe zalety:
- Możemy obsłużyć dowolne sytuacje, które uznamy za nieprawidłowe, bez potrzeby stosowania rozwiązań z np. zwracaniem specjalnej wartości (jak w przykładzie z dzieleniem).
- Przenosi odpowiedzialność obsługi błędu do tego, kto używa kod, który potencjalnie rzuca wyjątek. Zamiast w funkcji podziel wypisywać na ekran, że nie można dzielić przez 0 bądź zwracać specjalną wartość w takim przypadku, pozwalamy temu, kto wywołuje metodę podziel, na odpowiednie obsłużenie takiego przypadku. Jest to o tyle ważne, że w bardziej skomplikowanych przypadkach może nie być jednego uniwersalnego sposobu na obsłużenie danego błędu – rzucenie wyjątku pozwoli, by w różnych sytuacjach można było odpowiednio na dany błąd zareagować.
Wady wyjątków¶
W poprzednich rozdziałach widzieliśmy kilka cech wyjątków rodzaju Checked:
- jeżeli w wyniku wywołania metody może zostać rzucony wyjątek rodzaju Checked, a nie chcemy go obsługiwać, to musimy skorzystać ze słowa kluczowego throws, aby zaznaczyć, że dana metoda może taki wyjątek rzucić,
- jeżeli nie obsłużymy sytuacji, w której może być rzucony wyjątek rodzaju Checked i nie skorzystamy z throws, to nasz kod się nie skompiluje – kompilator zgłosi błąd.
Jeżeli wywołujemy metodę, która wywołuje kolejną metodę itd. i ostatnia z tych metod może rzucić wyjątek rodzaju Checked, a obsłużyć go chcemy dopiero na samej górze tego stosu wywołań metod, to wszystkie metody po drodze muszą zawierać klauzulę throws – inaczej nasz kod się nie skompiluje. Powoduje to potencjalny narzut, ponieważ teraz w każdym miejscu naszego programu, w którym wywołamy którąś z tych metod, będziemy musieli korzystać z bloku try..catch.
Ponadto, wymóg deklaracji rzucanego wyjątku i obsługi go uniemożliwia łatwą modyfikację już napisanych metod. Załóżmy, że mamy metodę pewnaMetoda, z której korzystamy w wielu miejscach naszego programu. W pewnym momencie musimy ją zmodyfikować i wykorzystać metodę z pewnej biblioteki, którą dodaliśmy do naszego projektu. Jeżeli metoda z tej biblioteki może rzucić wyjątek rodzaju Checked, to musimy albo dodać jego obsługę w naszej metodzie pewnaMetoda, albo dodać do jej sygnatury throws – ale to spowoduje, że wszystkie miejsca w naszym programie, które wcześniej korzystały z metody pewnaMetoda, przestaną się kompilować, jeżeli nie korzystały z try..catch.
Częstym zarzutem odnośnie wyjątków jest ich nadużycie – niekiedy metody definiują rzucanie wielu wyjątków, które potem trzeba obsługiwać. Czasem takie wyjątki powinny po prostu być rodzaju Unchecked zamiast Checked. Czasem takie sytuacje rozwiązuje się po prostu poprzez złapanie wszystkich wyjątków za pomocą klasy Exception:
try { metodaMogacaRzucicWieleWyjatkow(); } catch (Exception e) { // .. zbiorcza obsluga wyjatkow }
Jeżeli wyjątek ma być obsłużony wyżej w hierarchii wykonań, to zamiast go obsługiwać, jest on rzucany dalej, ale tym razem metody wyżej mają do obsługi jeden znormalizowany wyjątek.
Poza tym, poprzez ciche łapanie wyjątków (o którym wspominałem w jednym z wcześniejszych rozdziałów) możemy spowodować, że nasz program będzie zawierał ciężkie do wykrycia błędy. Chociaż takie przypadki to ewidentna wina programisty, a nie samego mechanizmu wyjątków.
Podsumowując – jeżeli w naszym programie chcemy zasygnalizować problem, który można rozwiązać i po którym program może dalej działać, to korzystajmy z wyjątków rodzaju Checked, a w przeciwnym razie rzucajmy wyjątki Unchecked. Unikajmy cichego łapania wyjątków i pamiętajmy, jakie konsekwencje niesie ze sobą dodanie rzucania wyjątków Checked przez już istniejące i używane metody.