sobota 17. května 2014

Refaktoring, část II.: Technický dluh

Aneb kapitola (nejen) pro manažery, ekonomy, zkrátka byrokracii, která rozhoduje o investicích, financování, směrování projektů - a taky o tom, kdy se projekt uzavře jako "hotový".
Původně jsem chtěl jít rovnou na zdrojáky, ale událo se něco, co mě přimělo vložit ještě jednu kapitolu. Zjistil jsem totiž, že u nás se o technickém dluhu až zase tak moc nemluví. Nicméně programátorší "guru" o této metafoře mluví už docela dlouho:

Co je to?

Manažeři i zákazníci milují vodopádový model: objednávka, zadání, analýza, zhotovení, akceptace, zaplacení. Nic složitého to přece není, vypadá to triviálně a jednoznačně. Ti zkušenější už ví, že každá ta fáze skýtá mnohá nebezpečí a pasti. Obecně nejednoznačnost a nedotažení každé té fáze - příčinou je obvykle neznalost přesných požadavků a neznalost způsobu výroby na druhé straně.

Zjednodušeně řečeno, technický dluh vzniká vždy, když kdokoliv na projektu odloží něco, o čem dobře ví, že je třeba udělat, ale odloží to - ať už se to týká analýzy, testů, dokumentace, vyčištění kódu.

Jak moc to vadí?

To je různé - asi jako inflace, státní dluh, vaše dluhy; proto se tomu říká technický dluh. Jsou to nedodělky, ale ne jen ty, které vidíte při předvádění aplikace. Ty skryté jsou daleko nebezpečnější. Proč? Nedodělky, které vidí uživatel, viděli všichni během vývoje projektu, a došlo k nějakému konsenzu, že jde o kompromis, se kterým uživatel dokáže žít.
Nedodělky, které ale vidí jen programátoři (pokud je vůbec někdo vidí), mají vlastnost právě té inflace - je to exponenciální funkce. S každou další iterací se umocňuje vliv dluhu, veškerá jeho negativa. Dlouho to nemusí vadit, ale když nad technickým dluhem ztratí vývojový tým kontrolu, už není cesty zpět a vývoj projektu skončí s potupnou ztrátou a obviňováním všech, kteří se na něm podíleli, navzájem.


Exponenciální funkce

Je jasné, že udržet projekt bez dluhů je prakticky nemožné. Vždycky se dá všechno udělat lépe. Na druhou stranu, když dluhy nesplácíte, špatně skončíte. Z vlastní praxe bych to rozdělil na takové tři kategorie (v horším případě fáze) ...

Dluh pod kontrolou

Dobrý stav, který znamená, že projekt má budoucnost a přestože obsahuje pár chyb (někdy i hodně), má smysl v něm pokračovat. Příznaky jsou následující:
  • vývojáři dávají celkem rozumné odhady pracnosti
  • nikdo není nervózní, panují dobré vztahy
  • vývojáři se těší na další úkol
  • většinou se stíhají termíny

Zadlužení

To už je horší stav, ale není nezvladatelný. Nesmí se podcenit - i za cenu oddálení termínu předání další verze je nutné dluh udržet nebo ideálně snížit. S každým dalším nárůstem se situace zhoršuje. Příznaky tohoto stavu jsou takové:
  • vývojáři pracují přesčas, často neplaceně a dobrovolně
  • zpravidla se nestíhají termíny, předání verze se oddaluje i opakovaně
  • často se mění analýza během vývoje
  • množí se požadavky na "až"
  • horší se přesnost odhadů pracnosti - obvykle se podstřelují v toužebné snaze všech stihnout termín
  • tendence přidávat lidi do zpožděného projektu
Management nechápe, proč se dříve termíny stíhaly a teď ne, má tendenci přitlačit, motivovat, ale prakticky dosahuje jen jediného - zvýšení tlaku a stresu, což často končí odchodem zaměstnanců, zpravidla těch nejlepších v první řadě, těch nejhorších potom v řadě druhé. Zůstávají jen bojovníci - pokud se dokážou vzepřít veškeré nepřízni, má projekt ještě naději.

