Skip to content

Kategória: Praktikák

Continuous Delivery

Miért?
Clean Code fejlesztőként szeretnék biztos lenni abban, hogy a setup helyesen telepíti a terméket. Ha ezt csak a megrendelőnél derítem ki, akkor ez már túl késő.

A zöld fokozaton a Continuous Integration folyamatot állítottuk fel a buildelés és a tesztelés számára. Ezáltal a Continuous Integration folyamat arról gondoskodik, hogy a buildelés és a tesztelés során a hibákat hamar felfedjük. Ha pl. egy változtatás a kódban ahhoz vezet, hogy egy másik komponenst nem lehet lefordítani, akkor a Continuous Integration folyamat nem sokkal a változtatás commitja után figyelmeztet a hibára. Amennyiben azonban a végén egy setup program jön létre, amelyet azonban hibák miatt nem lehet telepíteni, akkor mindennek ellenére sem értük el azt a célunkat, hogy működő szoftvert lehessen telepíteni a megrendelőnél.

Ebből az következik, hogy a setup és a kiszállítás fázisát is automatizálnunk kell, hogy gombnyomásra végrehajtható legyen. Csak így lehetünk biztosak abban, hogy telepíthető szoftvert állítunk elő. És az automatizálással biztosítva van az, hogy senki sem felejt el egy fontos lépést, pl. olyat amit eddig „gyalog” kellett megtenni. Így minden team képpes bármikor arra, hogy a termék aktuális állapotát telepítésre készen kiadja és telepítse.

Lásd még az eszközök alatt.

Iteratív fejlesztés

Miért?
Von Clausewitz után szabadon: Nincs olyan terv, nincs olyan implementáció, ami túlélné az érintkezést a megrendelővel. A szoftverfejlesztés tehát jobban teszi, ha változtat a menetirányán.

A szoftverfejlesztés természetesen mindig a tervezéstől az implementáláson át a megrendelő által végrehajtott tesztekig halad. Téves azonban az a feltételezés, hogy egy projektnek elegendő lenne egy tervezési, egy implementációs és egy megrendelői teszt fázis. Ez csak – ha egyáltalán – triviális helyzetekben működik, amikor a tervezési fázisban minden követelmény ismert. Valós projektekben azonban minden fázisnak vannak felismerései az előző fázisból. Főképpen a megrendelő tesztjeiből származnak konzekvenciák a tervezés és a implementáció számára.

Az ilyen felismerésekn azonban csak akkor tudnak befolyással lenni egy projektre, ha az eljárás nem lineáris. Ha egy későbbi fázisból nincsen visszaút egy korábbi fázisba, akkor a visszacsatolás haszontalan.

A fejlesztési folyamatnak ciklikusnak kell lennie, hogy a visszacsatolás a szoftver termékre befolyással lehessen. Főképp az a ciklus szükséges, amelyik a megrendelői tesztfázistól vissza a tervezéshez vezet. Ez azt jelenti, hogy a szoftverfejlesztésnek iteratívnak, tehát több ciklusúnak kell lennie, ami a megrendelő követelményfüzetén átível. Aki megpróbál „egyszerre” (big bang) kiszállítani, az ez ellen a felismerés ellen vét. A szoftverfejlesztési folyamatot sokkal inkább úgy kell megtervezni, hogy a követelményeken keresztül „kis falatokban, katonákban” fogyasztható legyen. Minden ilyen katonának olyan kicsinek kell lennie, hogy a tervezéstől a megrendelői tesztig az átfutás ne legyen hosszabb 2-4 hétnél. Csak akkor kapunk elég gyakran visszajelzést a megrendelőtől, hogy a megvalósítás közben ne kelljen túl hosszú ideig tévelyegnünk.

