Az infokukac blog 2010. márciusában átköltözött. Átirányítás folyamatban...

Az átirányítás automatikus. Ha mégsem sikerülne, látogasd meg az új oldalt a http://infokukac.com címen, és frissítsd a könyvjelzőidet!

2009. szeptember 29., kedd

Álljunk meg egy szóra!

Már sokszor utaltam arra, hogy mennyire fontos az, hogy a fejlesztett szoftverünk kódja jól olvasható legyen. Egy jól olvasható kód magáért beszél: csökkenti a fejlesztők közötti közvetlen kommunikáció szükségességét, valamint lehetővé teszi, hogy a szoftver könnyebben továbbfejleszthető, karbantartható legyen.

Ha jó a kód, akkor abból "visszafejthető" maga az üzleti igény, a mögöttes üzleti gondolkodás. A mai post-ban való világbeli példákat vettem elő.

Az éjjelnappali akciója

Nemrég egy éjjelnappali kisbolt akciójára lettem figyelmes:

2l Coca cola, 
2l Cola light, 
2l Zéró-t Ha 
vesz ajándék po- 
harat adunk 2db 
esetén!

Nem kicsit vicces a megfogalmazás, de amúgy egy kis gondolkozás után mindenki számára érthető: "2db 2L-es Coca Cola üdítő (Coca Cola, Coca Cola Light vagy Coca Cola Zero) megvásárlása esetén ajándék poharat adunk!"
 
A probléma gyökere a szavak sorrendjében keresendő. A mondat közben elejtett "ha vesz", ill. a mondatvégi "2db esetén" a gondolat elejére kívánkozik, így érthető tisztán a gondolatmenet: ha X, akkor Y

BKV-vonaljegy

Fogósabb a BKV-vonaljegy hátoldalára írt üzenet megfejtése (bocs, már érvényesítettem a jegyet, ezért egy része nem látszik a képen).

(i) Érvényes egy utazásra, átszállás és az utazás megszakítása nélkül, autóbuszon, villamoson, trolibuszon, fogaskerekűn a járatok teljes hosszán, HÉV-en a Budapest határán belüli vonalszakaszokon. 

(ii) Az érvényesség időtartama alatt a metróhálózaton belül (ideértve a földalattit is) átszállásra jogosít. 

(iii) A jegyet a metrón és a földalattin az utazás megkezdése előtt, a többi közlekedési eszközön a felszállás után kell érvényesíteni. 

(iv) Bélyegzős érvényesítés esetén a kezeléstől számított 60 percig jogosít utazásra, az éjszakai járatokon 90 percig érvényes. 

(v) A jegyet ellenőrzéskor fel kell mutatni, és az ellenőrzést végző személy kérésére át kell adni.


Mit is jelent ez? Az Index is foglalkozott ezzel az üggyel, az ő olvasatukban: "Január elsejétől a BKV azon járatain, ahol dátum kerül az érvényesített vonaljegyre, 60 percig lehet utazni az átszállások számától függetlenül. Ez igaz a metróra, a kisföldalattira és egyes felszíni járatokra is."

Én először az Index videójából értesültem erről a változásról, ezért "elhittem", amit ők gondoltak. Azóta a barátnőm érdeklődött egy jegypénztárnál, ahol az ott dolgozó munkatársnő egyértelműen letagadta, hogy ez igaz lenne. A kérdés elég konkrét volt: "ha metróval utazom, utána átszállhatok-e villamosra, miközben ugyanazt a vonaljegyet használom?"

Gondoltam, előveszem a vonaljegyet, és elkezdem megérteni mi is van rá írva.


  • (i) mondat jelentése: nem lehet megszakítani az utazást, ill. átszállni a jeggyel, ha nem metrón közlekedünk.
  • (ii) mondat jelentése: a metróhálózaton belül lehet átszállni a jeggyel.
  • (iv) mondat jelentése: ha bélyegzős érvényesítés történik, 60p-ig lehet utazni a jeggyel akár átszállással is (éjszakai járatokon 90p-ig).
  • (iii) ill. (v) mondat jelentése ebben a kontextusban nem releváns.