Exekuce se blíží

V tuto chvíli se podívejte opět na ten graf exponenciální funkce. V určitém bodě se dostanete přes hranici, kdy vývoj dalších verzí projektu stojí ohromné zdroje a úsilí a jste ve stavu, kdy je extrémně těžké s tím něco začít dělat.
  • vývojáři často mění své odhady, klidně o dva řády - z hodiny je týden, z týdne 20 minut.
  • jakýkoli termín vyvolává šílený smích vývojářů
  • panuje nervozita a dochází k hádkám a práskání dveřmi
  • manažeři zakazují jakoukoliv údržbu, dovolené, a snaží se do projektu dostat nové lidi - a to jakékoliv
  • neprovádí se analýza, nebo jen povrchně
  • nehledí se na žádná kvalitativní měřítka
Je téměř vyloučeno, abyste se dostali z této fáze zpět. Pokud chcete v budoucích projektech uspět, uvědomte si, co jste zanedbali dříve, podcenili. Není to o tom, že jste měli požadovat vyšší cenu nebo sehnat více lidí.  Vždy potřebujete čas a vždy potřebujete nějakou stabilní kvalitu. Na tom, co děláte dnes, budete stavět zítra.

Udržování dluhu pod kontrolou

Všechno je vlastně docela snadné a pro řadu souvisejících problémů dokonce existují nástroje.

Odhadování pracnosti

To je problematika, na které často stojí váš úspěch - odhadnout, kolik času budete potřebovat na zhotovení něčeho, o čem ještě nemáte "ani páru", je trochu neřešitelný úkol. Existuje na něj řada strategií a doporučení, ale vždy ke kvalifikovanému odhadu potřebujete přehled. Odhad navíc nemůže být definitivní - je to jen odhad, že ...

P: "Devět žen neporodí ani jedno dítě za jeden měsíc. Chápeš?"
M: "Jojo, tohle ví každej, to znám ... Ale Ty jsi chlap!"
P: "Máš pocit, že devět chlapů nějaké dítě porodí?" 

Jak se projevuje technický dluh na odhadu? Představte si, že máte nějakou knihovnu, kterou lehce zanedbáváte - používáte jí ale v aplikaci bez problémů. Přijde ale nový úkol pro aplikaci, při kterém ale zjistíte, že v knihovně je chyba. Také zjistíte, že chybu jste v jiných aplikacích, které na ní už narazily, obešli. Jenže tuto obezličku v nové aplikaci uplatnit nemůžete, protože je v rozporu se zadáním - a navíc jste tehdy nepsali ani testy, takže ani nevíte, co všechno se opravou naopak rozbije.
A tak vám nezbude, než chybu opravit, čímž ale možná rozbijete již hotové aplikace s obezličkou. Tudíž pak budete muset i dopsat testy a opravit i tyto aplikace.

A teď se krátce zamyslete - jaký asi byl původní odhad? Kolikrát ho během opravy změníte? A jaká byla výsledná pracnost? Tím to ale nekončí - opravené aplikace bude možná třeba také distribuovat, takže nám vzniká další pracnost.
Ufff, tohle bolelo. A ještě bude, protože všem musíte vysvětlovat, co se vlastně stalo a proč - a čas běží dál a náklady rostou.

Psaní automatických testů

Automatický test je vlastně další kód, který programátor napíše nejlépe předtím, než začne programovat nějakou funkcionalitu aplikace. Test není součástí aplikace, ale verzuje se spolu se zdrojáky, a moderní programovací jazyky velmi pečlivě zohledňují testovatelnost.
Není žádná výmluva pro nepsaní testů, nikdy. Už dobrých 20 let se považuje za prokázané, že automatické testy vedou k
  • rychlému nalezení chyb nového kódu
  • ujasnění designu a zpětné vazbě analýze dříve, než je aplikace hotová
  • rychlému nalezení chyb, které způsobily opravy na jiném místě
  • konzistentnímu refactoringu (nic se nerozbije)
  • dokumentaci funkcionality (test minimalisticky ukazuje, jak se funkcionalita používá)
