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!


2 komentarze:

  1. Bardzo fajnie, ja tylko ciągle nie rozumiem dlaczego się porównuje wszystko do cepa...

    Akurat ewolucja budowy ręcznej młockarni jest przykładem złego rozwoju. Po wprowadzeniu gązwy pokrzywdzone zostały osoby leworęczne, dla których i tak nie było już pracy przy koszeniu (kosy leworęczne to rzadkość) jak i przy innych pracach polowych. Przez to dzieci wioskowe do tej pory są zmuszane do praworęczności. Gązwy bowiem przy półobrocie z lewej strony uszkadzają zakończenia dzierżaka.

    OdpowiedzUsuń
  2. Misko Hevery jest świetny w wyjaśnianiu rzeczy w sposób zrozumiały. Polecam jego blog, szczególnie wpisy z lat 2008 do 2009.

    OdpowiedzUsuń