Jak jsme refaktorovali Android aplikaci Liftago
Rok 2018 v Liftago Android aplikaci byl ve znamení refaktoringu. Z poměrně nepřehledného špagetového kódu se nám za několik měsíců povedlo vytvořit aplikaci, která má jasně danou strukturu a se kterou je radost pracovat.
Liftago je na trhu už od roku 2013 a na kódu to bylo hodně vidět. Postupným vývojem se kód stal velmi nepřehledným. Když jsem na začátku roku dostal jednoduché zadání, bylo pro mě problematické zorientovat se, do jakých zákoutí musím sáhnout, abych dosáhl svého cíle. Bylo jasné, že takový stav je neúnosný.
Všechno jde, když se chce
Při střetu s ne úplně pěkným kódem někteří programátoři vyměknou, podají výpověď a zkusí štěstí někde jinde. Jiní rezignují a začnou “prasit”. Dělají co nejmenší možné změny ve strachu, aby něco nerozbili. Novou funkcionalitu kostrbatě naroubují do stávajícího kódu, i když k tomu není nejvhodnější.
Ověřili jsme si, že se dá postupovat i jinak. Malými postupnými krůčky vždy o kousek zlepšíte stávající kód a když je jich hodně, tak si můžete být jisti, že výsledek bude na kvalitě poznat.
Nikdy nic nerozbít
Klíčem k úspěchu je dělat malé změny, u kterých se mohu zaručit, že jimi nic nerozbiju. Pokud mám ambicioznější cíl, musím se naučit rozložit si práci na sérii menších změn, které mě dovedou k cíli. Ty pak musím co nejrychleji začleňovat, aby se moje verze nerozjížděla s produkcí.
Samozřejmě ne vždy se daří a někdy se stávalo, že jsem se týden v něčem vrtal a stejně to úplně nefungovalo. Pak se mi osvědčilo nelítostně všechno zahodit a začít znova. Většinou to pak dopadlo dobře, z problému jsem se poučil, použitelné části jsem nakopíroval z vedlejší větve v gitu a zbytek rychle dokončil.
Díky této filozofii jsme se vyhli problémům se stabilitou a téměř dosahovali cílové metriky 99,9 % spuštění aplikace bez pádu.
Automatické formátování kódu
Někteří programátoři jsou citlivější na konzistenci mezer a odřádkování, jiní méně. Konzistentní kód se ale lépe čte a pokud ho naformátuje IDE místo programátora, tak se celý projekt dá opravit za chvilku a všichni jsou spokojení.
Nepoužívaný kód
Nejlepší kód na údržbu je takový, který neexistuje. Za léta vývoje byla v aplikaci hromada nepoužívaného kódu, který šel jednoduše smazat. A když kus smažete, tak často zjistíte, že další související kód taky není potřeba. Děkujeme Android Studiu, že nás na unused kód upozorňovalo zašedlou barvou metod nebo tříd.
Kotlin
Když jsme narazili na soubor, který by potřeboval vylepšit, tak jsme ho vždy nejprve převedli z Javy do Kotlinu. Android Studio má na to magickou klávesovou zkratku, takže je to velice jednoduché. Jejím použitím vzniká kód, který není úplně pěkný, ale po opravě cca 0–2 menších chyb funguje. Pak následuje fáze, kdy kód upravíme do podoby, jak by vypadal, kdybysme ho psali na zelené louce.
Někdo doporučuje převést na začátku do Kotlinu rovnou celý projekt. My jsme zvolili postupný přechod z následujících důvodů:
- menší pravděpodobnost, že se něco pokazí
- víme, že kód v Kotlinu už je “hezký” a není nutné ho dále přepisovat
- GitHub nám ukazuje kolik procent kódu je už je převedeno do Kotlinu, což používáme jako přibližný ukazatel, kolik dobré práce už jsme odvedli (taková gamifikace trochu :) ). Mimochodem počítadlo po roce práce ukazuje slušné číslo 67,3 %.
Dependency Injection
Nástrojem, který nám velmi pomohl k pročištění kódu aplikace, bylo Dependency Injection. Důsledné používání DI velmi zpřehlední, jak na sobě kód závisí, a umožňuje psát unit testy pro jednotlivé třídy.
Původně hojně používané singletony jsme začali likvidovat jeden po druhém. Postup je takový, že přidáte singleton do DI kontejneru, najdete si jeho getInstance metodu, necháte si pomocí funkce find usages v Android Studiu ukázat její jednotlivé výskyty a postupně je přepisujete na injektované závislosti. Když metoda getInstance nemá žádný výskyt, vyhráli jste a můžete ji smazat.
Univerzální řešení
Podle mého názoru nejhorším zlem, které se šíří mezi méně zkušenými programátory je poučka DRY — do not repeat yourself. Vznikají pak takzvaně univerzální komponenty, které mají milion přepínačů a blbě se používají, místo několika jednoduchých a jednoúčelových.
V Liftagu máme dvě Android aplikace (pro pasažéry a pro řidiče). Máme je ve společném projektu jako tři moduly: DRV, PAS a Common. Na první pohled možná vypadá jako dobrý nápad nacpat co nejvíce kódu do commonu, co kdyby ho potřebovaly obě aplikace. Vznikaly nám tak ale komponenty, které při pozornějším pohledu obsahovaly spoustu ifů, kde půlka kódu se spouštěla v PAS, půlka v DRV a skutečně společného kódu bylo tak málo, že by bylo lepší ho copy pastovat.
Ono to tak nutně nemusí být v momentě, kdy ten kód píšete. Ale postupem času přicházejí nové požadavky, které jsou jiné pro obě aplikace.
Další nevýhodou common modulu je znepřehlednění aplikace. Kdykoliv něco potřebujete, musíte to hledat na dvou místech. Také platí, že kvůli veškerým změnám v commonu musíte upravovat obě aplikace.
Aktuálním doporučeným postupem je tedy vypráskat z common modulu co nejvíce kódu. Obzvláště to platí pro UI komponenty, protože kdykoliv může přijít požadavek na změnu designu, který bude platný jen pro jednu aplikaci.
Architektura
Jednotlivé obrazovky jsme postupně přepisovali do architektury Model-View-Presenter. Vybrali jsme ji proto, že se dá velmi snadno nasazovat postupně. Není nutné dokonce ani překopat celý fragment najednou.
Původně jsme měli aplikaci složenou z fragmentů a aktivit, které byly až v několika tisíciřádkových třídách, kde se míchala logika a prezentační vrstva.
Na příkladu bych chtěl lehce naznačit, jak MVP v praxi vypadá. Pro každou obrazovku vytvoříte kontrakt, který se skládá z interfaců pro presenter a view. Interface View bude implementován fragmentem nebo aktivitou, ke kterému vznikne přidružený objekt presenter.
Pak už vlastně jen stačí přesunout prezentační kód do metod definovaných view interfacem a logiku do metod presenteru. Jen šoupete existující řádky kódu na správná místa, až nakonec vznikne krásně přehledný kód :)
Vzal jsem to trochu hopem, ale podrobnosti si můžete přečíst třeba v tomto článku.
Testy
Testovat jsme nezačali úplně hned. Nejdřív musíte mít pěkný testovatelný kód a k tomu chvíli trvá se propracovat.
Postupně jsme začali psát unit testy příležitostně pro některý nový kód, když byla nálada. Pak nás trošku vyhecoval náš CTO Martin, že za dob jeho programátorské kariéry on měl tisíce (a miliony testů). Výzvu jsme přijali a nyní již máme tisícovku taky. Teda skoro - když zaokrouhlíme číslo 587 :D Aktuálně jsme na tom tak, že pull request bez testů neprojde přes code review.
Mezi výhodami testů se uvádí, že vám například pomohou nerozbít aplikaci při změnách. Z naší zkušenosti ale největším benefitem je, že vás donutí navrhnout správně architekturu kódu, což později oceníte.
Refaktoring za běhu
Pokud programátor není schopen dodávat do produkce a neustále hlásí “já refaktoruju”, tak to nepůsobí úplně dobře. Řešíme to tak, že refaktorujeme kód, který souvisí s novou funkcionalitou. Při plánování můžeme úměrně navýšit odhad pracnosti, takže náš produkťák David ví, s čím má počítat.
Vedlejším efektem tohoto postupu je, že se zlepšuje kód, který je důležitý, a ne obrazovky, které nikoho nezajímají.
Závěrem
Na začátku roku 2019 máme Android aplikace vnitřně v mnohem lepším stavu, než tomu bylo v lednu 2018. Nám programátorům se s kódem lépe pracuje a radostněji se nám vstává do práce. Aplikace je spolehlivější a méně trpí na divné bugy. Největší posun však nastal ve schopnosti adaptovat se na nové požadavky. Komplikovanou funkcionalitu, kterou bysme dříve možná odmítli dělat z důvodu přílišné náročnosti, nyní bez problémů přidáme.