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

wtorek, 30 października 2012

Nie zwracaj nulla!

Po raz kolejny siedzę w czyimś kodzie w którym co chwilą jest if:
public List <User> usersList() {
    List<Object[]> users = db.query("SELECT id, name, email FROM users");
    if (users == null);
        return null;
    //...
    // mapowanie na obiekty i zwrot wyników
I dalej gdzieś w serwisie czy czymś:
List<User> users = usersRepo.usersList();
if (users == null)
    return null; // i zabawa trwa dalej!
Takie IFy mają tendencje do ciągnięcia się przez wszystkie warstwy sięgając nawet samego widoku. Pozbycie się tego nie tylko poprawi wygląd kodu, zmniejszy złożoność, ale też ograniczy możliwość wystąpienia NullPointerException. A rozwiązanie tego jest proste - zwracać tzw. NullObject. Koncepcja prosta jak budowa cepa. W tym przypadku możemy szybko zmienić by obiekt zwracał pustą listę:
public List <User> usersList() {
    List<Object[]> users = db.query("SELECT id, name, email FROM users");
    if (users == null);
        return Collections.<User>emptyList();
    //...
    // mapowanie na obiekty i zwrot wyników
I już nie trzeba się bać o NPE i kod robi to co powinien (czyli zwrócić _listę_). Ciekawszym przykładem będzie, gdy zaczniemy mieć różne implementacje jakieś funkcjonalności. Np. gdy mamy odpowiednią klasę odpowiedzialną za kolorowanie obramowania, ale czasami nie musimy go kolorować. Najpierw wersja 'brzydka':
public interface BorderPainter {
    void paint(Element element);
}

public class BorderPainterFactory {
    public static BorderFactory get(BorderType type) {
        switch (type) {
            case BOLDED:
                return new BoldetBorderPainter();
            case DOTTED:
                return new DottedBorderPainter();
            case NONE:
                return null;
        }
    }
}

// i użycie
BorderPainter painter = BorderFactory.get(type);
// albo gorsze - sprawdzać czy type jest typu NONE przed wywołaniem factorki
if (painter != null) { 
    painter.paint(someElement);
}
A lepiej sobie wprowadzić implementację BorderPainter która najzwyczajniej w świecie nic nie robi :)
public NoBorderPainter implements BorderPainter {
    public void paint(Element element) {}
}
// i wywołanie jest zawsze takie samo:
BorderPainter painter = BorderFactory.get(type);
painter.paint(someElement);
Takie podejście jest dużo bardziej object oriented, zmniejsza złożoność, zwiększa elastyczność, czytelność, stabilność i testowalność kodu. Oczywiście nie zawsze jest to możliwe, by takowy NullObject skontruować i wprowadzić. Ale z doświadczenia widzę, że około 90% takich konstrukcji może zostać zrefaktoryzowanych.

Piękna prezentacja na temat usuwania IFów z kodu autorstwa Misko Heverego (co mocno rozszerza mój wątek o null objectach) można zobaczyć poniżej. POLECAM!


poniedziałek, 26 marca 2012

GeeCON 2012 [Film:]


Czwarta edycja GeeCONa zbliża się wielkimi krokami! Dla tych co jeszcze nie wiedzą odbędzie się w pomiędzy 16 a 18 Maja w Poznaniu.
Dzisiaj na twitterze szumu narobił nowy film promocyjny podsumowujący zeszłoroczną edycję:
The laws of GeeCON
Jak widać warto tam być! W tym roku wśród gwiazd konferencji znajdą się:
  • Bruce Eckel
  • Gavin King
  • Ivar Jacobson
  • Adam Bien
  • Kevlin Henney
  • Pete Muir
a z polskich znanych twarzy:
  • Jacek Laskowski
  • Dawid Weiss
  • Tomek Kaczanowski
  • Piotr Walczyszyn

Schedule jest już znany, więc można powoli planować ścieżkę prezentacji podczas konferencji.

Jak ktoś już się zapisał to można sobie na bloga wrzucić ładnego bannerka :-)

Do zobaczenia!