Volt egy projekt
Csatlakoztam egy frontend projekthez, amiről már az első perc előtt tudtam, hogy alacsony kódminőségű (a híre ugyanis megelőzte), és bár aktív fejlesztés alatt van, legacy érzésű a vele való munka. Az új feature-ök fejlesztéséhez a meglévő rossz/elavult mintákat használják, semmi indítattás nem mutatkozott a dolgok jobbá tételére. A tétlenség fluktuációt eredményezett a fejlesztők körében (az évek alatt 50+ ember dolgozott a kódon), a fluktuáció pedig ignoránssá tette őket, így egy önmagába roskadó spirált generálva.
A projektet egy olyan szoftvercég emberei rakták össze eredetileg, amelyik arról híres, hogy a minőséget inkább mennyiséggel próbálja kompenzálni, sikeretelenül. A megbízók azt hitték, hogy a unit test code coverage magas százelékú előírása minőségi kódot eredményezhet - természetesen tévedtek, a "tesztek" gyakorlatilag akadályai voltak a szoftver jobbá tételének később, és nem voltak többek szimpla függvényhívásoknál, valódi ellenőrzés (assertion), lényegében tesztelés nélkül.
Angular/TypeScript projekt, tehát lett volna lehetőség a kvázi-statikus típusosság kihasználásának, mégis a legtöbb helyen figyelmen kívül volt hagyva, remek táptalajt szolgáltatva az olyan hibáknak, amik csak a fejlesztői környezeten kívül fordulnak elő és közvetlenül a végfelhasználók szeme előtt jelentkeznek. Ha voltak is típusok definiálva, gyakori volt az azonos nevű és célú, viszont teljesen különböző helyen és különböző módon implementálva (mindenhol épp csak annyi, amennyi a típusellenőrzőt elcsitítja, semmi több).
A stabilitást további futásidejű (feature flag, autentikáció, cookie consent, stb) és fejlesztési idejű (nyelvi fordítások, ikonpakkok) külső függőségekkel veszélyeztetjük: 3rd party szolgáltatók, amik rendelkezésre állása nem kiszámítható, sem számonkérhető, de az is maximum vihar utáni kármentés, a bukás minden alkalommal a megbízó nevéhez fűződik.
Miután a projekt backendje is nagyon sok függőséggel rendelkezik, amik néhol egymásra is támaszkodnak, integrálódnak, a teljes képet megszerezni sok idő volt (még most sincs meg teljesen).
A gondolat, hogy ezzel valamit kezdeni kell, már azelőtt megszületett, hogy a projekthez csatlakoztam. "Ezt újra kellene írni" - gondoltam és nyitottam meg először a kódot. De elég ideje vagyok a szakmában, hogy tudjam, hogy egy ekkora méretű és ilyen szerepű projektet, ekkora mérnöki kitettséggel nem lehet csak úgy újraírni, ki kell találni rá egy stratégiát. Ezen a stratégián gondolkodtam a megismerés ideje alatt, majd elkezdtem összeszedni azokat az ötleteket, amik szerintem a jó sínre tehetik - életében először - a projektet.
A problémák
Az egyik megfigyelésem, ahogy fent említettem, a típusosság drasztikus hiánya. Hibák komplett kategóriáját ki lehet szűrni azzal, ha minden változónak rendes típust adunk meg. Ez tehát az egyik pont a tervben.
A kód struktúrája és formája teljesen inkonzisztens. Nincs két egyformán kinéző fájl (kivéve az a 2000 soros, amit kompletten átmásoltak, hogy csak egy függvénynevet változtassanak meg benne). A különbözőségek lassú és rossz olvashatóságot eredményeznek, komoly hatással vannak a bővíthetőségre és lehetőséget adnak hibák elkövetésére.
A projekt architektúrája abszolút esetleges. Többféle szerepkör (néha 6-8 is) egyetlen komponensbe került mindenféle racionális ok nélkül (például: a fejlécben volt a "harang" ikon, ezért a fejlécben volt implementálva a komplett értesítéskezelés a-tól z-ig).
A komponensek egymagukban komplett modulokat valósítottak meg, óriási mennyiségű üzleti logika, megjelenési komplexitás, state kezelés egyben. A unitok (amiket aztán tesztelni kellene) nem ritkán 2000 sor fölé nyúltak, ellehetetlenítve a hatékony unit tesztelést.
Szinte semmilyen technikai szabályozás nem volt érvényesítve, sem a TypeScript fordító, sem a linter részéről, ami borzasztó hanyag kódot és kódolást eredményezett.
A dolgot súlyosbította, hogy a közös kódbázison dolgozó frontend fejlesztők 4 különböző csapatba voltak szétosztva, nem voltak egységes irányelvek lefektetve, sem kikényszerítve, nem volt semmilyen technikai vezetés (ami korábban volt, az bár ne is lett volna). A közhely a közös lóról tökéletesen megállta a helyét, ráadásul állatorvosi is volt.
A code reviewnak semmilyen kultúrája nem volt. Amikor odakerültem, visszanéztem az útóbbi 3 hónap pull request-jeit és nem találtam összesen 20 kommentet. Nincs az a tökéletes projekt, amelyik 3 hónap alatt (mondjuk naponta 4 PR-ral ez kb. 240 PR ennyi idő alatt) csupán ennyi kommentet indukál, lehetetlen. Aztán az első általam review-zott PR-ra egyből raktam 15-öt, mert annyi sebből vérzett.
A projekt az összeszedetlensége okán teljesen indokolatlan 3rd party libekkel volt tele. Az Angular alapból biztosít számformázási lehetőségeket, de már a böngészőben is van erre natív támogatás. Ennek ellenére egy 7 éve karbantartatlan libet sikerült erre felhasználniuk. Azaz, már a projekt indulásának pillanatában 2 éve magára volt hagyva ez a lib, bármely józan fejlesztő azonnal nemet mondott volna az installálására. Nem is beszélve a biztonsági problémákról, amik ezekben a csomagokban rejtőzhetnek - a biztonsági audit stabilan 20 fölötti számot mutatott bármely pillanatban.
A hanyagság további problémákat szült: statikus fájlok voltak commitolva a repositoryba (képek, pdf-ek, videók, stb), amik miatt a 30 MB-os projekt git repója 2GB méretű volt.
A state management, ahogy fent említettem, a komponensekben történt, leginkább class property-k felhasználásával. Ez a megközelítés évek óta ellenjavallt a framework szerint, mert kézzel fogható teljesítményproblémákhoz vezet. Ha valahol mégis értelmes módon volt ez kezelve, ott annyi féle minta volt, ahány előfordulás - zéró konzisztencia ebben is.
A projekt belső függőségeinek térképe (import-gráf) egyetlen kusza pókháló volt, minden mindennel összedrótozva, ráadásul tele god-objectekkel.
A stílusozás (CSS) szintén átláthatatlan módon volt megoldva, esetleges volt reszponzív kinézet is, ami aztán rendszeresen visszaütött, amikor valamit módosítani kellett.
Egy csomó külső függőségnek nem voltak meg a típusfájlai vagy wrapper libjei - gyakorlatilag futásidőben töltődtek be és az alkalmazás teljesen rá volt utalva a nem várt változásokra és tippelgetésekre. Volt, amikor az éppen aktuális, minifikált javascript kódot kellett debugolni egy funkció miatt, mert nem volt dokumentálva sem a 3rd party által.
A hab a tortán a branching stratégia esetlegessége, követhető élesítési folyamat hiánya, dedikált webszerver helyett egy content management rendszer használata meglehetősen unortodox módon.
A dilemma
A projekt a jelenlegi állapotában egy szakmai zsákutca, egy fekete lyuk, amibe mérnökök értékes óráit és motívációját égetjük mindenféle fejlődés nélkül. Borítékolható a fluktuáció/elhanyagolás spirál további mélyítése. "Ne panaszkodj annyit, oldd meg!" - neki is láttam.
A megoldás
Elkötelezett voltam abban, hogy ezen változtatni kell. Összegezve a problémákat elkezdtem gondolkodni egy megoldási lépéssorozaton, amit, ha kell, akár egyedül is végig tudok vinni, de nyilván minél többen állunk neki, annál hamarabb tudunk végezni.
A kód formájának konzisztenssé tétele. Ez egy hosszú és unalmas folyamat, aminek része egy előre felállított szabályrendszer alkalmazása minden egyes fájlra, ami a projektben van. A szabályrendszer nagyon sok pillérből áll, kezdve a hivatalos Angular kódolási javaslatokkal, clean kód szabályokkal, linter és fordítási szabályok bevezetésével és egyedi, a projektre szabott szempontokkal.
Típusok bevezetése mindenhova. A dolog elég trükkös, mert megköveteli a kód részleges megértését és némi kísérletezést, utánajárást, tesztelést.
A kód könnyed átstruktúrálása (fölösleges kódok kidobása, egybe tartozó dolgok egymás mellé rendezése a későbbi mozgatást előkészítendő).
Kitakarítani a fölöslegesen behúzott libeket, helyettesíteni azokat, amiknek semmi keresnivalója a projektben vagy elavultak/megbízhatatlanok (lodash, jquery, stb.).
Teljesen megérteni a nagyobb komponensek szerepét, szétdarabolni őket a felelősségi körök alapján. A feldarabolás során átalakítani a belső működésmódjukat úgy, hogy a legkevesebb state managementre legyen szükség. Azaz a class propertyk állítgatása helyett pure function-ök alkalmazása (csak input és output, semmi side-effect). Ezzel lehetővé válik a unit tesztelés, a performanciaoptimalizálás és további komponens-átszervezés.
Megszűntetni a belső state-eket a komponensekben, bekapcsolni az OnPush CDS-t. Szintén megszűntetni a stage management-et a service-ekben, azok helyett globális state managert használni (NGRX), ahol erre mégis szükség van.
Átstruktúrálni a kódot úgy, hogy világosan meg lehessen különböztetni a prezentációs komponenseket az üzletiektől - ezzel lehetővé válik a kiszervezés és akár egy-egy ilyen modul egy komplett csapathoz rendelése: sokkal világosabb felelősségi körök és számonkérhetőség, gyorsabb hibajavítás, stb.
Az eredmény
Mivel nem voltam kinevezett lead ezen a projekten, a hatásgyakorlásnak egyéb módjait kellett alkalmaznom. Volt például céges Wiki, amibe felvezettem a komplett tervet, kidolgozva az első néhány nagyobb mérföldkövet. Ezt aztán felhasználtam, amikor a code review során olyan módosításokat láttam, amik nem követték ezeket a sorvezetőket. Természetesen a dokumentációk mindenki számára hozzáférhetőek, szerkeszthetőek és vitathatóak voltak, erre mindenkinek előre fel is hívtam a figyelmét.
Elkezdtem RFC-ket gyártani (Request for Comments) azokról a dolgokról, amik újdonságot jelenthetnek a projekt életében vagy egy régi gyakorlatot challenge-elnek meg. Például keressünk glyph-font alternatívát (mivel a meglévő ingyenes szolgáltatás a beetetés után fizetőssé vált), vagy döntsük el, hogy egy új funkcióra milyen külső libet akarunk használni (milyen feltételek mentén vizsgáljuk meg, hogyan döntjük el, stb.), és további ~10 másik. Az RFC-k lényege az volt, hogy minél többen kritizáljuk meg az ott írtakat egy jussunk egy olyan konszenzusra, amit a legtöbbünk helyesnek gondol.
Az egészet prezentáltam több körben az üzleti döntéshozóknak, időbecsléssel, hatásokkal, várható eredményekkel. A tartalmat úgy formáltam, hogy érhető legyen annak is, aki nem annyira technikai, mint amit a projekt jellege megkíván. Bár minden felmerülő kérdést sikerült teljesen tisztázni, a cégnek nem volt profilja a szoftverfejlesztés, így egy szponzor sem mert mögé állni (annak ellenére, hogy a terv arról szólt, hogy a napi munkával párhuzamosan folynának a refaktorálási tevékenységek).
Nem vagyok az a fajta, aki könnyen beletörődik a ignoranciába, így szponzor nélkül is, értelemszerűen nagyon lassú tempóban, de elkezdtem a napi munka részeként átrágni magamat a problémás részeken. Amikor a cost-cut elérte a szerződésünket, úgy 15%-át sikerült a kódnak úgy rendbetenni, hogy arra a későbbiekben is építeni lehessen.
Rendbe tettem és optimalizáltam a CI/CD pipeline-t is, hogy ne 50 percig fusson egy deploy, csak 3-ig. Ez a fejlesztőknek is sokkal gyorsabb visszajelzést adott a módosításaikkal kapcsolatban (a projekt jellege miatt nem lehetett mindent a saját gépen ellenőrizni). Dokumentáltam az egész fejlesztési és élesítési folyamatot is, ami addig szájról-szájra módszerrel, minden alkalommal torzulva jutott egyik fejlesztőtől a másikig.
Gyakran csináltam live-coding eseményeket, amikor megmutattam a kollégáknak, hogy hogyan is gondolom mindazt, amit leírtam, illetve demonstráltam is. Tartottam külön session-öket, ha bármi egyéb témában merült fel kérdés (pl. hatékony unit-tesztelés; az SVG lehetőségei és korlátai; projekt specifikus 3rd party toolok használata; stb.).
Mit nyertem ezzel?
Szigorúan tech skillek szempontból az ég világon semmit, 100% kidobott idő volt. A projekt végig megőrizte a legacy jellegét, egy igazi soul grinder volt az utolsó percig (nem csak technikai-, de management szempontból is).
Amit a projekt hasznára írok az az, hogy elkezdtem jobban dokumentálni, hatékonyabban és érthetőbben beszélgetni nem-technikai emberekkel. Részt vettem folyamatoptimalizációs tevékenységekben, közösséget építettem, előadtam. Sokkal nagyobb méretekben gondoltam át a szoftver dimenzióit, mint az eddigi projektjeim során. Megterveztem egy al-projektet, aminek a célja az volt, hogy egy elhanyagolt, legacy frontendet modernizáljunk és készítsünk fel arra, hogy hosszú távon, a fluktuációt letörve, karriert építve tudjon rajta dolgozni az összes fejlesztő.