Skip to content

Szerző: admin

A terv (architecture) és az implementáció nem fedik egymást

Miért?
Azok a tervezési iratok, amelyek a megvalósítással nincsenek összhangban, többet ártanak, mint használnak. Ezért nem a tervezést kell feladni, hanem az inkozisztencia esélyét kell minimalizálni.

A szoftverfejlesztés egyik legalapvetőbb problémáját jelentik az olyan implementációk, melyeken nem lehet látni, hogy előre meg lettek tervezve. Ilyenkor tervezési diagrammok lógnak a falon, melyeknek már alig van köze a kód valóságához. Ennek az oka a legalapvetőbb DRY elv megsértése: A terv és az implementáció ismétlődései ugyanannak a dolognak, mégpedig a szoftver struktúrájának. Mivel az implementáció a tervezést követi, és a munka dandárját jelenti, ezért mindkettő gyorsan kilép az összhangból, amennyiben a struktúra implementáció alatti változásait nem vezetjük be újra és újra a tervbe. A tervezési diagramoknak az implementáció kezdete után nem sokkal már nincsen semmi értékük.

Hogyan lehet javítani a szituáción? Fel kellene hagyni a tervezéssel, ha végülis az implementációban él a „struktúra valósága”? Nem, biztosan nem. A tervezésre szükség van. Tervezés nélkül nincsen elképzelésünk a célról. De a tervnek és az implementációnak meg kell felelnie a DRY elvnek. Ezért a tervezésnek és az implementációnak, amennyire csak lehet, nem szabad egymást fednie. Az interfészüknek vékonynak kell lennie. Amikor ez igaz, akkor már nem egymás ismétlődései, hanem különböző dolgokat írnak le. Ez azt jelenti, hogy: A terv/architektúra nem törődik az implementációval és az implementáció nem törődik az architektúrával.

És hol van a választóvonal? Az úgynevezett komponenseknél (lásd lent praktikákat). Az architekt nem törődik a komponensek belső felépítésével. Az architekt számára ezek fekete dobozok, melyek osztálystruktúrája nem architektúra-releváns. Fordítva viszont a komponens implementálójának az architektúra irreleváns. Amit neki implementálnia kell, az a komponensének importált és exportált szerződéseiből derül ki számára. Nem kell ennél nagyobb összefüggéseket ismernie.

Az architektúra feladata ezáltal az is, hogy a szoftvert komponensekre bontsa, azok függőségeit meghatározza és a szerződések teljesítéseit leírja. Ezeket a struktúrákat aztán csakis az architektek fogják gondozni. És az implementáció feladata az architektúra által meghatározott komponensek megvalósítása. Hogy hogyan teszik ezt, az nem architektúra-releváns. A komponensek belső struktúrája az architektúra számára láthatatlan.

Az implementáció tükrözi a tervet

Miért?
Az olyan megvalósítás, amely tetszés szerint eltérhet a tervtől, egyenes úton vezet a karbantarthatatlanságba. A megvalósításnak szüksége van egy terv által meghatározott fizikai keretre.

Az architektúrának és az implementációnak nem kellene fednie egymást, hogy a DRY elvet betartsuk. Így lehet elkerülni az inkozisztenciát, ami az által léphetne fel, hogy az egyik oldalon megváltozik valami anélkül, hogy a másik oldalon ezeket a változásokat követnénk.Mindennek ellenére az architektúra tesz kijelentésket az implementációról. Nem a részleteiről, hanem az alapvető formájáról. Az architektúra határozza meg a struktúra elemeit és azok viszonyát egy kódrendszeren belül. Az implementáció tehát az átfedések hiányában sem létezhet függetlenül az architektúrától, hanem inkább csak benne.

Éppen ennek kell megjelennie az implementációban. Így könnyebben érthető lesz, így könnyebben lehet biztosítani, hogy az implementáció tényleg kövesse az architektúrát. Az architektúra által különböző absztrakciós szinten meghatározott struktúra elemeknek ezért nem kellene egy nagy „kódvödörbe” (pl. egy nagy Visual Studio solution) „összevonásra kerülniük”. Sokkal jobb, már csak a magas produktivitás és a tesztelhetősg érdekében is, az architektúra logikai struktúráit a lehető legfizikaibb módon megjeleníteni.

  1. Az architektúra által különböző absztrakciós szintre tervezett struktúráknak amennyire csak lehet tükröződniük kell a kód elrendezésben. Ez egyik oldalon azt jelenti, hogy az architektúra mint struktúra elemeket elsősorban fizikai kódegységeket használ. És a másik oldalon ezeknek a struktúraelemeknek a forráskódban ill. a repository kód elrendezésében tisztán kivehetőnek kell lenniük.
  2. A struktúraelemek implementációja közben és különösen a komponensek belsejében lehetetlennek kell lenniük az „mellékes” architektúraváltoztatásoknak. Aki egy struktúraelemben vagy elemen dolgozik, tehát egy részen, annak nem szabad ad hoc megváltoztatnia a környező struktúrát, tehát az egészet. Csak amikor ez biztosítva van, akkor nem fog kontrollálatlanul nőni a szoftver entrópiája. Ez fontos, mert az architektúra fő célja, a szoftver entrópiájának és ezzel a komplexitásának minimalizálása.