Na, és mi is van akkor, ha először metrón utazom, és utána átszállok egy villamosra? A metróhálózaton átszállhatok, és 60p-ig érvényes a jegyem (nap közben), ha villamoson utazom, akkor viszont hiába kerül rá a bélyegző, nem szállhatok át másik járműre új vonaljegy érvényesítése nélkül. De akkor mi van, ha metróról szállok át villamosra?!!

Teljesen megtévesztő a szövegezés, nem egyértelmű, ráadásul ha csak egyes elemeit vizsgáljuk, teljesen más következtetésre is juthatunk (pl. az (iv) mondat esetén). 

Mi van akkor, ha autóbuszon felkerül a bélyegző a vonaljegyre, és már több, mint 60p-e utazunk? Pl. a 7-es buszon a két végállomás között 43p a menetidő, ez egy jó kis fővárosi dugóban könnyen túlléphető. (Az (i) mondat azt mondja viszont, hogy a teljes vonalon utazhatunk.)

Ha csak a metrókra érvényes a 60p-es bélyegzős érvényesítés (mivelhogy a többi járműtípuson úgyis csak átszállás nélkül utazhatunk egy jeggyel), akkor miért írják oda, hogy az éjszakai járatokon 90p-ig érvényes a jegy? A metrónál nincs is éjszakai járat.

Amúgy az angol szöveg azt sejteti, hogy kizárólag a metrón belül lehet átszállni. Mi lett volna, ha ezt írja a BKV?:

"Érvényes a teljes metróhálózaton belül tetszőleges átszállással a jegy érvényesítésétől kezdve 60p-ig. A többi járattípuson (autóbusz, villamos, trolibusz, fogaskerekű) a járat teljes hosszán egy utazásra érvényes - átszállás és az utazás megszakítása nélkül." (De még mindig nem értem az éjszakai járatokat.)

Szóval számomra ez teljes káosz. (Ha vki megtalálja a megfejtést, kérem, ossza meg!)

GUI-forráskód

Az alábbi leegyszerűsített Javascript függvényrészlet egy felhasználói felületen engedélyez control-okat. (Elrejti/megjeleníti őket, enabled-dé/disabled-dé teszi őket.) A spagetti kódból csak azokat a részket hagytam meg, amik az ún. "szolgalatiUtonFelterjesztendo" checkbox-szal állnak kapcsolatban.

    setAlapadatokFieldDependencies: function() {
...
        szolgalatiUtonFelterjesztendo.show();

...

        szolgalatiUtonFelterjesztendo.enable();
        if (this.sajatSorszamosRendelkezes) {
...
            szolgalatiUtonFelterjesztendo.disabled = true;        
        } else {
            if (!szolgalatiUtonFelterjesztendo.getValue()) {
...
            } else {                
...
            }
        }

        if (this.erkeztetesEllenorzese) {
...
            szolgalatiUtonFelterjesztendo.disable();
        }

        if (this.fax) {
...
            szolgalatiUtonFelterjesztendo.hide();
            szolgalatiUtonFelterjesztendo.setValue(false);
        }

        if (rosszCimzes) {
...
            szolgalatiUtonFelterjesztendo.setValue(false);
...
            szolgalatiUtonFelterjesztendo.hide();
        }

...

    },

Az eredeti kód ennél sokkal hosszabb, és kb. egy tucatnyi control állapotát állítja. Meg tudja-e mondani vki ezek után, hogy:

  • mikor látható a "szolgalatiUtonFelterjesztendo" checkbox?
  • mikor engedélyezett (enabled)?

Ehhez már nem kevés gondolkozás kell. Természetesen létezik megoldás, amellyel a kód sokkal áttekinthetőbbé tehető:

    setAlapadatokFieldDependencies: function() {
        if (isSzolgalatiUtonFelterjesztendoVisible()) {
            szolgalatiUtonFelterjesztendo.show();
        } else {
            szolgalatiUtonFelterjesztendo.hide();
        }

        szolgalatiUtonFelterjesztendo.disabled = isSzolgalatiUtonFelterjesztendoDisabled();
        ...

    },

    isSzolgalatiUtonFelterjesztendoVisible: function() {
        if (this.fax) {
            return false;
        }

        if (this.rosszCimzes) {
            return false;
        }

        return true;
    },

    isSzolgalatiUtonFelterjesztendoDisabled: function() {
        if (this.sajatSorszamosRendelkezes) {
            return true;
        }

        if (this.erkeztetesEllenorzese) {
            return true;
        }

        return false;
    },
        
