Cean Code - SRP (single responsibility principle), tell don't ask, god object

Az absztrakciós szinteknél már kitárgyaltuk, hogy a DRY arra képes, hogy az ismétlődő kódot beteszi alacsony absztrakciós szintre, annak a hívását meg magas absztrakciós szintre emeli. Így általánosít. Ennek az az eredménye, hogy szép rétegek alakulnak ki, minden rétegben egyre alacsonyabb absztrakciós szintű kóddal. A felhasználótól jövő adat áramlása ezeken a rétegeken keresztül történik magas absztrakciós szintről az alacsony felé, és természetesen visszafelé is, amikor a program válaszol.

Az SRP ennél annyival tud többet csinálni, hogy horizontálisan is széttagolja a kódot. Ezt úgy éri el, hogy kimondja, hogy egy osztálynak csak egyetlen feladatköre lehet. Így ha eddig mondjuk egyetlen nagy osztály - ún. god object - látott el egy csomó feladatot, akkor ezentúl több kicsi osztály fogja megcsinálni ugyanazt. Természetesen ez is eredményezhet a horizontális mellett vertikális tagolást, ha két ilyen kicsi osztály feladatát össze kell hangolni a kódban egy magasabb szintű osztályból. Az SRP megszegésének így több változata lehetséges. Ilyen például a már említett god object, aminél a felelősségi köröket mosunk össze (megsértjük a horizontális és esetleg a vertikális tagolást is). Szintén egy változat ha egymás alatti absztrakciós szinteket mosunk össze (megsértjük a vertikális tagolást), ezzel kapcsolatban is van egy kifejezés, a tell don't ask.



A tell don't ask-be most futottam bele, amikor annak kerestem utána, hogy vajon ORM használatot hogyan lehet DDD-vel összeegyeztetni. Mint kiderült a PM (persistence model) és a DM (domain model) két külön dolog, és így az ORM-et nem lehet közvetlenül a DM-hez hozzákötni, tehát csak annyit spórol a dologgal az ember, hogy kevesebb kézzel írott SQL-t fog csinálni. Mindezt a blogjában taglalja Mehdi Khalili, aki egy ThoughtWorks-ös srác. Érdekes, hogy Liz Keogh is odavalósi volt, akinek tegnap néztem a BDD-s videóját. Jó hely lehet.

Magáról a tell don't ask-ről itt lehet egy elég részletes leírást találni. Sok újat nem mond, pár sor alapján nekem az jön le, hogy nem szabad elkérni az objektumoktól az állapotukat, aztán megkérni rá őket, hogy változtassák meg azt. Helyette dönteni kell, és közölni az objektumokkal, hogy mit csináljanak. Ez elég logikus, ha fenn szeretnénk tartani az encapsulation-t. A Clean Code-ban van szó erről, amikor a 6-os fejezetben (Objects and Data Structures) megjelenik Data feje, mármint a Star Trek-es androidé. Ott elég jól kifejti Robert C. Martin, hogy mi a különbség az objektumok és az adatstruktúrák között. Az objektumok elrejtik a belső állapotukat és az implementációjukat egy felület mögé. Ezen a felületen keresztül érintkezünk velük, és csináltatunk velük dolgokat. Az adatstruktúráknál pont fordított a helyzet, ott szabadon hozzáférhetünk az adatokhoz, és az általunk írt kód szépen módosítani tudja őket. Az objektum orientált megközelítésnél így nyilván objektumoknak mondjuk meg, hogy mit csináljanak, míg a procedurális megközelítésnél adatstruktúráktól kérjük el az állapotukat, módosítjuk azt, aztán visszaírjuk. Természetesen az objektum orientált programoknál sem csak és kizárólag objektumokat használunk, pl DDD-nél is vannak value objectek, amik adatstruktúrák. Az oo inkább csak kiterjeszti a procedurális programozást egy újabb eszközzel, amit használni tudunk.

A tell don't ask tehát nagyjából arra mutat rá, hogy valami bűzlik a kódban, ha objektumaink vannak, és mégis procedurális megközelítést alkalmazunk. Ilyenkor az van, hogy az objektumokba való, alacsony absztrakciós szintű kód jelenik meg eggyel magasabb szinten, ahol már az objektum metódusait kellene használnunk, és egy interface el kellene, hogy fedje a konkrét megvalósítást.
A srác ugyanezt az SRP megsértésével magyarázza, ami nyilvánvalóan következik a fent leírtakból. Ha rossz absztrakciós szinten van a kód, akkor nyilván rossz osztályban is van, így az az osztály a saját feladatai mellett még átveszi az alacsonyabb szintű ojektum osztályának a feladatait is legalább részben. Ezt mindenképp kerülni kell.
Ezt Robert C. Martin is leírja. Ő konkrétan úgy fogalmaz, hogy a legrosszabb az, amikor adat struktúra és objektum hibridet hozunk létre, mert akkor nehezen tudunk új osztályt (implementációt) hozzácsapni ugyanazzal az interface-el (polimorfizmus, objektumoknak könnyen megy) és nehezen tudunk új metódust hozzácsapni a már meglévő osztályainkhoz, amik ugyanazt az interface-t implementálják (procedurálisan könnyen megy, mert egyetlen if-else elágazásokból álló függvényt írunk, aztán kész is, objektumoknál viszont minden meglévő osztályhoz külön metódust kell írni). Egy hibrid mindkét világ rossz tulajdonságait egyesíti, tehát nehezen módosítható lesz. Erre a hibridre a legjobb példa, amit eddig láttam, a jQuery. Az egész egy hatalmas god object, ami adat struktúra és objektum is egyben. Az egyedüli szerencséje, hogy objektumként sosem használják (nincs interface, nincs polimorfizmus), helyette csak a procedurális kódnak egy sugar syntax-et nyújt, így nem törik el. A maga nemében zseniális ez a megoldás szerintem, de én magam biztosan nem alkalmaznám, mert nem szeretek procedurálisan programozni. A god object annyiban más, mint a tell don't ask, hogy az egy másik következménye az SRP megsértésének. A kettő együtt is járhat.

Szóval nagyjából egy ilyen lavinát indítunk el, ha elkövetjüka tell don't ask-re jellemző hibát:
  1. SRP megsértése
  2. absztrakciós szintek összemosása
  3. tell don't ask megsértése
  4. encapsulation megsértése
  5. gyenge karbantarthatóság
A god object alkalmazásánál pedig valami hasonló történik:
  1. SRP megsértése
  2. felelősségi körök összemosása
  3. god object antipattern alkalmazása
  4. gyenge karbantarthatóság,
Ha ezek bármelyike feltűnik, akkor sürgősen járjunk utána, hogy az adott kód hova való, és helyezzük át a megfelelő osztályba. Ez macerás tud lenni, mert meglévő unit test-eket fog eltörni, de lépésről lépésre refactoring-al megoldható. Én személy szerint jobb szeretek nagyobb lépésekben haladni, aztán a végén committálni, ha már minden tesztet átírtam, és minden zöld. Ennek is megvannak a maga veszélyei, de én ezt így gyorsabbnak érzem, mint ezer lépésben átírni az egészet.  Nyilván kell hozzá rutin meg elég jó rálátás a projekten belüli összefüggésekre. Ha ez nincs meg, akkor könnyen kerülhetünk olyan bajba, amiből csak egy git revert húz ki.

Nincsenek megjegyzések:

Megjegyzés küldése