A tervezésre szükség van. Az implementációnak nem szabad megtorpedóznia a tervezést. (Természetes, ha az implementáció felismerései visszahathatnak a tervezésre.) Ezért kell a tervezést és az implementációt szétválasztani. És ahol ez nem lehetséges, ott a tervezésnek az implementáció eszközeivel kellene dolgoznia és az implementációnak a tervezést fizikailag tükröznie kell.

You Ain´t Gonna Need It (YAGNI)

Miért?
Az olyan dolgok, amikre senkinek nincsen szüksége, azok értéktelenek. Ne pazaroljunk rá tehát időt.

A YAGNI elv (You Ain´t Gonna Need It) a szoftverfejlesztés egyik legegyszerűbb – és a DRY elv után leggyakrabban megsértett elve. Ezért a YAGNI nem csak a piros fokozat elején áll, hanem itt, az értékrendszeren keresztül vezető útnak majdnem a végén is.A YAGNI szabály a követelménypontosságból és a termék materialitásának a szoftverfejlesztésben különleges viszonyából alakult ki. A követelmények notorikusan pontatlanok vagy váltakozók, a termék pedig, amiben meg kellene valósulniuk, immateriális. Összehasonlítva a gépészettel vagy az építészettel az anyag végtelenszer rugalmasabb és elvileg viszonylag kevés ráfordítással szinte minden követelményhez hozzá lehet igazítani. Magas illékonyság ill. pontatlanság tehát nagy rugalmasságra talál. Ez elsőre ideálisnak tűnhet.

A gyakorlat azonban azt mutatja, hogy pontosan ebben az arányban rejlik sok projekt sikertelenségének magja. Rövidtávon a figyelve a projektek megpróbálják a legkézenfekvőbbel a helyes dolgot tenni:

  • A pontatlan követelményeket gyakran olyan termékek kompenzálják, melyek megkísérlik a pontatlanságot kompenzálni. A szoftver immaterialitását arra használják, hogy olyan szélesen és rugalmasan implementáljanak, hogy a még ismeretlen vagy elmosódó követelményeket már quasi előresiető engedelmességel kielégítsék.
  • A folyamatosan változó követelmények a termékben a lehető leggyorsabban bevezetésre kerülnek, mert ez az immaterialitásának köszönhetően lehetséges.

Hosszútávon azonban az ilyen viselkedés kontraproduktív:

  • Az előresiető engedelmesség szélességhez és rugalmassághoz vezet, amelyekre nincsen valóban szükség. Olyan feature-öket valósít meg, amelyeket később nem használnak.
  • A szoftver gyors átépítése a változós követelményeknek megfelelően a kód minőségének eróziójához vezet. A szoftver ugyan immateriális és rugalmas – de nem minden szoftverstruktúra továbbfejleszthető vagy csak érthető.

Zavaros és változó követelményszituációk az szoftver alapvető rugalmasságának háttere előtt gyorsan vezetnek felesleges többletköltségekhez és merev kódhoz. Sok olyan projekt, amelyik messze túllépte a költségeit, és még több olyan projekt, amely pár év elteltével karbantarthatatlanná vált, bizonyítja ezt.

A Clean Code fejlesztőknek, mint professzionális szoftverfejlesztőknek nap mint nap ellen kell állniuk az ilyen fejleményeknek. A szoftver tagadhatatlan természete miatt – immateriális és az is marad – ezt a követelményekkel való bánásmódnál lehet megfogni. Ez a YAGNI szabály forrása.

A YAGNI szabály olyan, mint egy éles kés: Aki használja, az egy problémát a közvetlenül szükséges elemek kicsi kockáira vág. A YAGNI szabály szerint csak a kétségtelenül és közvetlenül hasznot hozó dolgok kerülnek implementálásra. Minden egyéb… azt majd később. Ilyen tekintetben a YAGNI kéz a kézben jár a Lean Software Development„dönts a lehető legkésőbb” szabályával.