Ezáltal a szoftverfejlesztés egy tanulási folyamattá válik. A lefolyása alatta a projektteam a megrendelő követelményeiről tanul. A projektteam meghallgatja a megrendelőt, tervez, implementál és a kiad egy szoftververziót, amely a meghallgatottak megértését tükrözi. Utána a team újra meghallgatja a megrendelőt, tovább/ismét tervez az aktuális ismeretek alapján és így tovább, mindig körbe. Iterációról iterációra. Néha valamit továbbfinomítunk egy korábbi iterációból, néha újat teszünk hozzá.

De nem csak egy szoftver fejlesztése egy tanulási folyamat. A tanulásnak a szervezési szinten is végbe kellene mennie. A teamnek nem csak a megrendelőről kellene tanulnia, hanem saját magáról is. Ezért újra és újra szükség van arra, hogy megálljunk, amikor a team a saját eljárására reflektál. Az ilyen retrospektívek felismerései belefolynak a fejlesztésszervezés a következő iterációjába. Itt kapcsolódik a kék fokozat a piros fokozathoz, amihez a mindennapi reflektálás tartozik.

Természetesen minden iterációnak vége kell legyen valamikor. És ahhoz, hogy az ember tudja, hogy készen van-e, előtte meg kellet határozni, hogy mit akarunk elérni az iterációban. A célok elérhetőségét mindig csak megbecsülni lehet, ebben is segít a reflektálás, hogy a becsléseket lépésről lépésre annyira javítsuk, hogy a tervezéshez elegendők legyenek. De mikor is értük el az előre meghatározott célt? ‘What is done?‘ A legfelsőbb cél a működőképes szoftver kiszállítása a megrendelőnek. Ebből következően a cél csak akkor lehet elérve, ha kiszállításra kész szoftvert állítottunk elő. Ez különösen azt jelenti, hogy a szoftver le van tesztelve, és a setup segítségével telepíthető. A Continuous Integration segítségével folyamatosan biztosítjuk ezt. Semmiképpen sem dönthetünk kevéssel egy iteráció vége előtt úgy, hogy az egyik cél el van érve, pedig még nincs lezárva az összes teszt.

Lásd még az eszközök alatt.

Komponensorientáltság

Miért?
A szoftvernek black box építőelemekre van szüksége, amelyeket párhuzamosan lehet fejleszteni és tesztelni. Ez serkenti a továbbfejleszthetőséget, a produktivitást és a helyességet.

A CCD-értékrendszer elvei eddig kisebb kódrészletekre vonatkoztak. Mi álljon egy eljárásban, mit kellene több eljárásra bontani? Mely eljárásait tegye nyilvánossá egy osztály? Honnan kellene egy kliensobjektumnak egy szervizbjektumra szert tennie? Eddig a szoftverfejlesztés elveiről volt szó kicsiben.

Nincsen mit mondania a CCD-értékrendszernek a szoftverfejlesztés nagyobb struktúráiról? Mi a helyzet a szoftverarchitektúrával? Éppen itt lép képbe a komponensorientáltság. Eddig ugyan használtuk a „komponens” kifejezést, de inkább csak lazán és köznyelvi értelemben. Innentől fogva azonban a komponensnek valami nagyon specifikusat kell jelentenie, amit alapvetőnek tartunk a továbbfejleszthető szoftver szempontjából.

Amíg a szoftvert csak osztályokból és eljárásokból felépülőnek képzeljük el, addig úgymond a számítógépet tranzisztorszinten próbáljuk leírni. Végeredményben ez nem fog menni, mert belefulladunk a részletekbe. Még az osztályok összefoglalása rétegekbe sem segít túl sokat. Inkább egy leírási lehetőségre van szükségünk a nagyobb szoftverstruktúrákra. De nem csak erre: A leíróeszköznek implementációs eszköznek is kellene lennie – olyannak, mint az osztályok – ahhoz, hogy a modell, a terv, a leírás a kódban tükröződjön.

Az operációs rendszer folyamatai ugyanilyen architektúraeszközök, de végeredményben ezek is túl nagyok. Addig, amíg egy egy alkalmazás egy folyamatának egy EXE-je több száz vagy több ezer osztályból áll, addig nem nyerünk ezzel semmit sem.

