janos.szurovecz.hu

A régi blogom, ahova lehet írok majd még...

Getterek és setterek: ördögtől való dolgok

2013-08-05 4 perc olvasási idő

A DDD-s előadásomban utaltam rá, hogy mi a gond a getterekkel és setterekkel, de talán érdemes külön is beszélni róluk, mert rengeteg problémát okoznak. Azt hiszem mindenki, aki ezt olvassa, valamilyen IDE-t használ, mert tudja, hogy rengeteg többletszolgáltatást biztosít egy egyszerű szövegszerkesztőhöz képest. Olyan plusz dolgokat ad, amivel felgyorsítható a munka. Ilyen a getter/setter generálás is. Létrehozza az ember az osztályt, felveszi a private membereket és legeneráltatja az IDE-vel a címben emlegetett metódusokat. Kész is vagyunk, csinálhatjuk a következő osztályt, ugye?

NEM!

Vagyis attól függ, de többnyire nem. Mit tanultunk az osztályokról, mielőtt megírtuk első OOP-s “Hello World” programunkat? Egységbezárás: az adatokat és a rajtuk végezhető műveleteket egységbe, osztályba zárjuk. Valóban helyesen járunk el akkor, amikor getter/setter metódusokkal korlátlan hozzáférést biztosítunk a belső változóinkhoz? Akár lehetne mindegyik public és akkor metódusok se kellenének, nem? Öreg rókák mondják ilyenkor, hogy de bizony a metódusokban csinálhatunk plusz dolgokat is később, ha szükséges. Igazuk van, de ez inkább álom, mintsem valóság, ráadásul akkor azok már nem is igazi setterek meg getterek.

A Java bean definíciója meghatározza a névkonvenciót ezekre a metódusokra azért, hogy szabványos módon lehessen hozzáférni a kívánt propertykhez, függetlenül attól, hogy az adott osztály milyen osztályból származik, milyen interfészt implementál. De miért kellene minden osztálynak beannek lennie? Mikor kell beaneket használnunk és biztosítani a névkonvenciónak megfelelő metódusokat? Tipikusan akkor, amikor adatátvitelre használunk egy objektumot és a túloldal egyszerűen csak hozzá akar férni az objektum adataihoz. Minden objektumunk ilyen DTO típusú? Ha igen, akkor ott valami nagyon nincs rendben.

Domain objektumok

A DDD is pontosan ezt mondja. A domain osztályokban üzletorientált metódusokat hozzunk létre, amelyekben üzleti logika van: ellenőrzéseket hajtunk végre, hogy betartsuk az invariánsokat, nem engedjük, hogy az objektumunk érvénytelen állapotba kerüljön. Felejtsük el végre a vérszegény domain modellt. Az előző évtizedben még menő volt, most már nem az!

Dolgozzunk úgy, hogy a settereket elfelejtjük, a gettereket pedig egyesével, szükség esetén hozzuk létre. A kód meghálálja majd a gondosabb odafigyelést. És ugye stateless osztályokban meg se fordul a fejünkben metódusokat generálni?!

Na jó, de milyen problémákat okoznak?

Tegyük fel, hogy a TDD-t ismerjük és még ha nem is alkalmazzuk, elismerjük és egyfajta távoli célként azért kitűzzük magunk előtt, hogy de jó lenne használni (ha nem, akkor mennyé indexet olvasni). Namost hogy fejleszt az ember TDD szerint, ha metódusokat generál agyvérzésig? Sehogy. Mert TDD-ben nem írunk felesleges metódust. Mégis ki ír egy felesleges metódushoz először tesztet és utána meg implementálja azt? Legfeljebb az, akinek két anyja van.

Jó, ok, nem használunk TDD-t, lusták vagyunk (pedig inkább az a lusta, aki használ, de sebaj). Legeneráljuk a metódusokat, felhasználjuk egy részüket, committolunk és örülünk. Aztán eltelik egy hónap, és valaki más Auto Complete barátunk segítségét hívva látja, hogy bizony van ott egy setter, amit jól meg is hív és várja a csodát. Aztán a csoda helyett meg látja, hogy hoppá, néhány nem várt mellékhatás lép fel. Mert például a setter segítségével meg lehet kerülni az invariánst, amit az osztálynak be kéne tartania. Miről beszélek? Nézzünk egy User osztályt, amire teljesül, hogy minden példány benne lesz a “Global” felhasználói csoportban, amit további csoportokkal bővíthetünk:

class User {  
    private $groups;  
      
    public function __construct() {  
        $this->groups = new ArrayObject();  
        $this->groups[] = "Global";  
    }  
      
    public function getGroups() {  
        return $this->groups;  
    }  
      
    public function setGroups(ArrayObject $groups) {  
        $this->groups = $groups;  
    }  
}  

Jó ez így? Naná, hogy nem, a setterben átpasszolok egy olyan csoport listát, amiben nincs benne a “Global”, és máris megsértettem az invariánst. Tegyünk ellenőrzést a setterbe? Elég gyenge megoldás. Ráadásul nézzük meg jobban: a getterel hozzáférünk a belső objektumhoz, amiből könnyedén kitörölhetjük a “Global” csoportot. Nem lenne jobb ezek helyett a generált metódusok helyett olyanokat felvenni, hogy addGroup(), meg removeGroup()? A szó, amit keresel az “igen”. Igen, sokkal jobb lenne. És azokban már csinálhatsz (csinálj!) ellenőrzéseket, és ha baj van, dobáljad a kivételeket!

Ezek után bújj a kisördög szerepébe és nézd meg néhány osztályodat: próbáld meg elrontani az invariánst, próbáld meg invalid állapotba hozni a példányokat. Sikerült? Akkor ideje takarítani.

comments powered by Disqus