Ezek után már nem lehet senkinek sem kétsége afelől, hogy mikor kell megjelennie a checkbox-nak. Ráadásul ha vki ismeri az üzleti problémát is, akkor szinte teljes megvilágosodás érhető el a kód olvasásakor.

Konklúzió

Ugyan általános receptet nem lehet adni arra, hogy hogyan kommunikáljunk, fogalmazzunk, azonban van pár dolog, amire célszerű törekedni: 

  • Legyünk egyértelműek!
  • Törekedjünk a lényegre! Mellőzzük az oda nem illő, felesleges részleteket, csapongásokat!
  • Ne bonyolítsuk túl! 
  • Legyen egy folyamatos gondolatmenete annak, amiről írunk! Legyen eleje, és vége! 

Én, amikor írok (akár forráskódról, akár doksiról, akár blog írásáról van szó), a leírt részeket újra és újra elolvasom az elejétől. Megpróbálok szűz szemmel olvasni, és próbálom átérezni, hogy hogyan is kapcsolódnak a gondolatok egymáshoz, hogyan építkeznek. Eközben mondatokat, bekezdéseket, programkódokat, gondolatokat török ketté, vonok össze. Az is lehet, hogy kidobok teljes bekezdéseket, mert felesleges szócséplésnek tartom őket. A lényeg, hogy miközben írok, saját gondolataimat rendszerezve egy sokkal világosabb, értékesebb írást készítsek el.


2009. szeptember 17., csütörtök

Nevezd meg és uralni fogod!

Az elv lényege az (üzleti) szoftverfejlesztésben az, hogy ha programot írunk, akkor a kódban mindent próbáljunk névvel, ill. megfelelő névvel illetni.

Ökölszabály: a kódban lévő azonosítók kövessék az üzleti terület fogalmi rendszerét. Építsünk egy mindent átható nyelvet (Ubiquitous Language), amelynek célja, hogy egy közös nyelvet beszéljünk az ügyféllel és ugyanezen fogalmakat használjuk a program kódjában is.

Mit is jelent ez a szoftverfejlesztő szemével nézve a kódban?

Van pár "alacsony szintű" szabály, amelyet érdemes követni:

  • Amikor épp egy új változót, metódust, osztályt deklarálunk, gondoljuk végig: az adott elnevezés valóban azt jelenti-e, amit a változó, metódus szándékszik kifejezni? Ha egy kódrészlet már régebben írodott, akkor is alkalmazzuk az elvet a már meglévő fogalomra.
  • Ha épp egy összetett metódust írunk, akkor a metódus törzsében kialakult külön választható logikákat emeljük ki külön metódusba. Hogyan tudunk (értelmes) nevet adni az új metódusnak (mit csinál)? Ha nem megy, valószínűleg nem volt jó ötlet a kiemelés, vagy valamit nem jól csinálunk.
  • Szervezzük ki az osztályokból az összetartozó mezőket, metódusokat, amelyek rejtett (implicit) fogalmakat takarnak:
    • Ha az osztályban sok a mező, sok a metódus, akkor valószínűleg több felelősséget is ellát az osztály. Az új osztály bevezetésével és a kiszervezéssel csökkenthető az osztály felelőssége, csökken a mérete, ezáltal növekszik az áttekinthetőség, és könnyebben unit-test-elhető a kód.
    • Ha logikailag több mező is összetartozik, és ezeknek tudunk egy összefogó nevet adni, akkor egy külön osztályba szervezzük ki őket. Pl. egy olyan programban, ahol szerződéseket nyilvántartanak, célszerű lehet a szerződések egyes adatcsoportjait összefogni, és külön osztályban tárolni. (Pl. szerződő fél adatai, szerződés hatálya stb.)
  • ...

Ha egy új fogalmat vezetünk be a szoftverben, akkor célszerű az üzleti terület fogalmi rendszerét figyelembe venni. Ideális esetben már létezik ilyen fogalom, és azt használhatjuk. Van-e esetleg hasonló fogalom? A hasonló fogalom hogyan viszonyul az általunk bevezetésre kerül fogalomhoz? Lehet, hogy célszerű inkább a meglévő, hasonló fogalmat használni, és végiggondolni, hogy ez hogyan befolyásolja a szoftverünket.