A komponensorientáltság siet segítségünkre. Azt mondja ki, hogy egy alkalmazásfolyamatnak először is komponensekből kell állnia, nem osztályokból. Csak a komponensek építőelemei lesznek majd az osztályok. És mégis mi egy komponens? Van néhány meghatározás a komponensekre, amelyek közül két kritérium tűnik megingathatatlannak:

  • A komponensek bináris funkcióegységek. (Egy osztály azonban egy forráskódszintű funkcióegység.)
  • A komponensek teljesítményét külön(!) szerződések írják le. (Egy osztály teljesítményének leírása azonban magában az osztályban található. Eljárásai szignatúrájának összege az.)

A Clean Code fejlesztő a szoftver tervezésénél a folyamatok komponensek szerinti meghatározását keresi, amelyekből a folyamatok állni fognak. Azt kérdezi magától, hogy az alkalmazást mely „szolgáltatási blokkok” teszik ki. És ezeket a blokkokat a Clean Code fejlesztő a osztályokból történő felépítése szempontjából fekete doboznak tekinti. Ezek a blokkok jól meghatározott szolgáltatásokkal ellátott assembly-k, de ismeretlen a struktúrájuk.

Egy C klienskompmponens éppen ezért nem tud semmit az S szervizkomponensének az osztálystruktúrájáról, csak S szerződését ismeri, mely független S implementációjától. A szerződések a komponensek számára olyanok, mint az interfészek az osztályok számára. Nem véletlenül állnak a szerződések nagyrészt vagy teljesen interfészekből.

A komponensek tehát ugyanúgy a tervezés elemei, mint a implementációé. Ezt kihangsúlyozandó a komponenseket fizikailag egymástól függetlenül kell implementálni; egy bevált eszköz ehhez a komponens-munkapad, ami elkülönített Visual Studio solution-öket jelent komponensimplementálásonként. Ez nem csak az egy feladatra összpontosítást segíti, mert az ember egy komponens munkája közben az fejlesztői környezetben csak annak a kódját látja. Ezen kívül az álobjektumokat használó konzekvens unit tesztelést is segíti, mivel más komponensek forráskódja nem látható. Az ilyen kódszervezés növeli a produktivitást, mert a szerződések elkülönített szerződéseiknek köszönhetően páthuzamosan fejleszthetők. És végül a fizikai elkülönülés a kód lopódzó entrópianövekedése ellen is hat. Mert ahol a komponensek között a kapcsolatokat csak a szerződésen keresztül lehet felépíteni, ott a csatolás (coupling) laza és kontrollált.

A komponensorientáltsághoz ezért nem csak a bináris, nagyobb kódegységek elkülönített szerződésekkel tartoznak, hanem a szerződések kifejlesztése is még az implementálás előtt (Contract-first Design). Mert amint a szerződések meg vannak határozva, melyeket egy komponens importál és exportál, elkezdődhet mindentől függetlenül a munka a komponensen.

Lásd még az eszközök alatt.

Test first

Miért?
A megrendelő az úr és ő határozza meg egy szolgáltatás formáját. Egy szerviz-implementáció csak akkor pontos, ha egy kliensen keresztül hajtják meg.

Ha a komponensorientáltság azt követeli meg, hogy a komponensek szerződéseit az implementálásuktól függetlenül határozzuk meg, akkor felmerül az a kérdés, hogyan is történjen ez. Kerekasztal-megbeszéléssel? Ez is biztosan egy módszer. De jobb módszer az, ha a szerződéseket nem tervezzük sokáig a táblánál, hanem azonnal kódba öntjük őket. Komponensszerződések – vagy általánosabban: minden kód interfész – végeredményben más kódoknak API-ként szolgálnak. Ezért konzekvens és hatékony ebből a kódból kiindulva interfészeket meghatározni.Ez a célja a Test firstnek. A Test first arra az ötletre épít, hogy a funkcionális egységeket (eljárásokat, osztályokat stb.) kliens-szerviz-viszony jellemez. Ezek a viszonyok a kliens és a szerviz közötti interfész körül forognak. És ezt az interfészt a kliensnek kellene meghatároznia. A kliens a szerviz megrendelőjeként úr. Őt kell szolgálja a szerviz, ehhez kelljen tehát igazodnia a szerviz interfészének.

