Kiekvienas, kas bent kelis metus dirbo su kodo baze, kuri gyva ilgiau nei pusmetį, žino tą jausmą – atidarai failą, kurį reikia pataisyti per penkias minutes, o po valandos vis dar bandai suprasti, kodėl funkcija vadinasi processData2, kodėl ji kviečia pati save per tris tarpines klases ir kodėl niekas nedrįsta jos liesti jau pusantrų metų. Tai ir yra techninė skola gyvai, be gražių apibrėžimų iš vadovėlių.
Terminą 1992 metais pasiūlė Wardas Cunninghamas, palygindamas greitą, bet nešvarų kodo rašymą su paskola banke – paimi dabar, grąžini vėliau su palūkanomis. Tik problema ta, kad kodo pasaulyje palūkanų normos niekas neparodo iš anksto, o grąžinimo terminas dažnai ateina tuo metu, kai visai nesitikite.
Kas iš tikrųjų yra ta techninė skola
Dažniausia klaida – techninę skolą tapatinti su blogu kodu. Iš tiesų tai platesnis reiškinys. Skola atsiranda kiekvieną kartą, kai komanda sąmoningai ar nesąmoningai pasirenka greitesnį, bet ne geriausią sprendimą, žinodama (arba nežinodama), kad ateityje reikės grįžti ir pataisyti.
Yra keletas skolos tipų, kuriuos verta skirti, nes kiekvienas reikalauja skirtingo požiūrio:
- Sąmoninga skola – komanda žino, kad daro greitą sprendimą, ir tai fiksuoja, pavyzdžiui, TODO komentaru arba tiketu backlog’e. Tai sveikiausia skolos forma, nes ji matoma.
- Nesąmoninga skola – atsiranda dėl žinių trūkumo. Programuotojas parašo kodą taip, kaip moka geriausiai tuo momentu, o po metų paaiškėja, kad architektūra netinka augimui.
- Bitinė (bit rot) skola – kodas buvo geras, kai buvo rašytas, bet aplinka pasikeitė: bibliotekos atsinaujino, verslo logika susikomplikavo, o kodas liko toks pat.
- Aplaidumo skola – tiesiog niekas nesirūpino kokybe. Nebuvo laiko, nebuvo motyvacijos, nebuvo standartų.
Martinas Fowleris šią klasifikaciją sudėliojo į matricą – tyčinė/netyčinė ir apgalvota/neapgalvota. Ir čia svarbiausia įžvalga: tyčinė ir apgalvota skola dažnai yra teisingas verslo sprendimas. Jei startuolis turi šešis mėnesius įrodyti, kad produktas apskritai reikalingas rinkai, idealiai švarus kodas gali reikšti bankrotą prieš pasiekiant tą įrodymą.
Kodėl kodas genda pats savaime
Įdomu tai, kad kodas, kurio niekas neliečia, taip pat pamažu praranda vertę. Ne todėl, kad jis fiziškai keičiasi, o todėl, kad keičiasi viskas aplink jį. Node.js versija, kurią naudojote 2019 metais, šiandien turi žinomų saugumo spragų. Biblioteka, kurią pasirinkote dėl populiarumo, šiandien nebepalaikoma. API, su kuriuo integravotės, pakeitė autentifikacijos schemą.
Tai reiškia, kad techninė skola nėra vienkartinis įvykis, o nuolatinis procesas, panašus į namo priežiūrą. Galite nedažyti sienų penkerius metus – namas nesugrius iš karto, bet vieną dieną atsiras pelėsis, ir tada remontas kainuos gerokai daugiau nei būtų kainavęs reguliarus priežiūros ciklas.
Praktikoje tai atrodo taip: komanda šešis mėnesius nekeičia priklausomybių versijų, nes „viskas veikia“. Kai galiausiai reikia atnaujinti dėl saugumo audito, paaiškėja, kad tarp senos ir naujos versijos yra dešimt breaking changes, ir atnaujinimas, kuris būtų užtrukęs valandą, jei būtų daromas reguliariai, dabar užtrunka savaitę.
Kodo kokybė – ne estetika, o rizikos valdymas
Dažnai kodo kokybė suvokiama kaip skonio reikalas – vieniems patinka trumpos funkcijos, kitiems ilgos, vieniems patinka daug komentarų, kitiems – savidokumentuojantis kodas. Bet realybėje kokybė matuojama vienu kriterijumi: kiek laiko ir kiek žmonių reikia, kad pakeistumėte sistemos elgesį nesukeldami naujų klaidų.
Yra keli konkretūs požymiai, kad kodo bazė artėja prie kritinio taško:
- Naujam žmogui komandoje reikia daugiau nei mėnesio, kad pradėtų savarankiškai dirbti su moduliu.
- Bet koks pakeitimas vienoje vietoje sukelia klaidas trijose kitose, nesusijusiose iš pirmo žvilgsnio.
- Testų aprėptis egzistuoja tik popieriuje – testai yra, bet niekas jais nepasitiki ir prieš deploy vis tiek testuoja rankiniu būdu.
- Funkcijų kodas turi daugiau nei penkis lygius įtraukimo (nested if/for), ir logika tampa neįskaitoma be debuggerio.
Šitie ženklai retai atsiranda staiga. Jie kaupiasi mėnesiais, ir dažniausiai niekas jų nepastebi, kol nauja funkcija, kuri turėjo užtrukti tris dienas, neužtrunka tris savaites.
Refaktoringas – ne prabanga, o einamoji priežiūra
Daugelis komandų refaktoringą traktuoja kaip atskirą projektą, kuriam reikia gauti vadovybės pritarimą, atskirą sprintą, atskirą biudžetą. Tai iš esmės klaidingas požiūris. Refaktoringas turėtų būti toks pat natūralus procesas kaip rankų plovimas prieš valgį – daromas nuolat, mažomis dozėmis, nereikalaujant atskiro leidimo.
Praktiškas principas, kurį verta įsivesti komandoje: kiekvieną kartą, kai liečiate failą dėl kokios nors užduoties, palikite jį truputėlį geresnį, nei radote. Angliškai tai vadinama „boy scout rule“ – palik stovyklavietę švaresnę, nei ją radai. Nereikia perrašyti viso modulio, užtenka pervadinti nesuprantamą kintamąjį arba ištraukti pasikartojantį kodą į atskirą funkciją.
Kita vertus, yra situacijų, kai smulkūs pataisymai nebepadeda – architektūra tiesiog nebeatitinka reikalavimų, ir reikia didesnio, planuoto refaktoringo. Čia svarbu neapsigauti: didelis „big bang“ perrašymas nuo nulio beveik visada rizikingesnis, nei atrodo. Joel Spolsky prieš daugiau nei dvidešimt metų parašė straipsnį, kodėl niekada nereikėtų perrašyti kodo nuo nulio – ir jo argumentai vis dar galioja. Senas, negražus kodas dažnai turi paslėptą logiką, kuri sprendžia realius pakraščio atvejus, apie kuriuos niekas nebeprisimena, kol jie vėl neiškyla produkcijoje po perrašymo.
Testai kaip apsauginis tinklas refaktoringui
Be automatinių testų refaktoringas tampa azartiniu žaidimu. Galite pakeisti funkciją, viskas atrodo tvarkinga, bet po savaitės paaiškėja, kad sulaužėte edge case, kurio niekas neaprašė reikalavimuose, bet klientas juo naudojosi kasdien.
Testų piramidė čia – ne akademinis konceptas, o labai praktiškas įrankis prioritetams nustatyti. Daug greitų vienetinių testų (unit tests), mažiau integracinių, dar mažiau end-to-end. Kai testų aprėptis yra pakankama, refaktoringas iš baisaus, rizikingo žingsnio virsta rutinine kasdiene veikla.
Svarbu paminėti ir priešingą kraštutinumą – testų rašymas dėl skaičiaus, ne dėl vertės. Aprėptis 100% neturi jokios prasmės, jei testai tikrina getterius ir setterius, bet nepaliečia sudėtingos verslo logikos. Geresnis klausimas nei „kiek procentų kodo padengta testais“ yra „ar galiu ramiai keisti šitą modulį, žinodamas, kad testai mane įspės, jei kažką sulaušiu“.
Code review kultūra – kur skola arba kaupiasi, arba sustabdoma
Code review procesas dažnai tampa arba formalumu, kurį visi kuo greičiau nori praeiti, arba realiu kokybės filtru. Skirtumas priklauso ne nuo įrankio, o nuo kultūros.
Keletas dalykų, kurie realiai veikia praktikoje:
- Review komentarai turėtų būti apie kodą, ne apie žmogų. „Ši funkcija galėtų būti aiškesnė, jei…“ vietoj „tu čia padarei chaosą“.
- Didelis pull request (daugiau nei 400 eilučių) praktiškai negali būti kokybiškai peržiūrėtas. Recenzentas po pirmų 200 eilučių tiesiog pradeda spaudyti „approve“ nesigilindamas.
- Automatizuokite tai, ką galima automatizuoti – formatavimą, linterius, tipų tikrinimą. Žmogaus laikas turėtų būti skiriamas logikai ir architektūrai, o ne tarpams ir kabliataškiams.
- Jei komanda turi „nepaliečiamą“ kodo autorių, kurio darbo niekas nedrįsta kritikuoti, tai jau ženklas, kad procesas sugedęs.
Įdomu, kad kai kurios komandos priima taisyklę – jei per review kyla ginčas, kuris trunka ilgiau nei dešimt minučių chate, geriau susiskambinti penkioms minutėms nei ginčytis tekstu valandą. Tai skamba banaliai, bet realiai sutaupo daug nervų ir laiko.
Kaip matuoti tai, kas atrodo nematuojama
Vadovybei sunku patikėti pinigus į kokybę, jei ji negali to pamatyti skaičiuose. Todėl verta turėti bent kelis metrikas, kurios padeda pagrįsti investiciją į techninės skolos mažinimą:
- Cycle time – kiek laiko užtrunka nuo užduoties pradžios iki jos production. Jei šis skaičius nuolat auga be aiškios priežasties, tai dažnai reiškia, kad skola trukdo darbui.
- Incidentų skaičius po deploy – kiek klaidų atsiranda dėl kiekvieno naujo išleidimo. Augimas rodo, kad testų aprėptis ar architektūra nebeatitinka poreikių.
- Cyclomatic complexity – automatizuotai skaičiuojamas kodo sudėtingumo rodiklis, kurį galima sekti per laiką atskiruose moduliuose.
- „Change failure rate“ iš DORA metrikų – kiek procentų pakeitimų sukelia problemą production aplinkoje.
Šie skaičiai patys savaime nieko neišsprendžia, bet suteikia bendrą kalbą tarp inžinierių ir vadovybės. Vietoj „kodas blogas, reikia refaktoringo“ galima pasakyti „per pastaruosius tris mėnesius incidentų skaičius išaugo 40%, o cycle time – dvigubai, ir šito modulio sudėtingumas viršija visus kitus tris kartus“.
Skolos grąžinimo strategija be dramos
Realistiškiausias požiūris – ne bandyti sumokėti visą skolą iš karto, o skirti tam nuolatinį, fiksuotą laiką. Daugelis komandų taiko principą – 20% sprinto laiko skiriama techninei skolai, refaktoringui, priklausomybių atnaujinimui. Tai nereikalauja atskiro pritarimo kiekvieną kartą ir tampa įprasta darbo dalimi, o ne kova su vadovybe kiekvieną sprintą.
Kita naudinga praktika – techninės skolos registras. Tiesiog atskiras backlog, kuriame fiksuojami žinomi skolos vienetai su trumpu aprašymu, kodėl jie atsirado, ir preliminaria kaina, jei jie nebus sutvarkyti. Tai leidžia priimti sprendimus faktais, o ne emocijomis, kai ateina metas planuoti kvartalą.
Vietoj išvadų – trumpas gidas tam, kas rytoj ateis į darbą
Jeigu iš viso šito reikėtų išsinešti tik kelis dalykus, štai jie. Pirma, techninė skola nėra gėda – tai normali kainos ir laiko kompromiso dalis kiekviename produkte, kuris kada nors turėjo terminą. Blogai ne tai, kad skola egzistuoja, o tai, kad ji nematoma ir niekas jos nefiksuoja.
Antra, geriausias momentas kalbėti apie kokybę – ne tada, kai viskas jau sugriuvo, o tada, kai dar galima parodyti skaičius: cycle time, incidentų skaičių, sudėtingumo metrikas. Vadovybė retai atsisako investuoti į kokybę, kai mato konkrečią kainą, kurią mokate kiekvieną sprintą.
Trečia, mažos, nuolatinės pastangos veikia geriau nei retos, didelės kampanijos. Pervadinti kintamąjį, ištraukti funkciją, papildyti testą – tai užtrunka penkias minutes, bet padaryta reguliariai per metus pakeičia visą kodo bazės kokybę labiau, nei vienas didelis „refaktoringo sprintas“, kurio visi bijo ir kurį visada nukelia.
Ir paskutinis dalykas – geras kodas nėra tas, kuris atrodo gražiausiai. Geras kodas yra tas, kurį naujas komandos narys gali pakeisti be baimės, kad kažkas sugrius tris moduliais toliau. Jei prie to artėjate, palūkanos, kurias mokate už savo techninę skolą, pamažu mažėja – ir tai vienintelis skolos tipas, kurį verta ramiai mokėti ilgą laiką.






