Örökös transzformáció

Annyi szépet és jót hallani a folyamatos szállításról, integrációról, telepítésről (continuous delivery, integration, deployment), no meg az automata tesztekről, amiktől "egyből jobb minőségű lesz a kód", hogy szinte mindenki elkezdte bevezetni ezeket a fejlesztési módszertanába. Ebben a rövid írásban felvázolom, hogy milyen buktatókkal kell megküzdened, ha belevágsz és egy vázlatos tervet, egy itinert adok át, ami alapján nekiállhatsz a megvalósításnak, vagy folytathatod azt, ha egy ponton elakadtál.

"Ha fáj, csináld gyakrabban" - mondta Martin Fowler, ami nem az önsanyargatás egyik tételmondata, hanem arra utal, hogy minél hamarabb veszünk észre egy hibát, vagy problémát, annál olcsóbb azt javítani. Ráadásul ez az összefüggés nem lineárisan, hanem exponenciálisan növekvő költséget jelent.

Ez az, amit sokszor nem veszünk észre a hangzatos jelmondatok között, és gyakran esünk abba a hibába, hogy egy adott módszer támogatására kidolgozott eszközt kezdünk el úgy használni, hogy a módszert magát nem vesszük át, vagy annak csak bizonyos részeit alkalmazzuk.

Elsőre könnyűnek tűnik feltelepíteni egy Jenkins-t, vagy létrehozni a repoban egy .gitlab-ci.yml-t, de nem mindig látjuk előre, hogy milyen mély is annak a bizonyos nyúlnak az ürege és mennyi munkát rántunk ezzel magunkra.

Nem mindegy, hogy egy legacy alkalmazáson, vagy egy zöldmezős beruházáson dolgozunk és ott kell ezeket a módszereket és eszközöket bevezetni. Egy legacy alkalmazásba nagyságrendileg tízszer nagyobb komplexitás bevezetni egy normálisan működő folyamatos szállítást és integrációt, mintha teljesen üres lappal indítunk el egy projektet.

Fontosnak tartom megjegyezni, hogy itt a legacy alatt olyan kódot értek, ami nincs automata tesztekkel lefedve, és úgy egyébként rendben van maga a kód.

Szóval, ha neked egy évek alatt összetákolt, borzalmat kell átvenni és automata tesztekkel lefedni, akkor az a tízes szorzó nem tízes lesz, hanem valami jóval nagyobb szám ami a végtelenbe tart inkább. Nehéz ugyanis automata tesztekkel lefedni egy alkalmazást, ha nincs ember, aki tisztába lenne a szoftver elvárt működésével vagy a különböző szerepkörökkel és a szerepkörök által igényelt felhasználói esetekkel. Akkor ugyanis ezeket neked kell majd pluszban előállítanod. Ha nincs szerencséd, akkor nem egységes az a folyamat sem aminek a támogatására az adott szoftvert használják, ilyenkor még talán a vállalati kultúra kialakításával is neked kell majd foglalkozni.

Az sem mindegy, hogy a cég ahol dolgozol milyen szoftverek fejlesztésére szakosodott. Ezek a modern eszközök használata ugyanis elsősorban a csapatban (legalább két fő) fejlesztett nagyobb, legalább két három hónapos projekt felett éri meg igazán. A járulékos költségük ugyanis a projekt méretétől függetlenül egy fix szám, amely egy bizonyos projektméret alatt a projekt költségével összemérhető költséget jelenthet.

Összefoglalva, ha kazalban nyomjátok ki a wordpress oldalakat úgy, hogy egy fejlesztő dolgozik napi 2-3 oldalon egyszerre párhuzamosan, akkor még az is előfordulhat, hogy egy "jól összerakott" CI bevezetésével nem csak lassabbak, hanem drágábbak is lesztek majd.

Természetesen nem arra szeretnélek buzdítani, hogy ne használd ezeket a modern eszközöket és módszereket a fejlesztés során, hanem arra szeretném felhívni a figyelmedet, hogy ez nem egy egyszerű, magától értetődő dolog. Többször láttam már olyat, hogy a fejlesztőknek a "Na gyere cipó bekaplak, hammm!" nekibuzdulás után a torkukon akad a falat, amit se kiköpni, se lenyelni nem tudnak már. Egy-egy tízperces fix aztán akár napokig is eltart, mert "a CI szerverrel már megint probléma volt"

Meg kell próbálnunk egy-egy kisebb területre fókuszálni, és apróbb lépésekben haladni előre. Ezt ugyan könnyű mondani, de nagyon nehéz kitalálni azt, hogy melyek legyenek ezek a lépések, és mi legyen az első.

Én azt javaslom, hogy első lépésben koncentráljuk a CI létrehozására, és job-ként valami egyszerű és atom stabilan működő dolgot válasszunk. Ilyen pl. a szintaxis ellenőrzés, ami a PHP parancssoros értelmezőjének a -l kapcsolója, vagy a Node.js parancssoros értelmezőjének -c kapcsolója. Ez elsőre nem soknak tűnik majd, de itt most nem ez a lényeg, hanem a CI folyamat megértése és felépítése.

Ezt aztán bővíthetjük valamilyen kódolási szabály ellenőrzővel (PHPCS, PHPStan, Eslint), majd valamilyen komolyabb statikus kód analizáló eszközzel. Figyeljünk arra, hogy ezeknek a kimenete egy örökölt kód esetén ne törje a buildet. Itt célként tűzhetjük ki azt, hogy a kódban található hibák számát csak csökkentheti egy-egy újabb módosítás, de semmi esetre sem növelheti.