Ha nem tudjuk pontosan az üzletben már meglévő fogalommal lefedni a kívánt új fogalmat, vagy tanácstalanok vagyunk, mindenképpen célszerű egyeztetni az üzleti szakértővel, és megosztani vele a gondolatainkat. A végeredmény lehet az is, hogy végül nem kerül bevezetésre az új fogalom, mert:

  • Rosszul közelítjük meg a problémát. Nem kell tovább erőltetni a dolgot.
  • Nem látjuk egyelőre tisztán az üzleti problémát, ezért tegyük is félre a problémát.

Manapság a modern fejlesztő eszközök az átnevezésekre (Rename Method, Rename Class, Rename Field stb.), ill. más egyszerűbb kódtranszformálásokra (pl. Extract Method) simán képesek. Használjuk őket!

Miért jó ez?

A sztandard válasz: segít a kód olvashatóságában, a kód karbantartásában. 

Valójában a tudatos elnevezés, és a fogalmak "hajkurászása" ennél sokkal többet segít. A fogalmak kutatásának nem csupán egy tisztább, érthetőbb kód az eredménye; a fogalmak tudatos rendszerezése segít egy tiszta mentális modellt is megalkotni. A kód írásakor és a fogalmi rendszer megismerése során a hosszú távú memóriánkban kialakuló sémák lehetővé teszik, hogy otthonosan mozoghassunk a kódban és az üzleti területen egyaránt. Az újabb és újabb megismerések, felismerések magasabb szintű sémákat eredményeznek. Minél rendszerezettebbek a sémáink, annál könnyebben találunk megoldást a felmerülő problémákra, és annál könnyebben tudjuk magát a kódot is kezelni.

Közismert tény, hogy a rövid távú memória 7+-2 tételt képes rövid távon (kb. 20mp-ig) tárolni. A korlátozott rövid távú memóriánk hatékonyan felhasználható, ha képesek vagyunk tömbösíteni a tételeket (pl. egy 7 jegyű telefonszám megjegyzésekor nem a hét számjegyet külön-külön jegyezzük meg, hanem helyette három számot próbálunk memórizálni. Pl. 334-23-40). A tömbösítés egy hosszú tanulási folyamat eredményeképpen jöhet létre, amely során a saját sémáinkat, absztrakcióinkat kialakítjuk. Ha egy nagyobb kódbázison dolgozunk, és különféle részeit egyszerre szerkesztjük, elemezzük, a magasabb szintű sémáink lehetővé teszik, hogy a kód nagyobb részeit egyszerre legyünk képesek áttekinteni. 

Az üzleti fogalomrendszer megértése, leképezése

Eric Evans DDD könyve a fogalmak tudatos kereséséhez egy külön részt is szentel, "Making implicit concepts explicit" - mondja. Az ingyenes, angol nyelvű kivonatban a "Bring key concepts into light" c. fejezet szól erről. Nagyon jól leírja, hogy hogyan fejlődik a domain modell, ahogy egyre jobban megértjük:

[Fordítás] "A refaktorálást apró lépésekben kell végezni. Vannak olyan esetek, amikor sok apró változtatás ellenére sem lesz sokkal értékesebb a kód, míg más esetekben kevés változtatás is hatalmas különbséget eredményez, ezt áttörésnek (Breakthrough) nevezik.

A projekt kezdetén a modell durva és felszínes, amit aztán a kóddal együtt finomítunk annak alapján, ahogy egyre mélyebbé válik a tudásunk a domain-ről, és ahogy egyre jobban megértjük a benne rejlő érdekeltségeket. Új fogalmakat és absztrakciókat vezetünk be, aztán a kódot refaktoráljuk. Minden egyes finomítással a kód világosabbá válik, másfelől pedig megteremti egy igazi áttörés előfeltételeit.

Az áttörés gyakori velejárója a gondolkozásmódbeli változás, azaz annak a módja, ahogyan a modellt látjuk. Ugyancsak a forrása lehet a projekt jelentős előrehaladásának, azonban akadályt is jelenthet. [...]

