D-nee tett fel hétvégén egy érdekes linket a deliciousre: Jeremy D. Miller írt egy egész részletes szösszenetet Orthogonal Code címmel, és egész jól összefoglal bizonyos programtervezési elveket.
Mondjuk ha csak annyi lenne a véleményem róla, hogy érdemes elolvasni, akkor egyszerűen fognám magam, és én is feltenném a deliciousre, hogy az a kevés ember, aki véletlenül odatéved, megtalálja. De a helyzet az, hogy egyfelől érdekesnek tartom, másrészt nem értek vele (mindenben) egyet.
Ami feltétlenül tetszett az írásban, és ami miatt minden, programozásban érintett embernek ajánlom elolvasásra, hogy konkrét példán bemutatja a különböző programfejlesztési elveket. Ráadásul mosóporreklám stílusban, azaz ilyen volt és ilyen lett összehasonlítással.
Előrebocsátanám még (egyszer), mielőtt elkezdek belemenni egyes részletkérdésekbe, hogy a cikkel alapvetően egyetértek, de néhány dolog érdemes továbbgondolásra. Az egyik, hogy ezeket a tervezési elveket szabálynak állítja be, szerintem viszont legfeljebb ökölszabálynak lehet tekinteni. Azaz alapvetően érdemes követni őket, kivéve, ha nem jók az adott helyzetben. 😀
Miért lehetnek rosszak ezek az elvek? Például ha minden egyes funkciót külön osztályba teszünk, az osztályok, interfészek száma könnyen kezelhetetlen méretűre duzzadhat. Szóval amit megnyerünk a réven (áttekinthetőbbek és szerkeszthetőbbek a lokális kódok), elveszíthetjük a vámon (struktrurálisan nehezen áttekinthető a rendszer, nagy a betanulási idő).
Emellett a sok indirekció és absztrakciós szint teljesítményproblémákhoz vezethet. Például egy hívásnak nem elhanyagolható költségei vannak (stackelés, stb.). Vagy éppen hivatkozhatnék arra a nem túl régi tapasztalatomra, hogy egy algoritmusnál a legtöbb időt az vitte el, hogy a memóriából ismételten lekért adatokat. Ezt profilerrel derítettem ki, és lokális változóba áttöltve az adatokat és újra felhasználva kb. felére csökkentettem a futási időt. Vicces dolog az a cache miss (legalábbis szerintem ez történhetett).
És a legkomolyabb, kimutatható probléma (szerintem): a “tell, don’t ask” elvet jelenleg piszkosul nem szokás adatmodellre alkalmazni. Legalábbis Java környékén az aktuális state of the art marhára nem teszi – kivéve, ha rosszul látom, hogy mi a legfejlettebb. Aminek akár még oka is lehet.
A “tell, don’t ask” elv mit is jelent? Mondd meg az objektumnak, hogy mit csináljon (állapotfüggően), ne lekérd az állapotát, és ez alapján te döntsél helyette.
Érdekes módon viszont az adatmodellek manapság egyre inkább mennek a nagyon buta, csak getter/setterekből álló modellek felé. Miért is? Én úgy tippelem azért, mert
- Ez a rész jól generálható. Az EMF alaptechnológia tipikusan arról szól, hogy valahogy összeklikkelgetsz egy EMF modellt, beállítod a genmodellt, és kapsz egy gyönyörűséges Java osztályhalmazt. Ami persze nem tud semmit azon túl, hogy getter, setter és factory hívásokkal felépíthető és bejárható.
- J2EE technológiánál az entitásokat menti le a rendszer egy az egyben adatbázisba, amely entitásoknak tipikusan szintén getter/setter metódusai vannak. Esetleg még számított mezők is beköszönnek.
Mindkét esetben az a szokás, hogy a modellbefolyásoló logikát külön Manager jellegű osztályokba tesszük, akik a tényleges igényeknek megfelelően építik (rombolják 🙂 ) a modellt.
És végül a legfontosabb hozzáfűznivalóm az íráshoz: igen, hosszú távon megéri ezeket az elveket követve tervezni, kivéve, ha amiatt, mert nem készülünk el határidőre, rövid/közepes távon befejeződik a projekt. És ennek a kezelése bizony emberi kérdés. Emiatt zárszóként Jeff Atwoodot idézném:
The guys and gals who show up every day eager to hone their craft, who are passionate about building stuff that matters to them, and perhaps in some small way, to the rest of the world — those are the people and projects that will ultimately succeed.
PS.: Mostanában Aadaam is foglalkozik azzal, hogyan érdemes nagyobb rendszereket összerakni. Ráadásul azt mutatja meg, hogyan lehet PHP-ban nem gányolni. Szép teljesítmény az is.
Hát igen. Nekem igazából az a “Tell, Don’t Ask” elvvel kapcsolatban a fenntartásom, hogy bizonyos szempontból az ortogonalitással megy szembe: szerintem tipikusan akkor fordulhatnak elő getManager().getPool().getEntity() típusú accessor-láncok, amikor a kódot szépen felkészítjük az ortogonális aspektusok lekezelésére. Nyilván a nem query, hanem command jellegű hívásoknak is átadhatóak argumentumok, de az ismét erősíti a csatolást. De ki kéne próbálni ezt a gyakorlatban, hogy valóban beigazolódik-e. 😀 Mindenesetre az biztos, hogy Pragmatikus Programozóként bármilyen ilyen ökölszabályt akkor érdemes csak követni, hogyha az éppen aktuális projektben, követelményhalmazban több előnnyel jár az absztrakció növelése, mint annak költsége.
Szerintem az accessor-láncok nem a “Tell, Don’t Ask” elv kapcsán keletkeznek. Legalábbis ha azt betartod, akkor az accessor-lánc helyett meghívod a legkülsőt, aki tudja, hogy azon belül kit kell meghívni és így tovább egyre mélyebbre.
Megint más kérdés, hogy ez mennyire építi ki a függést az alkomponensek között.
Talán a legjobb az a megközelítés, hogy az ember ezeket az elveket ökölszabályként kezeli, és ez lehetővé teszi azt, hogy bizonyos esetekben azt mondja, hogy nem veszi figyelembe őket. Ami viszont fontos, hogy azért ne tartsuk be ezeket a szabályokat, mert tudjuk, hogy adott esetben nem igazán jó, és nem azért, mert nem ismerjük őket.
Vagy van másnak jobb ötlete, hogyan kezeljük ezt?
Pont én is azt mondtam, hogy az accessor-láncok az ortogonalitás elérése közben léphetnek fel (pl. az ehhez szükséges oly gyakori “köztes absztrakciós szint beszúrása” refactoring művelet során), és ez ellentétes a TDA elvvel.
Egy (az ehhez szorosan kapcsolódó Law of Demeterről is szóló) blog post a sok közül, mely a tervezési elvek szigorúan betartását taglalja: http://haacked.com/archive/2009/07/14/law-of-demeter-dot-counting.aspx
Elolvastam a cikket, amit linkeltél. Hát, jó kérdés.
Ami fontos benne, hogy az accessor-lista nem feltétlen jelenti azt, hogy Demeter törvénye sérül. Hiszen lehet, hogy a saját osztály csatolódik vissza. 😀
Ha a vonatkozó osztály az adatmodell, akkor szinte biztos, hogy nem akarom ezt csinálni. Ha adatmodell van, akkor be akarom azt járni, és megkapni a végeredményt.
Lehetne az adatmodellbe Visitor-támogatást tenni, ez tény. De ezzel is vigyázni kell, mostanában kaptam egy 6000 soros Visitor kódot karbantartásra. Nagyjából jól meg van írva, de borzasztóan nehéz így is karbantartani.
Mindenesetre szerintem az adatmodell elérésénél lehet értelme ilyen accessorláncoknak is, gyakran az áttekinthetőbb megoldások közé tartoznak.
Persze általános esetben nem ez a nyerő, pl. nem biztos, hogy a business logic osztályait is így kezelném. 😀 És a lényeg: ha két-három szintű accessor láncod van, akkor lehet, hogy az a legáttekinthetőbb megoldás, minthogy teljesen szétcsatolni a kezelést olyan helyen is, ahol nem lenne szükséges.
És nem biztos, hogy szívesen írnék és dokumentálnék egysoros kvázi-feladatvégzés, amivel közvetlenül kikerülhető lenne ez az accessorlánc.