Tapasztalatom szerint öt PHPMD hibából egy komoly biztonsági vagy logikai hibára mutat rá. Ezért mindenképpen javaslom ezek áttekintését, de óvva intek mindenkit attól, hogy mindenfajta rangsorolás nélkül nekiálljon a hibák teljes eltűntetésének. Inkább célként tűzzük ki azt és folyamatosan lassan haladjunk a cél felé. Nem baj, hogy egy fél-egy év múlva tudunk csak ünnepelni elérve egy ilyen nagy mérföldkövet.

Ne felejtsük el azt sem, hogy megfelelő metrikákkal és kimutatásokkal könnyebben el tudjuk adni a menedzsmentnek (és aztán ők az ügyfélnek) a refaktort. Meg lehet velük értetni, hogy a nagy piros kör az rossz, a sok ki zöld pedig jó. Vagy egy Sonar által fejlesztői napban kiírt technikai adósság is plasztikusan láttatni tudja ezt az egyébként elég nehezen megfogható fogalmat. Ezzel eszközt tudunk adni a menedzsment felé, hogy figyelje és nyomon kövesse a változást.

Ezek után érdemes automata teszteknek nekilátunk, de első körben én azt javaslom, hogy egységteszteket (unit test) írjunk minden egyes újonnan fejlesztett kódhoz. Azért nem javaslom a már meglévő kódhoz az egységteszt írását, mert egységtesztelni csak olyan kódot lehet, amit abban a tudatban írtak meg, hogy azt majd egységtesztelni fogják. A legacy kód pedig per definit nem ilyen. Láttam olyan egyedileg fejlesztett kis drupal modult, ami pusztán öt sorból állt, de ez az öt sor öt olyan függőséget tartalmazott, melyek elfedése (mock) igen nagy belefektetett munkát és gyakorlatot kívánt volna. Ezért sem javaslom azt, hogy ezzel kezdjünk.

Egyből adódik a gondolat, hogy ha nem olyan az a kód, akkor refaktoráljuk, írjuk át, hogy olyan legyen. Ezzel csak az a probléma, hogy addig nem lehet refaktorálni, amíg nincs valami vagy valaki aki meg tudja nekünk mondani, hogy az átalakított kód, még mindig ugyanúgy működik-e.

Erre is van megoldásunk: End-to-End tesztet kell írni. Ezzel azonban az a probléma - amellett, hogy nagyon sok erőforrást fog felemészteni -, hogy kell hozzá egy működő alkalmazás egy megfelelően zárt környezetben. Nem szeretnénk ugyanis, hogy a vásárlóink tömeges teszt leveleket kapjanak például, és az sem lenne praktikus, ha az analitikai szoftverünk a teszt adatokat összekeverje az élessel, vagy éles számlákat állítana ki a webáruházunk, miközben a tesztek futnak. De nem csak zártnak kell lennie a környezetnek, hanem minden teszt futása előtt egy jól meghatározott alapállapotba kell állnia, a külső függőségekkel együtt, valamint a szükséges teszt adatokat is fel kell tudnunk vinni majd. Arról, hogy mi legyen az érzékeny (API kulcsok, stb.) és személyes adatokkal, most ne beszéljünk. Mindezt automatikusan kell majd megtennie a continuous integration szervernek, mindenfajta humán beavatkozás nélkül.

Ha megnézed, akkor az end-to-end tesztek futtatásához szükséges környezet nagyon hasonlít a fejlesztői környezetre, amit a fejlesztők használnak. Ezért én azt javaslom, hogy end-to-end tesztek írása előtt láss neki egy olyan fejlesztői környezet kidolgozásának, amit bármely fejlesztő maximum egy órán belül felállít és már tud is benne fejleszteni.

Ebbe azért érdemes energiát fektetni, mert egyrész úgy is szükséged lesz rá, másrészt ennek a fejlesztői és egyben teszt környezetnek a kialakítása viszonylag nagy munka lesz sok kísérletezgetéssel, beállítással amit így meg tudsz részben spórolni, hisz két legyet ütsz le így egy csapásra.

Az egész lelke majd az a README.md fájl lesz, amiben leírod, hogy hogyan lehet felállítani, újraindítani, alaphelyzetbe állítani az alkalmazást, valamint az egyéb fejlesztéshez kapcsolódó teendők is itt kapnak majd helyet. Ezek a leírások ne terjengős, minden lehetőségre kitérő rejtvények legyenek, hanem egyszerű, a parancssorba másolható parancsok gyűjteményéből kell, hogy álljanak. Egyrészt a fejlesztőknek is így lesz gyors, hisz nem kell időt tölteni a megértéssel, kiválasztással, és a különböző zsákutcák bejárásával. Másrészt ezeket a parancsokat fogjuk bemásolni a CI szerver job definícióiba is.

Mivel nagyon nehéz, ha nem lehetetlen olyan telepítő szkriptet írni, ami minden operációs rendszeren, annak bármilyen telepítésénél megfelelően működik, azért én azt javaslom, hogy ezt mindenképpen valamilyen konténerizált megoldással oldd meg. Jelenleg erre a Docker egy kitűnő választás tud lenni.

Ha szeretnéd ezt a folyamatot felgyorsítani, akkor Neked ajánlom a Konténerizált fejlesztői környezet kialakítása című workshopomat.