Naopak prosby nebo dokonce zákazy manažerů, aby se psaní testů odložilo, protože není čas, končí tak, že
  • dostanou funkcionalitu ještě později
  • druhý den se opravuje oprava dne předchozího, den za dnem
  • nikdo neví, co to vlastně dělá a k čemu to je (brzy ani autor)
  • jakákoliv změna v kódu znamená nutnost manuálního přetestování skoro celé aplikace, protože nikdo neví, co všechno změna ovlivnila

Refactoring

Refactoring se přímo zaměřuje na snižování technického dluhu. Obvykle je dobré začít psaním testů, dopisováním //FIXME a //TODO, případně komentářů, kam si zapíšete své objevy proč a co se v tom daném místě děje, co je na tom špatně, jak by to mělo být správně. Tyto komentáře neslouží k tomu, aby v kódu zůstaly, ale abyste se při své analýze neztratili.
Musíte postupovat opatrně, protože se pohybujete na "minovém poli" (proto kód chcete přece refaktorovat), a krok vedle může znamenat, že své úpravy zahodíte (dokud je ještě čas).

Refactoring předně slouží k tomu, aby byl kód čitelný, měl jasné odpovědnosti a funkcionalitu, choval se předvídatelně a funkcionalita byla vždy k nalezení tam, kde jí člověk hledá. Potom se na kódu teprve dá stavět něco dalšího, kde nebudete muset vymýšlet žádné obezličky.

Refactoring nikdy nekončí - ke každému kódu se po čase musíte iterativně vracet, protože jak se rozvíjí aplikace, je občas třeba změnit trochu i uspořádání kódu, sloučit věci, které se původně zdály rozdílné, ale nejsou, rozdělit věci, které původně dělaly téměř totéž, ale už dávno to není pravda, atd.
Ač se to některým lidem zdá pořád neuvěřitelné, nečitelnost kódu, velké množství duplicit a slabé pokrytí testy mají extrémní vliv na jakýkoliv budoucí rozvoj, daleko větší než sebekomplikovanější zadání.

... a odkládání

Pokud se údržba zanedbává, problémy na sebe nenechají dlouho čekat:
  • náklady na rozvoj aplikace jsou čím dál vyšší, neúměrně požadavkům zákazníka
  • opravené chyby uživatel opět hlásí jako neopravené (našel je i jinde)
  • aplikace se chová nekonzistentně (a uživatel jí nenávidí)
  • aplikace potřebuje více paměti a je pomalá
  • vývojáři trvá velmi dlouho, než zjistí, co má vlastně dělat, těžko se orientuje
  • n testů téže věci a podobná věc není otestovaná vůbec
  • nepřehledná dokumentace, nepřehledné testy, nepřehledná aplikace
  • vývojáři nenávidí aplikaci a po čase odchází jinam (nepodceňovat!)
Často se ale zapomíná také na to, že jsme jen lidé a zapomínáme. O týden odložená údržba už znamená, že se v ošklivém kódu přestává orientovat i jeho autor, a nejen rozvoj aplikace, ale i její údržba stojí více, je namáhavější a také při ní pravděpodobně vznikne více chyb.Je to podobné jako s úvěry - u některých můžete odložit několik splátek, ale pak je bude mnohem těžší dohnat. Možná to už nezvládnete ...

Nástroje

Co se týče sledování odhadu technického dluhu u nás používáme SonarQube; V této aplikaci je i řada dalších metrik kvality software a dá se říct, že je to jediná aplikace, kterou znám, která umí zobrazovat i historii různých hodnocení projektů a dá se i zhruba použít k porovnávání kvality. Podotýkám, zhruba, protože žádný software nemůže posoudit to, jak vaše aplikace plní požadavky uživatele a zákazníka.

No a pokud jde o nástroje pro vývojáře a tvorbu automatických testů, refactoring a vývoj obecně, ti už "ty svoje" nástroje určitě dobře znají ;-)