Gettery i Settery to czyste zło! Kropka.

Nadal trwa ta stara dyskusja rozpoczęta przez Allena Holuba w 2003 r. przez jego słynny artykuł zatytułowany: „Dlaczego metody get i set są złe”. Debata dotyczy tego czy getter/setter jest antywzorcem i powinien być omijany, czy jest to coś, czego koniecznie potrzebujemy w programowaniu obiektowym. Spróbuję dorzucić swoje trzy grosze do tej dyskusji. 

Teza tego artykułu to: getters/setters to okropne rozwiązanie, a ci, którzy je stosują nie są niczym usprawiedliwieni. Jednak, żeby było jasne: wcale nie twierdzę, że powinno się unikać get/set, kiedy tylko się da. Nie. Twierdzę, że nie powinieneś go nigdy dopuszczać do swoich kodów. 

Czy powiedziałem to wystarczająco arogancko, aby zwrócić twoją uwagę? Może jesteś szanowanym architektem Javy, stosujesz wzorzec get/set od 15-tu lat i nie chcesz czytać kolejnych nonsensów jakiegoś kolesia? No cóż, rozumiem twoje odczucia. Czułem się podobnie, kiedy natknąłem się na najlepszą książkę o programowaniu obiektowym, jaką dotąd przeczytałem, a mianowicie „Object Thinking” Davida Westa. Uspokój się więc i spróbuj zrozumieć to, co ci zaraz wyjaśnię.

Dotychczasowe argumenty

W świecie projektowania obiektowego istnieje kilka argumentów przeciwko metodom dostępowym (inna nazwa dla getters & setters), ale żaden z nich nie jest, według mnie, odpowiednio mocny, co postaram się krótko pokazać.

Nie pytaj, tylko zlecaj: Allen Holub mówi: „Nie proś o informacje, których potrzebujesz do wykonania danej pracy. Poleć obiektowi, który posiada te informacje, aby wykonał pracę zamiast ciebie”. 

Naruszenie zasady hermetyzacji: Obiekt może zostać poszarpany przez inne obiekty, jeśli mogą one, poprzez settery, wprowadzić weń jakieś nowe dane. Ten obiekt po prostu nie będzie mógł odpowiednio zabezpieczyć swojego stanu, skoro każdy będzie mógł wprowadzać jakieś poprawki. 

Eksponowanie szczegółów implementacji: Jeśli możemy dostać się do obiektu wydobywając go z innego obiektu, zbytnio polegamy na szczegółowych informacjach wprowadzania tego pierwszego. Jeśli zmienią się one jutro, powiedzmy, zmieni się typ wyniku, my także musimy wtedy zmienić kod. 

Wszystkie powyższe uzasadnienia mają sens, ale nie wskazują najważniejszego.

Błędne założenie

Większość programistów wierzy, że obiekt jest strukturą danych i metod. Zacytuję tu artykuł Bożydara Bożanowa „Gettery i settery nie są złe” [ang. „Getters and setters are not evil” Bozhidar Bozanow]:

Jednak większość obiektów, dla których ludzie generują gettery i settery to proste kontenery danych.

Takie błędne rozumowanie jest konsekwencją innego wielkiego nieporozumienia. Obiekty nie są „zwykłymi segregatorami danych”. Obiekty nie są strukturami danych z przyłączonymi do nich metodami. To pojęcie „segregatora danych” przyszło do programowania obiektowego z języków procedur, szczególnie z C i COBOL. Powtórzę zatem: obiekt nie jest zestawem danych i funkcji, które nimi manipulują. Obiekt w ogóle nie jest jednostką danych. 

Czym więc jest? 

Piłeczka i pies

W prawdziwym programowaniu obiektowym obiekty są żywymi istotami, jak ty i ja. Są żywymi organizmami ze swoim własnym zachowaniem, właściwościami i cyklem życiowym. 

Czy żywy organizm ma setter?  Czy można „ustawić” piłeczkę w psie? Nie bardzo. A to przecież właśnie dzieje się w poniższym kawałku programu: 

Dog dog = new Dog();

dog.setBall(new Ball());

Brzmi głupio? 

Czy można wydobyć piłeczkę z psa? Prawdopodobnie tak, jeśli wcześniej ją zjadł i właśnie robimy mu operację. W takim wypadku tak, możemy „wyciągnąć” (get) piłeczkę z psa. Mówię mniej więcej o czymś takim: 

Dog dog = new Dog();

Ball ball = dog.getBall();

Albo może jeszcze coś głupszego: 

Dog dog = new Dog();

dog.setWeight("23kg");