A YAGNI szabály a szoftverfejlesztés minden síkján és minden fázisában releváns. Bármikor, amikor azt kérdezzük magunktól, hogy „Szükség van-e erre a ráfordításra?” vagy „Szükségünk van erre valóban?” – és jöjjön ez akár egészen elnyomva agyunk leghátsó zugaiból – akkor ez a YAGNI szabály egy használati esete. A következőket mondja: Ha kétségeid vannak, akkor dönts a ráfordítás ellen.

Könnyűnek tűnhet, de nehéz. Innen adódnak a gyakori megszegései. Sok erő létezik, mely a ráfordítás elleni döntésnek ellentmond. „Nem is olyan nagy ráfordítás” vagy „Ha most nem gondolkodunk előre, akkor a jövőben nem fogunk tudni változtatni” ez csak két kézenfenkő magyarázat a ráfordítás megindoklására, akkor is, ha kétségek merülnek fel a hasznosságát illetően. Ez a architektúradöntéseket is érinti (pl. El kellene-e már most kezdeni a megosztott architektúrát, akkor is, ha a mai terhelésnél ez még nem szükséges?) és a lokális döntéseket is (pl. Kell-e már most optimalizálni az algoritmust, akkor is, ha pillanatnyilag nem okoz architektúraproblémákat?).

A megrendelő csak a közvetlen haszonért fizet. Az amit ma nem tud világosan specifikálni, az nem használ neki. Ezt az implementálásnál előre látni akarni, ráfordítást fektet be, haszon generálása nélkül. Amikor a megrendelő később pontosabban tudja, hogy mit is akar, akkor – és nem korábban! – eljött az ideje annak, hogy kielégítsük az akaratát. Amikor azonban egy projekt soron kívűl próbálja ezt az akaratot kielégíteni, akkor azt kockáztatja, hogy a megrendelő holnapi akaratával ennek ellent fog mondani. Egy olyan feature – funkcionális vagy nem funkcionális – amit ma világos követelmény nélkül implementálnak, az a megrendelőt holnap talán már nem is érdekli. Vagy talán már nem lesz neki annyira fontos, mint egy másik feature.

Ez a szoftverfejlesztés számára a következőket jelenti:

  • Kizárólag a világos követelményeket szabad implementálni.
  • A megrendelő priorizálja a világos követelményeit.
  • A világos követelményeket kell a prioritásuk sorrendjében megvalósítani.
  • A fejlesztési folyamatot és a kód struktúráját nagyban és kicsiben úgy kell kialakítani, hogy ne ébredjen félelem a változó és új követelmények megvalósítása iránt.

A Clean Code fejlesztők, mint professzionális fejlesztők a megrendelő felé ezt az eljárást félreérthetetlenül kommunikálják. Ezáltal:

  • szolgáltatásszelleművé válunk, hiszen nem kell elutasítanunk a megrendelő egyetlen világos követelményét sem
  • felelősségteljessé válunk, mert a büdzsét csak a világosan megfogalmazott haszon érdekében használjuk
  • védelmezővé válunk a kóddal szemben, mert megóvjuk a végeredményben szükségtelen dolgokkal történő túlpakolástól.

A YAGNI ezért nem csak egy olyan szabály, amit minden fejlesztőnek be kellene tartania, hanem projekteknek és teameknek a szabálya, tehát szervezeti szinteken. A YAGNI ugyanúgy mindig használandó, mint a DRY. Kétség esetén, ha lehetséges halaszd el a döntést. Különben dönts a ráfordítás ellen. Ez feszültségtől mentesít, salaktalanít és gyors sikerhez vezet.

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.

Open Closed Principle

Miért?
Azért, mert új feature-ök hozzáadásával egy addig hibátlan rendszer instabillá válásának kockázatát a lehető legalacsonyabban kell tartani.

Az Open Closed Principle (OCP) azt mondja ki, hogy egy osztálynak nyitottnak kell lennie a bővítésre, de el kell zárkóznia a változtatások elől. Ez egy további SOLID-elv. A következő kód mutatja be, hogy milyen problémákhoz vezethet ennek az elvnek a be nem tartása:
public double Ár() {
    const decimal TörzsvásárlóiKedvezmény = 0.95m;
    switch(vásárlóTípus) {
        case VásárlóTípus.Egyszeri:
            return mennyiség * darabár;
        case VásárlóTípus.TörzsVásárló:
            return mennyiség * darabÁr * TörzsvásárlóiKedvezmény;
        default:
            throw new ArgumentOutOfRangeException();
    }
}

Ebben az implementációban az problematikus, hogy az osztályt meg kell változtatni, ha az árszámítás egy további módja kerül hozzá. Ennél nagy a veszélye annak, hogy hibát vétünk, és a már létező funkcionalitások nem működnek megfelelően. Ha vannak automatizált unit tesztek és integrációs tesztek is, még akkor is fennál annak a veszélye, hogy új bug-okat hagyunk hátra, mert nem lehet elérni százszázalékos tesztlefedettséget. Tehát általánosságban egy olyan eljárást keresünk, amely az osztályt bővíthetővé teszi anélkül, hogy az osztályt magát meg kellene változtatni. Ezt például a Strategy Pattern segítségével lehet elérni:

