Ekstremalna obiektowość w praktyce – część 7 – Nie używaj klas o więcej niż dwóch polach

Część 0
Część 1
Część 2
Część 3
Część 4
Część 5
Część 6

Z głośników benefis Marka Niedźwieckiego. Na blogu siódma zasada Jeff’a Bay’a:

Nie używaj klas o więcej niż dwóch polach

Na początek kilka wyjątków, ponieważ ta zasada jest dość ciekawa. Otóż do liczby pól w klasie nie zaliczamy serialVersionUID oraz cache dla hashCode i toString. Pierwsze z wiadomego powodu – być musi jeżeli klasa jest serializowana. Reszta być może jeżeli klasa będzie niezmienna – pola będą final i obiekty w tych polach też będą niezmienne.

Teraz do rzeczy.

Ile masz rąk?

Zazwyczaj odpowiedź brzmi dwie. Nawet jeżeli jesteś „code monkey” to nadal nie posiadasz ani chwytnych stóp ani ogona. Zatem jesteś wstanie wykorzystywać tylko dwa przedmioty na raz.

Co z klasami modelującymi dane?

Zdawać by się mogło, że zasada ta nie jest do zastosowania wobec klas, które modelują dane (jakie to klasy? W uproszczeniu te które zawierają adnotacje JPA). Szybko można jednak dojść do wniosku, że i tu mamy pole manewru. Poza klasami posiadającymi tylko jedno pole – zasada numer 3 i zasada numer 8, są jeszcze klasy, które mają za zadanie komponować klasy zawierające po jednym polu. Jeżeli taka klas będzie miała dwa pola to będzie dość.

Po co?

Ok po co to robimy. W przypadku obiektów biznesowych intencja jest jasna. Im mniej pól, tym mniej wykonywanych operacji i tym samym ściślejsza odpowiedzialność.
W przypadku obiektów DTO sprawa ma się trochę inaczej. Na pewno każdy prędzej czy później trafi na obiekt, który ma wiele(10+) pól i współpracuje z innymi obiektami o podobnym rozmiarze. Jest to często spotykany przypadek w starym kodzie. Prześledzenie w jaki sposób przekazywane są poszczególne wartości. Jak wygląda współpraca między nimi, co jak i dlaczego jest przekazywane. Znacznie prościej jest zatem zdekomponować klasę i otrzymać znacznie prostszy w ogarnięciu kod. Nie ma wtedy miejsca na zastanawianie się, które z dziesięciu pól do czego służy. Jesteśmy za to prowadzeni jak po sznurku od elementu do elementu.
Ostatnią istotną cechą tak stworzonej hierarchii obiektów jest bardzo proste sprawdzanie poprawności stanu całego zestawu. Każdy z elementów ma własny walidator, który można odpowiednio przetestować, ma własny zestaw testów, ma w końcu jasno określony cel. Dodatkowo łatwo jest też „uniezmienniczyć” obiekty wystarczy dodać kilka razy final.

Podsumowanie

Problematyczne na pierwszy rzut oka wydaje się wykorzystanie tej zasady w przypadku wykorzystywania ORM. Ostatni mój projekt poprowadzony zgodnie z tymi zasadami używa JPA2. Na początku było dziwnie. Teraz jest dobrze. Grunt to poznać zasady mapowania w JPA2. Szczególnie jak mapować klasy złożone. Szczególnym przypadkiem wykorzystania tej zasady są naturalne klucze główne, które ze swej natury organizują kod w mniejsze klasy o lepiej określonym przeznaczeniu.
To tyle. Za niedługo trzeba będzie pokazać kawałek kodu 😀