Egy szoftver kódegységek interfészének meghatározása ebből az okból kifolyólag kintről befelé történik. Kint a felhasználói felületen ül a legvégső kliens, a felhasználó. Ő határozza meg az UI-kódegységek vizuális/tapintható interfészét. Ők azonban a lejjebb fekvő kódrétegek kliensei. Ezek aztán a mély rétegek kliensei stb. A legalsó kódrétegek teljesítményét és interfészét tehát csak akkor lehet meghatározni, amikor a fölöttük levőké már meg van határozva stb.

Ez ellent mond a kódegységek gyakori bottom-up meghatározás szerinti hozzáállásának. A projektek gyakran kérik egy adatelérési réteg meghatározását és implementálását. Ez érthető is, hiszen az ilyen alapvető funkcionalitás látszólag minden további dolog feltétele. De ez az eljárás problémás, mert sok kudarcba fulladt projekt azt mutatja, hogy:

  • Aki lentről felfelé, bentről kifelé specifikál és implementál, az a megrendelő túl későn tud egy értéket felajánlani. Ez legalábbis frusztráló, ha nem kontraproduktív.
  • Aki bottom-up jár el a specifikációban, az az ultimatív kliens, a felhasználó pontos követelményei nélkül specifikál. Amit tehát specifikál az belefut abba a veszélybe, hogy a végén túl általános és ezáltal kezelhetetlen – vagy akár használhatatlan lesz (a YAGNI szabály megszegése, lásd fent és a piros fokozaton).
  • Aki lentről felfelé implementál belefut abba a veszélybe, hogy nem szeparál valóban. Mert amikor szükség van mélyebb rétegekre ahhoz, hogy a fölöttük lévőket implementáljuk, akkor valószínűleg nem használunk elszigetelt unit teszteket álobjektumokkal és Inversion of Control-t sem.

A Clean Code fejlesztők azonban elkerülik ezeket a problémákat. Az interfészeket nem csak az implementációk előtt (Contract-first, lásd fent komponensorientáltság) specifikálják, hanem kintről befelé és egészen gyakorlatias kódolással. Az automatizált tesztek eszközével ugyanis nagyon egyszerű az interfészeket kis lépésekben tesztek formájában meghatározni.

A Test first a szintaktikai szerződésekhez (pl. interfészek) egy szemantikai oldalt ad hozzá. A szemantika specifikálásának más, formális eljárása hiányában a tesztek az egyetlen járható út a követelmények formalizálásához. Ha valaki egy fejlesztőhöz hozzá akar rendelni egy komponenst implementálásra, akkor az jobban teszi, ha nem csak a „felületét” (API) írja elő szintaktikailag, hanem a kívánt viselkedést is tesztek formájában.

Ennek sok előnye van:

  • Az interfész formája közvetlenül kliens-meghajtott, ezáltal maximálisan releváns. A YAGNI-nak nincsen esélye.
  • A tesztek nem csak tesztek, hanem specifikációdokumentációk. Egy interfész használói ugyanúgy tanulmányozhatják, mint az implementálói. Egy külön dokumentáció messzemenően szükségét veszti. Ez megfelel a DRY elvnek.
  • A specifikációk nem csak passzív szövegek, hanem végrehajtható kódok. Ha már egy implementáció rendelkezésre áll, ellenőrizni lehet a tesztekkel szemben. Így a specifikáció és a tesztek elkészítése nem időrabló egymásra következő fázisok. Ez emeli a produktivitást. Így a minőségbiztosítás megelőzi az implementációt.

Lásd még az eszközök alatt.