poniedziałek, 30 kwietnia 2012

Otaczanie wyjątków niskiego poziomu

O tym, że warto tworzyć własne wyjątki pisałem już tutaj. Dzisiaj pozwolę sobie na kontynuację tematu dotyczącego wyjątków, a mianowicie będzie kilka słów na temat tego czy i dlaczego warto otaczać wyjątki niskiego poziomu.

Co mam na myśli pisząc "wyjątki niskiego poziomu"?
Chodzi mi tutaj o wszelkie wyjątki, które mogą zostać wyrzucone przez wykorzystywane przez nas frameworki, biblioteki, ORM'y itp., czyli przez klasy, które nie zostały przez nas zaimplementowane.

Załóżmy, że cała aplikacja jest stworzona w taki sposób, że do użytkownika końcowego nigdy nie doleci 'czysta' informacja dotycząca wyjątku. Dostaje on zawsze jakiś ładny (mniej lub bardziej opisowy) komunikat, informujący o tym, że operacja się nie udała. W takim razie, po co ewentualnymi wyjątkami dodatkowo się zajmować i marnować na nie czas, który można przeznaczyć na bardziej twórcze zajęcia np. nową funkcjonalność?

Pewnie większość z Was w jakiś sposób loguje przechwycone wyjątki, a robicie to na pewno, jeżeli pracujecie nad większym projektem. Jak wiadomo wyjątek jest czymś, co nie powinno się pojawić. Z tym, że jest to tylko teoretyczną mrzonką, w praktyce występują niestety częściej niżbyśmy chcieli:(
W momencie wystąpienia wyjątku przystępujemy do procesu mającego na celu usunięcie usterki. Sprawdzamy treść wyjątku oraz jego typ. Załóżmy, że dostaliśmy wyjątek typu SqlStatementException, a w treści wyjątku coś na temat nie istniejącego rekordu o danym id w bazie. Oczywiście po przeanalizowaniu wiadomości znajdziemy problem - np. brak rekordu o id 13 w tabeli applicationUsers.
Jednak oprócz tego faktu nie wiemy nic więcej. Oczywiście możemy prześledzić stos i dowiedzieć się, gdzie został wyrzucony ten wyjątek. I tutaj możemy mieć (co najmniej) dwie sytuacje:
  • Id użytkownika powinniśmy otrzymać z GUI
  • Id powinno zostać dostarczone do danego miejsca w kodzie, ponieważ w innym wypadku natrafiamy na błąd w logice
Niby ten sam wyjątek, wyrzucony z tego samego powodu, a jednak nie do końca.
O wiele łatwiejsza byłaby identyfikacja, gdybyśmy w pierwszym przypadku otrzymali AclMissingParameterException, bo nim wczytamy się w treść wyjątku wiemy, że z gui nie dostaliśmy wymaganego parametru, natomiast w drugim - NonExistingUserException. Tak naprawdę drugi przypadek może (powinien?) być jeszcze bardziej powiązany z wykonywaną w danej chwili logiką.

Dzięki otaczaniu wyjątków odrywamy się poniekąd od narzędzi (bibliotek etc.), które wykorzystujemy w celu realizacji logiki aplikacji, a więc staje się ona (logika) bardziej od nich niezależna. Z drugiej strony ułatwia to proces usuwania błędów, ponieważ skraca czas potrzebny na znalezienie problemu (brak rekordu w bazie) i identyfikację jego przyczyn (brak parametrów z gui).