ś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.