Ahhoz, hogy áttörést érjünk el, a rejtett fogalmakat egyértelművé, kifejezetté kell tenni. Amikor a domain-szakértőkkel beszélünk, sok ötletet és tudást osztunk meg egymással, aminek során bizonyos fogalmak a mindent átható nyelv részévé válnak, míg mások már a kezdetekkor is figyelmen kívül maradnak. Ezek az ún. rejtett fogalmak, amelyeket csak a többi modellbeli fogalom magyarázatához használunk. A kód finomításának ezen szakaszában néhány rejtett fogalom a figyelmünk előterébe kerülhet. Amennyiben úgy vesszük észre, hogy kulcsszerepet játszanak a modellben, akkor ezen a ponton kifejezésre kell juttatnunk őket, és létre kell hoznunk a megfelelő osztályokat és kapcsolatokat számukra. Ha mindezeket megtesszük, akkor esélyünk lehet egy áttörésre.

A rejtett fogalmaknak a felszínre kell törniük. Amennyiben ezek domain fogalmak, akkor meg kell jelenniük a modellben és a kódban is. Hogyan ismerhetjük fel őket? Az egyik módja, hogy megfigyeljük a nyelvet. A modellezés és fejlesztés során használt nyelv ugyanis sok információt rejt a domain-ről. A projekt elején ez még nem feltétlenül igaz annyira, és az is lehet, hogy az információk egy részét helytelenül használjuk. Előfordulhat, hogy egyes fogalmak nem egyértelműek, esetleg teljesen rosszul értelmeztük őket. Mindez az új domain tanulási folyamatának a része. Ahogy a mindent átható nyelvet építjük, a kulcsfogalmak utat törnek maguknak. Ez az a pont, ahol a rejtett fogalmakat felderíthetjük.

A kódrészletek olykor nem teljesen tiszták. Bizonyos fennálló kapcsolatok nehezen követhetővé teszik a kódot, vagy a metódusok olyan bonyolultak, hogy szinte lehetetlen megérteni, mit csinálnak. Ez ügyetlenség a kódolás során, de egyben a megfelelő hely arra, hogy fény derüljön egy rejtett fogalomra. Talán valami hiányzik. Ha egy kulcsfogalom hiányzik a „kirakós játékból”, akkor másoknak kell helyettesíteniük a hiányzó funkcionalitást. Ennek következtében néhány objektum „elhízik” olyan működés hozzáadása miatt, amelynek nem is ott kellene lennie, és mindez a kód tisztaságának rovására megy. Keressünk rejtett fogalmat, és ha megtaláltuk, tegyük egyértelművé, azaz vezessük be a modellbe. A modell refaktorálása egyszerűbbé és rugalmasabbá teszi azt.

A tudás megszerzése (felépítése) során néha ellentmondásokhoz juthatunk. Ha valamit mond a domain szakértő, az ellentmondásba kerülhet azzal, amit mások helyesnek tartanak, azaz az egyik követelmény ütközhet egy másikkal. Az ellentmondások nem biztos, hogy valóban azok: lehetnek ugyanannak a dolognak más nézőpontjai, vagy egyszerűen csak az értelmezés pontatlanságának eredménye. Meg kell próbálnunk összhangba hozni az ellentmondásokat, aminek során fontos összefüggések kerülhetnek figyelmünk előterébe. Még ha ez nem is így fog pontosan történni, a modell tisztaságát akkor is figyelmünk előterében kell tartanunk.

Egy másik módja a fontos fogalmak előásásának a domain szakirodalmának használata. Rengeteg könyv áll rendelkezésre szinte minden lehetséges témában, amelyek hatalmas tudást közvetítenek a megfelelő domain-ről. Ezek a könyvek általában nem tartalmaznak modelleket az általuk bemutatott domain-ről. A hordozott információt fel kell tudni dolgozni, leszűrni a lényeget és finomítani azt. Mindazonáltal a könyvekben található információk hasznosak, és mély betekintést nyújtanak a domain-be."

"Nevezd meg és uralni fogod!" - tartották a középkorban a démonűzők: ha kimondod, amitől félsz, akkor megszabadulsz tőle.