public interface IÁrSzámító {
    double Ár(int mennyiség, double darabÁr);
}

private IÁrSzámító árSzámító;

public double Ár() {
    return árSzámító.Ár(mennyiség, darabÁr);
}

public class EgyszeriVevő : IÁrSzámító {
    public double Ár(int mennyiség, double darabÁr) {
        return mennyiség * darabár;
    }
}

public class TörzsVásárló : IÁrSzámító {
    const decimal TörzsvásárlóiKedvezmény = 0.95m;
    
    public double Ár(int mennyiség, double darabÁr) {
        return mennyiség * darabÁr * TörzsvásárlóiKedvezmény;
    }
}

Az ár konkrét kiszámítását egy interfészen keresztül más osztályokba helyeztük. Ezáltal lehetségessé válik a tetszés szerinti bővítés az interfész új implementációival. Így az osztály nyitottá vált a bővítésekkel szemben, és egyidejűleg zárttá a változtatásokkal szemben. Létező kódot pl. a Replace Conditional with Strategy refaktorálással lehet úgy átépíteni, hogy a Open Closed Principle-t betartsuk.

Tell, don´t ask

Miért?
Az erős összetartás (cohesion) és a laza csatolás (loose coupling) erények. Osztályok nyilvános állapotrészletei ellentmondanak ennek.

Egy kicsit provokatívan megfogalmazva: az osztályoknak ne legyen property getter-e. Ezek egy osztály felhasználóit arra csábítják, hogy egy objektum értékei alapján döntéseket hozzanak. Ahelyett tehát, hogy az objektumnak megmondanánk, hogy mit tegyen, kikérdezzük, és kívülről az objektum belső állapotára vonatkozó megállapításokat hozunk.Az objektumorientált programozás egyik alapelve az Information Hiding (lásd még a sárga fokozaton). Az osztályoknak nem volna szabad olyan információkat mutatni magukról, amiből kiderül, hogy hogyan implementálták őket. Amennyiben egy osztálynak a munkájához szüksége van egy belső állapotra, akkor ezt általában egy belső mezőben tároljuk. Ha ez az érték kifelé is látható, akkor az osztály felhasználóit arra csábítjuk, hogy az objektumnak ezt a tulajdonképpen belső állapotát saját döntéseikre használják. Ezáltal az osztályt gyorsan lefokozzuk a tiszta adattárolásra. Mindenképpen előnyben kell részesíteni egy olyan implementálást, melyben az objektummal közüljük, hogy mit tegyen. Ezáltal az osztály felhasználóját már nem kell érdekelje az, hogy az osztály hogyan fogja a feladatot belül elintézni.

A Tell don’t ask-elv eredményeként viselkedéssel rendelkező objektumok jönnek „buta” adattároló objektumok helyett. Az objektumok összjátéka lazán csatolt, mivel az objektumoknak nem kell feltételezésekbe bocsátkozniuk az együttműködő objektumokról. De nem csak ennyi! Ha az objektumok nem hozzák nyilvánosságra az állapotukat, akkor megtartják a döntéshozatali fennhatóságukat. Ezzel erősödik a döntő kód összetartása (cohesion), mert egy helyre koncentrálódik.

Law of Demeter

Miért?
Objektumok függőségei egy szolgáltatási lánc több szemén keresztül csúnya szoros csatoláshoz (coupling) vezetnek.

A Law of Demeter-nél az a cél, hogy az objektumok összjátékát egészséges mértékre korlátozzuk. Ezt egyszerűsítve a következőképpen lehet megfogalmazni: “Don’t talk to strangers”. A Law of Demeter szerint egy eljárásnak csak a következő eljárásokat szabadna használnia:
  • saját osztályának eljárásait
  • paramétereinek eljárásait
  • saját osztálya által használt osztályok eljárásait
  • saját maga által létrehozott objektumok eljárásait

Azonban: Figyelembe kell vennünk, hogy néha van értelme a tisztán adattároló osztályoknak. Ezekre természetesen nem kell alkalmazni a Law of Demeter-t. Éppenséggel lehet, hogy van értelme annak, hogy a konfigurációs adatokat több hierarchikus osztályra osszuk szét, úgy hogy a végén a következő hozzáférés adódik egy értékhez:

int margin = config.Pages.Margins.Left;

Ha itt a Law of Demeter-t akarnánk alkalmazni, akkor csak a config.Pages volna megengedett.