17 myśli na temat “Ekstremalna obiektowość w praktyce – część 7 – Nie używaj klas o więcej niż dwóch polach

  1. Z wszystkich zasad z ta jedna sie nie zgadzam.
    No chyba sie nie zgadzam. Bo co z referencjami do innych obiektow?

    Jesli mam usera i bede chcial z nim powiazac: dane osobowe, dane kontaktowe, token autentykacyjny to juz mam 3 informacje.
    Tak, moge zagniezdzic dane osobowe w kontaktowych, ale zaczyna sie robic dziwnie. Jak doloze kilka innych pol to mam wrazenie ze powstanie cos czego ogarnac sie nie da.
    Ilosc czasu potrzebna na mapowanie relacji rowniez wzrosnie niesamowicie, nie wspominajac juz ze gdy bede chcial wypisac na ekran informajce o telefonie i powiazanym operatorze komorkowym to powstanie gigantyczne drzewo getow gdzies (tak wiem, mow nie pytaj, ale nie wierze by przy drukowaniu na ekran to dzialalo).

    Albo zasade prosze uscislic albo to pierwsza z ktora sie absolutnie nie zgadzam/jest na duzo wyzszym poziomie rozwoju niz ja.
    Przypominam tez ze programy nie maja odzwierciedlac swiata rzeczywistego, one go modeluja. Tymbardziej kod nie powinien udawac czlowieka, a nawet czlowiek ma nie tylko 2 rece, ale tez kilku kolegow ktorych moze o cos poprosic/spytac.

    Zasada tez totalnie a’stosowalna w wiekszosci frameworkow MVC. Nie bardzo wyobrazam sobie 50% stron z wstrzyknietymi jeno 2 obiektami.
    No chyba, ze zaczniemy ukrywac fakt posiadania wielu obiektow i bedziemy prosic przez 2 o dostep do wszystkich, niemniej to tez mnie zdrowo przerasta i jakos teraz tego nie widze.

    Jako ze ekstremalna obiektowosc – tak, ekstremalna gimnastyka – nie to na koniec powtorze, ze z ta pierwsza zasada sie nie zgadzam (przynajmniej na obecnym poziomie zrozumienia)

    Michal

    PS sorry za brak ogonkow, ale nie mam PL ukladu klawiatury, praw admina, a pisanie do IT support to za duzo zachodu 😉
    M

  2. wow, teraz patrze na moj comment i jest on prawie tak dlugi jak Twoj post :E A staralem sie ograniczyc 😉
    To taka offline informacja ktora nie przejdzie przez moderacje, a jako ciekawostke zostawiam 😀

  3. Michale, widzisz wpadłeś na minę, którą nazywam „kontekstową”. Tu w klasie reprezentującej użyszkodnika chcesz pogodzić dwie rzeczy:
    1. dane biznesowe – dane osobowe i kontaktowe
    2. dane systemowe – token

    W takim przypadku dobrym rozwiązaniem jest wprowadzenie klasy SystemUser:
    class SystemUser{
    private final BussinesUser bussinesUser;
    private final SystemData systemData;
    //...
    }

    Część biznesowa nie jest „zaśmiecona” przez elementy stricte systemowe i na odwrót elementy związane z systemem nie mają informacji biznesowych. Pozwala to na swobodną wymianę jednego z elementów bez naruszania drugiego. Chcesz wymienić system autoryzacji? Proszę bardzo wymieniasz i nie dotykasz kodu biznesowego. Obecnie może nie uwiera to aż tak bardzo, ale to co wspominał Kuba na prezentacji o Springu – Java Evil Edition miała to do siebie, że silnie wiązała kod biznesowy z konkretną implementacją serwera aplikacji. Tu to powiązanie jest przeniesione poza kod biznesowy.
    Zasada ta zmusza do wykorzystywania małych klas, które będą ze sobą kooperować w ramach klas na troszkę wyższym poziomie. Koniec końców na najwyższym „rozsądnym”, w danym kontekście, poziomie otrzymasz interfejs biznesowy pozwalający na realizację jednej czynności.

    Co zaś tyczy się MVC to popatrz na to w ten sposób. Strona to zestaw elementów stałych + zestaw zmieniających się modułów. Wstrzykujesz elementy stałe + kolekcję modułów, a nie pojedyncze moduły.

  4. Bartku, nie chce sie zaglebiac w przyklady, ale o uzytkowniku da sie naprawde duzo informacji zbierac. Wiec i obiekt da sie rozdmuchac do duzych rozmiarow.
    Powiazanie wszystkich informacji ze soba w koncu bedzie wymagalo jakiegos dziwnego zapytania, szczegolnie jesli uzywamy ORMa.

    Mimo wszystko jesli obiekt jest niezmienny (immutable) to nie uwazam by musial miec tylko 2 pola. Jesli da sie zachowac zasade SRP to wprowadzenie 2-4 dodatkowych pol nie jest az takim problemem.
    Pozwala tez uniknac dziwnych zapytan. Jakich?
    Wroce do przykladu usera po Twoich zmianach.

    Majac „SystemUser su” moge wiec zrobic paskudnosc taka jak:
    a)
    su.getbussinesUser.getPersonalInformation.getContactData.getPhoneNumbers

    co jest gorzej niz zle. I z tymi getami wyglada ochydnie.
    b)
    Mozemy tez udostepnic na kazdym obiekcie po drodze metode getPhoneNumbers ktora bedzie wolala ta sama metode na galezi ponizej (bo koniec koncow powstaje nam drzewo obiektow).

    Pomijajac, ze, w tym przypadku, obiekt u samej gory korzenia bedzie mial pierdyliard metod (czego normalnie tez bysmy nie unikneli), to 2 pierwsze galezie beda musialy miec po pol pieldyriarda metod.

    Zeby uciac dyskusje o getterach: na getterze najlatwiej jest mi pokazac o co mi chodzi, bo kazdy widzial ciuchcie z milionem wagonow.
    Dokladnie to samo da sie jednak przedstawic na metodzie biznesowej. Tylko mniej osob moze zrozumiec przyklad.
    W tej opcji moze byc jeszcze ochydniej, bo kazdy obiekt po drodze bedzie musial teraz znac obiekty zwiazane z domena jakiegos specyficznego liscia calego drzewa obiektow
    (np jesli bede uaktualnial operatora zwiazanego z numerem telefonu. Teraz SystemUser musi wiedziec ze istnieje cos takiego jak operator telefonu oraz telefon)

    Co do stron to czasem framework wymaga od nas wstrzykniecia ‚czegos’.
    Teraz pracuje z tapestry wiec prosty przyklad, Request dostajemy jako pole wstrzykniete przez kontener.
    Tak samo mozemy wstrzyknac rozne komponenty strony, czy wartosci wyswietlane dynamicznie na stronie ustalamy na poziomie pola/accessor metody.

    Wspominam o tym poniewaz nie rozumiem co masz na mysli piszac o kolekcji modulow (wspomniane pola, komponenty etc zakladam ze uznajemy za te elementy stale wiec ostatni akapit chyba mozemy pominac).

    Jesli kolekcja modulow to obiekt z np mapa innych obiektow i interfacem pozwalajacym sie dobrac do wszystkich funkcji tych obiektow to powstanie nam obiekt o miliardzie odpowiedzialnosci (bedzie antyfasada i imho zlamie na pewno srp)

    Jesli nie bedzie probowal udawac fasady tylko bedzie pozwalac uzyskac dostep do konkretnych obiektow, wykonujacych logike biznesowa, to jest tak naprawde ukrycie faktu, ze te wszystkie pola mamy na stronie.
    Jest to rownie zle jak opcja numer 1 i duzo gorsze niz bezposrednie wstrzykniecie wszystkich tych pol w obiekt strony/controller (bo zwieksza bledogennosc).

    Mozna tez uznac ze strona sklada sie z malutkich elementow tworzacych calosc (komponentow), podnosi to jednak niebotycznie poziom skomplikowania i nawet w komponentowych frameworkach nie ma duzo sensu imho.

    Znaczy umiem sobie wyobrazic kod o jakim piszesz, ale nie widze w tym celu i nie wyobrazam sobie bym gdziekolwiek gdzie pracowalem, umial przekonac innych do tego.
    Osobiscie utrudniloby mi to chyba tez zrozumienie tego kodu, miast go ulatwic, ale to zalozenie ktorego raczej nigdy nie sprawdze (choc moze :))

    I z smutnych rzeczy na koniec to benefis Niedzwieckiego przegapilem wczoraj ;(

    A i ostatnia rzecz (teraz tak rili). Sorry za drugi comment. Wydawalo mi sie ze po opublikowaniu pierwszego mialem info, ze wiadomosc czeka na moderacje. Nie spodziewalem sie, ze oba wejda na raz.

  5. Wstrzymam się z komentarzem, bo korzystam z listy przedstawionej przez chłopaków na Warsjavie i jest jak widzę trochę inna niż w oryginalnym tekście Bay’a. To powoduje drobne nieścisłości.

    W skrócie powiem tylko, że o kolekcjach porozmawiamy mam nadzieję przed świętami, a o getterach i setterach po świętach. Ponieważ tych właśnie spraw dotyczą dwie ostatnie z zasad Jeff’a Bay’a

  6. ok, to jak dojedziemy do konca listy to jeszcze raz sie nie zgodze z tym punktem 😛
    Sorry, ale rozumiem opakowywanie listy i przyjmuje ze przy geterach zwracamy obiekt biznesowy reprezentujacy ja.

    Co do metod zastepujace gety to chetnie poczekam, bo nie przecze, ze moge cos zle rozumiec.
    Chetnie bym zobaczyl projekt gdzie sa po 2 pola na klase.
    Kojarzysz moze cos OS co przestrzega tej zasady? Z JUGu/konf/w jakims tekscie Bay’a?

  7. Z listami jest tak, że nie powinniśmy pracować z „gołym” Collection/Array tylko z jakimś obiektem udostępniającym metody biznesowe plus coś w stylu scalowego foreach.

    Co do getterów to jest to dość skomplikowany problem praktyczny.

    Co do zasad to trzeba by zapytać chłopaków ze Szczecina, bo to oni najwięcej mają do czynienia z takim kodem.

  8. I czy to lista, mapa czy set to ma to duzo sensu i w pelni rozumiem, popieram. Interface takiej kolekcji jest duzo czytelniejszy.

    Odkrylem ta zasade w pierwszym projekcie w ktorym bralem udzial i projektowalem spory kawalek struktury danych. Zauwazylem wtedy ze moja ‚oczywista’ struktura mapy<map<map<map<list (czy cos takiego), nie jest dla wszystkich oczywista 😉
    Troche nerwow mnie kosztowalo stworzenie do tego fasady, ale chyba pomoglo koniec koncow 🙂

    przeciw getterom to rozumiem dlaczego ludzie sie przeciwstawiaja. Kojarze tez troche literatury, ktora tlumaczy dlaczego nie i zgadzam sie.
    Jednak wtedy zostajemy z przypadkiem jak w pkt b. Nie jest to zle, wrecz przeciwnie, ale nie w przypadku gdy mamy tylko 2 kolaborantow na klase.
    Jesli bedziemy sie trzymac tej zasady tak bardzo stricte mocno, to bedziemy miec bardzo wiele obiektow udostepniajacych te sama metode i tylko FWD wykonanie metody. To mi sie nie podoba, bo oznacza duzo powtorzen w kodzie, zmeiniajac jedynie obiekt na ktorym wywolamy metode. Dla przykladu updajtniecie nr telefonu

    SystemUser:
    updatePhoneNo(oldNumber, newNumber){
    businessUser.updatePhoneNo(oldNumber, newNumber);
    }

    BussinesUser:
    updatePhoneNo(oldNumber, newNumber){
    personalInformation.updatePhoneNo(oldNumber, newNumber);
    }

    PersonalInformation:
    updatePhoneNo(oldNumber, newNumber){
    contactData.updatePhoneNo(oldNumber, newNumber);
    }

    ContactData:
    updatePhoneNo(oldNumber, newNumber){
    phoneNumbers.updatePhoneNo(oldNumber, newNumber);
    }

    a gdyby to nie byl najprostszy przyklad tak z glowy, tylko prawdziwy system to bym po drodze mial jeszcze z 2-3 takie wywolania.
    Gdyby pozwolic userowi znac jego dane kontaktowe (poprzez jeden obiekt PersonalAddressBook czy cokolwiek) to ilosc metod identycznych zmniejszyla by sie niewspolmiernie.
    Przy 2 polach na klase bedziemy miec duzo klas, (co dla javy nie bedzie problemem), z wieloma powtorzonymi wywolaniami metod (znow java sobie powinna wydajnosciowo poradzic, bo zoptymalizuje kod) i pewni latwo jest to ogarnac, ale:
    a) tworzymy mnostwo bezuzytecznego kodu
    b) moze to glupie, ale mialbym problem nazwac wszystkie obiekty sluzace tylko jako interface pomagajacy zwiekszyc ilosc pol dostepnych 😉

    Ok, ok. Zamykam sie i czekam spokojnie na kolejne wpisy. Wzglednie mozemy przeniesc dyskusje na maila. Bedzie ciut wygodniej.

  9. A czy jeżeli nie znałbyś implementacji to też być narzekał? Kod pisany wg. tych zasad ma ciekawą własność. Mianowicie zaczyna być zbliżony do kodu funkcyjnego (w sensie CO, a nie JAK).

  10. zakladam ze ja tworze implementacje i ja mam sie trzymac tych zasad.

    Jak dostaje gotowe API to mnie smyra kto i jak to zaimplementowal, wole programowac do interfacu.

  11. no dobra, ale pisanie tego bedzie trudne. Ekstremalnie.
    To nazywam ekstremalna gimnastyka, pisanie tego, nie korzystanie.

    Jak dla mnie to ta zasada jest przekombinowana. Wczesniejsze i nastepne sa ok. Ta jest conajmniej dziwna.

    Wydaje mi sie ze moge osiagnac to samo, ale w przypadku wiekszych obiektow naturalne dla mnie jest, ze maja oni wiecej niz tylko 2 kolaborantow. Ilu?
    Tego nie wiem.
    Wprowadzilbym tu zasade zdrowego rozsadku i staranie trzymania sie SRP.

  12. Obaj pomyliliście parę rzeczy, obaj macie trochę racji i trochę nieracji 🙂

    1) To są heurystyki a nie zasady. Trzymanie się ich na sztywno w prawdziwym projekcie pewnie by najlepiej się nie skończyło. Ale na potrzeby gimnastyki są bardzo ok.
    2) Nie łączcie JPA (czy dowolnego innego kodu wymuszonego jakąś technologią / zewn. API) z modelowaniem problemu. Te heurystyki służą modelowaniu obiektów domenowych a nie mapowania DB. Z resztą wykorzystywanie klas JPAowych jako modelu biznesowego to zwykle kiepski pomysł – model podlega zmianom w czasie tworzenia kodu (ujawniają się nowe relacje, odkrywamy, że coś powinno być gdzie indziej itp.) i dłubanie za każdym razem w mapowaniu (i potencjalnie w relacjach bazodanowych) jest proszeniem się o kłopoty i niepotrzebnym zmniejszaniem naszych możliwości modelowania/projektowania kodu.
    3) Obiekty kodu to nie obiekty realne. Tak jak pisaliście użytkownik jest jeden, ale może być (powinien?) reprezentowany przez różne encje w zależności od ich wykorzystania (warto poczytać o contextual models, bounded contexts, responsibility-driven design, no i oczywiście SRP i zasada segregacji interfejsów, którą można też nagiąć do tego) To zupełnie zmienia sposób pracy z kodem i stosowanie takich reguł jak ta o dwóch polach przestają być problemem.

    Paweł

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax