środa, 21 listopada 2012

Enkapsuluj swoje wyjątki

Tyle o tej enkapsulacji, a mało kto pisze o tym w kontekście wyjątków.
Może najpierw trochę o tym jakie wyjątki rzucać. Zgodnie z arkanami sztuki metoda powinna rzucać wyjątki _tylko_i_wyłącznie_ istotne z punktu widzenia interfejsu jaki reprezentuje. Biorąc za przykład:
interface UserRepository {
    List<User> activeUsers() throws DataAccessException;
}
Metoday activeUsers zwraca listę użytkowników i jest zupełnie sensownym to, że zgłosi wyjątek DataAccessException, gdy pojawi się błąd z dostępem do danych. Zgłaszanie wyjątku typu BufferOverflowException, albo DOMException nie da klientowi żadnej użytecznej informacji na temat tego co się stało. Klienta interesuje to, że repozytorium ma problem z dostępem do danych, a nie to, że w środku jakiś bufor się przepełnił, albo przetwarza błędny dokument DOM.

To prowadzi do myśli o enkapsulacji. Gdybyśmy sprzedawali bułki w sklepie, a nasz piekarz wstrzymałby dostawę na parę dni, ponieważ w jego piekarni pojawił mklik próchniczek - nie informowalibyśmy o tym naszych klientów, bo już więcej by się nie pojawili. Taką informację zachowamy dla siebie, zmienimy piekarza, a klientowi powiemy, że mamy problemy z dostawą na parę dni. I tak też powinniśmy robić w świecie Javy. Metoda activeUsers() nie powinna zwracać wyjątku typu SQLException czy FileNotFoundException. Klienta to nie interesuje skąd repozytorium bierze dane. Klient po prostu chce te dane!
Wyjątki powinny być enkapsulowane przed rzuceniem ich dalej. Przykładowa implementacja metody activeUsers:
public List<User> activeUsers() throws DataAccessException {
    try {
        // wyciąganie danych z bazy
    } catch (SQLException e) {
        throw new DataAccessException(e);
    }    
    return users;
}
DataAccessException ładnie enkapsuluje w sobie co się stało w metodzie activeUsers. Użytkownik repozytorium jest wolny od tej implementacji i wymiana na inne źródło danych nie pociąga za sobą daleko idących zmian w kodzie.
Jeśli to repozytorium byłoby wystawiane np. przez webservice do zdalnych klientów to implementacja może wyglądać tak:
public List<User> activeUsers() throws DataAccessException {
    try {
        // wyciąganie danych z bazy
    } catch (SQLException e) {
        logger.error(e);
        throw new DataAccessException("Error during data fetching");
    }    
    return users;
}
W takim wypadku klient nie ma zupełnie wiedzy o tym czego używamy do przechowywania danych, a my nie tracimy informacji o błędzie.

4 komentarze:

  1. Niestety jednocześnie istnieją wyjątki, które wartoby użyć w niektórych kontekstach swojej aplikacji - np JAAS'owy LoginException, ale nie nie pozwalające 'elegancko' przekazać żadnego Throwable w konstrukturze -można to obejść tylko przez initCause.

    Ale np Sonar/PMD nie rozumie ani initCause, ani motywacji tej drugiej sytuacji (webservice, ejb) i ponad wszystko będzie krzyczał, ze tracimy historie błędu ;/.

    btw throw new ;)

    OdpowiedzUsuń
    Odpowiedzi
    1. Przejrzałem dokumentację i faktycznie tak jest, to musi być jakiś błąd w API, bo inaczej nie rozumiem motywacji (nie używałem nigdy bezpośrednio JAASa).

      Poza tym ten wyjątek to trochę jest to o czym pisałem - zupełnie nie mówi o co chodzi. Chyba, że błędem jaki zgłasza jest błąd loginu(?!). Te które po nim dziedziczą już są bardziej wymowne.

      Co do Sonara to trzeba go traktować z przymrużeniem oka. To ma być wyznacznik a nie punkt odniesienia.

      dzięki za 'new'!

      Łukasz

      Usuń
  2. A co sądzisz o enkapsulacji do wyjątków dziedziczących po RuntimeException?
    Spring chyba kiedyś przyjął taką politykę odnośnie wyjątków rzucanych z Hibernate.

    OdpowiedzUsuń
  3. W .net są tylko runtime exp. i wygląda że wszystko im wszystko działa :-) wykorzystanie tego typu wyjątków sprzyja enkapsulacji, używając checked exp. zmiana na niskim poziomie najprawdopodobniej doprowadzi do sporych zmian na wyższych poziomach. To tak jakby implementacja wyciekala nam poza klasę w której się znajduje. Mając runtime exp. ograniczamy powiązania klasy i dostajemy większą elastyczność i czytelniejszy kod.

    OdpowiedzUsuń