Czy wyobrażacie sobie taką transakcję w realnym świecie? ☺

Czy to przypomina ci to, co codziennie piszesz? Jeśli tak, to jesteś programistą procedur. Czas się do tego przyznać się do tego. A oto, co David West pisze na ten temat w swojej książce (str. 30). 

Pierwszym krokiem w transformacji dobrego programisty procedur w dobrego programistę obiektowego jest lobotomia.

Czy potrzebujesz lobotomii? Ja potrzebowałem i dokonałem jej czytając „Object Thinking” Westa.

Myślenie obiektowe

Zacznij myśleć jak obiekt a natychmiast przemianujesz te metody. Oto, co prawdopodobnie otrzymasz: 

Dog dog = new Dog();

dog.take(new Ball());

Ball ball = dog.give();

Teraz traktujemy psa jako prawdziwe zwierzę, które może wziąć od nas piłeczkę i ją nam oddać, kiedy poprosimy. Warto wspomnieć, że pies nie może nam oddać zera (NULL), bo psy po prostu nie wiedzą, co to jest zero.  Myślenie obiektowe natychmiast eliminuje puste referencje (NULL references) z naszego kodu. 

Rybka zwana Wandą

Poza tym, myślenie obiektowe doprowadzi nas do niezmienności obiektu, jak w przykładzie z „wagą psa”. Przepisalibyśmy to mniej więcej tak:

Dog dog = new Dog("23kg");

int weight = dog.weight();

Pies jest niezmiennym żywym organizmem, który nie pozwala nikomu z zewnątrz zmieniać swojej wagi, rozmiaru czy imienia.  Na żądanie może nam podać swoją wagę czy imię. Nie ma nic złego w publicznych metodach, które pokazują żądania konkretnych „składowych” danego obiektu. Te metody jednak nie są getterami i nigdy nie będą miały prefiksu „get”. Nie wyjmiesz (get) niczego z psa. Nie zabierzesz jego imienia. Prosisz go, żeby podał ci swoje imię. Widzisz różnicę?

Nie mówimy tu też o semantyce. Odróżniamy wzorce myślowe w programowaniu proceduralnym od myślenia w programowaniu obiektowym. W proceduralnym programowaniu pracujemy z danymi, manipulujemy nimi, wydobywamy je (getting) i ustawiamy (setting), a także kasujemy w razie potrzeby. To my rządzimy danymi, a one są biernym komponentem. Wobec tego, rzeczony pies nie jest niczym więcej jak kontenerem danych. Nie ma własnego życia. Możemy dowolnie z niego wydobywać cokolwiek chcemy i wstawiać, co nam się podoba. Oto jak działają (działały) języki proceduralne, takie jak C, COBOL, Pascal i wiele innych. 

Inaczej się dzieje w prawdziwym programowaniu zorientowanym na obiekt. Tu traktujemy obiekty jak żywe istoty, z ich własną datą urodzenia i śmierci, czy jak wolisz, z ich własną tożsamością i zwyczajami. Możemy poprosić psa, żeby podał nam jakieś dane (np. swoją wagę), a on poda nam, zgodnie z żądaniem. Zawsze jednak pamiętamy, żeby traktować go jak aktywny komponent. To on decyduje, co się stanie po wysłaniu przez nas zapytania. 

Oto, dlaczego, konceptualnym błędem jest stosowanie metod zaczynających się od  set lub  get w przypadku obiektów.  Nie chodzi tu o łamanie zasady hermetyzacji, jak uważa wielu. To kwestia tego, czy wybierasz myślenie obiektowe, czy zostajesz przy COBOLu i piszesz w składni Javy. 

PS. Oczywiście, możesz zapytać: „A co z JavaBeans, JPA, JAXB i wieloma innymi JavaAPI, które opierają się na notacji get/set? Co z wbudowanymi funkcjonalnościami Ruby, które upraszczają tworzenie metody dostępu (accessors)? No cóż, to wszystko jest naszym nieszczęściem. Dużo prościej jest zostać w prymitywnym świecie proceduralnego COBOLa, niż naprawdę zrozumieć i docenić piękny świat prawdziwych obiektów. 

PPS. Zapominałem powiedzieć, że owszem, wstrzykiwanie zależności (DI) poprzez setters jest także okropnym antywzorcem. O tym napiszę w jednym z kolejnych postów. 

PPPS. A co proponuję zamiast getters?  Polecam printers.

Orginalny tekst: Getters/Setters. Evil. Period. Więcej o problemie można przeczytać w książce autora wpisu "Elegant Objects vol. 2" [przyp. red]