neděle 26. února 2012

Techniky pro prevenci softwarových chyb

... aneb chyby se začnou bát vás!

Chyba, defekt, štěnice, brouk, bug, fail, fault, mistake. Tato slova nám nezní moc libozvučně. Ve své práci vynakládáme nemalé úsilí na jejich deratizaci. V tomto průřezovém článku jsem se zaměřil na několik preventivních technik. Zdravotní pojišťovny ví, že prevence je levnější než léčba. Stejně tak si i zkušení vývojáři uvědomují, že čím dříve se chybu podaří diagnostikovat, tím bude její odstranění levnější. Legrace končí, pokud se chyba dostane až do provozního prostředí.

Chyby na sebe berou mnoho podob. Může se jednat o pád aplikace na neošetřenou chybu některé z komponent, sémantickou nefunkčnost aplikace (chybný výpočet, nesprávná data) nebo nesplnění nefunkčního požadavku na systém - pomalost, porušení zabezpečení, neškálovatelnost, apod. Chyby mají různou závažnost a některé dokáží způsobit hodně velkou škodu. Na míře chybovosti je závislé vnímání vaší aplikace uživateli. Je to výrazný faktor úspěšnosti celého vašeho snažení.

Pište čistý kód

Krysy žijí v kanálech a švábi v místech, kde je nepořádek a neuklízí se. Softwarové chyby to mají podobně. Více se objevují v kódu, který nedodržuje metodiku "čistého kódu". Jaký kód je čistý? Kód, který je čitelný, přehledný, dodržuje kódovací standardy, je správně strukturovaný, používají se v něm vhodné názvy, má správné úrovně abstrakcí v metodách, apod. K technikám čistého kódu se možná dopracujete praxí nebo rychlejším způsobem, přečtením výborné knihy Čistý kód od Roberta C. Martina.

Čistý kód podporuje efektivnější ladění. Pokud např. dodržujete pravidlo, že každá metoda má právě jeden výstupní bod (příkaz return), je snadnější rozmístit ladící body. Pokud správně strukturujete kód třídy na menší metody a nemícháte více úrovní abstrakce do jedné metody, navigace je rychlejší a případné problémové místo najdete snáze.

Důsledkem aplikace pravidel čistého kódu je především dobrá čitelnost kódu, která snižuje riziko "ukrytí" zákeřných programových chyb už v prvotní fázi psaní kódu.

Dobrý objektový návrh

Chybný objektový návrh dokáže hodně zavařit a čím později si chyby uvědomíte, tím více práce si přiděláte. Dobře navržený systém respektuje pravidla návrhové metodologie SOLID a dalších dobrých návrhových praktik. Rozsáhlejší systémy, které nerespektují dobrý návrh jsou problematicky udržovatelné a vysoce rizikové při jakémkoliv pozdějším zásahu do kódu. Říkáme, že jsou křehké. Zanesení chyby je vysoce pravděpodobné.

Neobjevujte kolo a naučte se využívat návrhové vzory. Buďte si jistí, že problém, který řešíte, už měly zřejmě stovky vývojářů před vámi. A návrhový vzor je efektivním řešením tohoto typu problému. Jsou popsány desítky vzorů pro vznik objektů, pro zachycení struktury mezi objekty, pro řízení chování, pro implementaci jednotlivých vrstev aplikací (datová, obchodní logika, prezentační, ...) a další.

Díky dobrému návrhu zoptimalizujete i další preventivní techniky popsané v tomto článku. Například bez Inversion of Control a programování proti rozhraní (ISP) byste psali problematicky jednotkové a integrační testy.

Naučte se i antivzory (antipatterns), špatné techniky návrhu. Jejich znalost vás již dopředu bude varovat před chybnými kroky, které by vás jinak stály vyšší chybovost, čas a prostředky.

Refaktorizace kódu

Rekaftorizace kódu je změna struktury kódu, která nemá vliv na jeho celkovou funkčnost. Znalost refaktorizačních technik patří mezi základní dovednosti vývojáře. Příkladem refaktorizačních technik je přejmenování (proměnné, metody, třídy), vyjmutí části kódu do samostatné metody nebo třídy, změna pořadí argumentů metody, apod. Nezbytností je, aby vaše IDE podporovalo refaktorizační techniky. Nebojte se refaktorovat vždy, když budete mít pocit, že se kód zlepší.

S využitím refaktorizace dosáhnete čistého kódu a dobrého návrhu. Snížíte složitost částí kódu. Platí pravidlo, že co je složité, je náchylné k vyšší chybovosti. Dobrý vývojář dokáže i složitý problém implementovat přehledně a "jednoduše".

Hezký popis refaktorizačních technik je k dispozici na serveru SourceMaking.

Revize kódu (Code Review)

Promluvili jste si někdy o svém kódu s kolegou? Bavili jste se, proč jste použili zrovna takovou implementaci? Uvědomili jste si při tom, že by problém šlo vyřešit lépe nebo jste dokonce objevili chybu? Výborně! Pak jste použili techniku revize kódu. Když se na váš kód podívá někdo jiný, je to téměř vždy ke prospěchu věci.

Kdy provádět revizi kódu? Ideální by bylo během nebo těsně po vlastní implementaci. Revize kódu je jedním ze základních aspektů párového programování (Pair Programming). Dvojice společně vyvíjí kód a revize probíhá neustále. Pokud neaplikujete párového programování, můžete nastavit povinnou revizi kódu při umisťování změn na server. Revizi můžete také provádět v rámci celého vývojového týmu na pracovních poradách.

Jak je revize kódu efektivní? Záleží na úrovni zkušeností vývojáře a "revizora". U začínajících vývojářů budou revize častější a revizorem by měl být zkušený pracovník. Při revizi kódu vytvořeného zkušeným vývojářem nemusí být objeveno mnoho chyb, ale celý proces může posloužit k tomu, že posluchačům budou předány zkušenosti a praktické rady. Technik revizí kódu je více a liší se svojí formálností, obsazením revizního týmu a způsobem evidence nalezených defektů.

Revize kódu má však i svá rizika. V posuzování práce někoho jiného musíte být opatrní, abyste dotyčnému neublížili před ostatními. Chybně provedená veřejná revize může způsobit konflikty. Měli byste vždy vývojářům, jejichž práce bude revidována, vysvětlit účel a přínosy pro něj i ostatní.

Statická analýza kódu

Analýza kódu, která je prováděna bez nutnosti spouštění programu. Jedná se o soubor preventivních technik, které mají odhalit defekty a problémová místa ve vašem systému. Jak se problémová místa poznají? Například podle složitosti. Jak již bylo uvedeno výše, u složitě kódované části aplikace je pravděpodobnější, že vývojář zanesl nebo přehlédl chybu.

Problémová místa vám pomáhají určit softwarové metriky orientované na kód a na návrh tříd. Uvedu pár příkladů:

  • Cyclomatic Complexity vyjadřuje složitost části programu (např. metody) co do množství větvení a cyklů. Tyto programové konstrukce zvyšují počet možných cest provádění programu. Vysoké číslo indikuje komplikovaně napsané složité metody, které je problematické pokrývat testy. Řešením je metodu rozbít do více menších metod.
  • Line Count vyjadřuje množství řádků kódu v metodách. Dlouhé metody jsou nepřehledné a dělají zřejmě více než jednu věc (pro danou úroveň abstrakce). Použitelnost takových metod je problematická, stejně jako pokrytí testy. Řešením je opět refaktorizace do více metod.
  • Objektové metriky jako Weighted Method Count (celková složitost metod ve třídě), Depth of Inheritance Tree (počet předků třídy) a Coupling Between Objects (provázanost mezi objekty) mohou indikovat problémy v objektovém návrhu.

Údaje získáné z metrik, by měl vyhodnocovat vedoucí vývojář. Nástroje pro statickou analýzu jsou u komerčních IDE k dispozici ve vyšších edicích. Pokud máte k dispozici nástroje, které metriky zobrazují přímo v kódu, naučte se je vyhodnocovat a vaši práci podle nich přizpůsobovat již během vlastní implementace.

Mezi techniky statické analýzy patří také validace kódu podle pravidel definovaných pro zdrojový kód. Tato technika zvýší přehlednost a čitelnost kódu. Každý vývojový tým by si měl na začátku projektu zadefinovat kódovací standardy a zvolit nástroj, který bude na dodržování dohlížet. Jak lze využít StyleCop si můžete přečíst v příspěvku Jak psát lepší kód s využitím StyleCopu.

Volba technologií

Správnost volby technologií může mít na chybovost zásadní vliv. Rozsáhlejší projekt využívá velké množství technologií a nástrojů. Relační databázový systém, vývojové prostředí, programovací a značkovací jazyky, validační frameworky, perzistentní framework, základ pro služby, prezentační frameworky a komponenty, apod. Vybírejte technologie prověřené a odzkoušené (pozor na poslední releasy), které se dobře používají a mají perspektivu (abyste si je nemuseli vyvíjet v budoucnu sami :). Rozhraní dobrých komponent je snadno použitelné, intuitivní a dobře zdokumentované. Důležitá je také testovatelnost.

Automatizované testování

Testování je technika dynamické analýzy kódu. Psaní testů by podle metodologie Test-Driven Development (TDD) mělo předcházet vlastní implementaci. Zkuste si tento přístup zažít a uvidíte, že se vám zalíbí. A pokud ne, vězte, že testy stejně musíte napsat. Jinak se pro vás stane prvotní vývoj a především pozdější zásahy do produkčního kódu noční můrou. Testy jsou indikátory chybových stavů. Vývoj aplikací bez pokrytí testy provozují pouze hazardéři ;)

První typ testů, který napíšete, budou jednotkové testy (unit test). Základní logika jednotkového testu je jednoduchá. Voláte metodu instance testované třídy s určitými vstupy a validujete výstupy. Pokud výstupy neodpovídají předpokladům, test selže a je nutno hledat chybu v implementaci. Lze prohlásit, že vyšší pokrytí testy lépe pojistí váš kód. Ale je také nutno dodat, že testy se nesmí psát jen z formality, ale musí být správně cílené na problémové situace. O špatných technikách psaní jednotkových testů si můžete přečíst v příspěvku Jak nepsat jednotkové testy.

Pokud je test napsaný tak, že "vidí" do implementace, mluvíme o white-box testování. Pokud je testovaný kód pro test černou skříňkou, hovoříme o black-box testování. Oba přístupy mají své opodstatnění. Pomocí "bílého" testování snáze pokryjete všechny cesty provádění. "Černé" testování zase zosobňuje naivní přístup klientské strany, který může přinést nečekané způsoby volání.

Vyšší formou testů jsou integrační a systémové testy. Validují interakci více objektů a funkcionalitu větších celků. Pamatujte, že požadavky na kvalitu kódu testů jsou stejně přísná jako na vlastní testovaný (produkční) kód. Kód testů budete udržovat stejně dlouho jako produkční kód. Zjednodušeně shrnuto, testy by měly běžet krátkou dobu, na libovolném "kompatibilním" počítači, měly by po sobě uklidit a neměly by být na sobě vzájemně závislé.

Snažte se co nejvíce testů zautomatizovat, určitě se vám práce vyplatí. Bez automatizace nejsou některé typy testů vůbec proveditelné. Těžko se shání několik stovek uživatelů na zátěžové testy, kteří by v jednom čase začali používat a zatěžovat vaši aplikaci :)

Zautomatizovat se dá i interakce s uživatelským rozhraním vaší aplikace. Volba nástrojů závisí na technologii prezentační vrstvy.

Ruční testování

Jedná se o pracnější formu validace funkcionality vaší aplikace, která má však stále svoje opodstatnění. Šikovný tester je vynalézavější než vaše automatizované testy a objeví situace a scénáře, které jste nepředpokládali. Ještě vynalézavější jsou však uživatelé, proto se také uvolňují nefinální beta verze :)

Automatizace buildů

Vytvořte a nakonfigurujte buildovací server pro týmový vývoj. Po každém vrácení změn na server spouštějte build průběžné integrace (Continuous Integration), který pohlídá, aby vrácená změna nerozbila integritu projektu. Vývojář získá okamžitou zpětnou vazbu, že jeho změna může zablokovat práci celého týmu.

Užitečné jsou i buildy pro zajištění kvality. Jsou spouštěny v pravidelných cyklech, například každou noc a jejich úkolem je provádět statickou i dynamickou analýzu kódu. Provádějí se validace kódu, vyhodnocují se metriky, spouští testy. Výsledky se následně vyhodnocují a sjednávají se nápravná opatření.

Výhodou automatického buildování je i rychlé vytváření průběžných testovacích verzí a jejich nasazení do testovacího prostředí.

Prototypování

Někdy je obtížné již v ranných fázích vývoje přesně specifikovat způsob realizace cílových požadavků na systém. Víme sice, co chceme udělat, ale nejsme si jisti, jak bude vypadat například uživatelské rozhraní naší aplikace. Prototyp je funkčně zjednodušený základ (předobraz, demoverze) vyvíjeného systému. Měl by vzniknout relativně rychle a slouží k průběžné revizi požadavků. Prototyp můžete předvést investorovi a získat zpětnou vazbu. Prototyp je vyvíjen v cyklech. V každém cyklu jsou zapracovány získané připomínky.

Pokud používáte prototypování správně, můžete získat efektivní nástroj pro řízení a směrování projektu. Vyhnete se chybám, které by byly jinak objeveny až později a jejich řešení by bylo nákladné. Pro vývojáře je někdy tvorba prototypu neoblíbenou činností. Může se totiž stát, že celý prototyp se zahodí jako nevhodné řešení a začne se vytvářen nový. Pořád to však méně bolí než složitě předělávat systém v pozdní fázi implementace.

Revize výstupů v předimplementačních fázích

Většina vývojových metodik rozděluje vývojový cyklus na fáze specifikace požadavků, analýza, návrh, implementace, testování, nasazení a údržba. V každé fázi vznikne určitý výstup. Například v první fázi specifikace požadavků vytvoříte požadavky na systém. Pokud jsou sestaveny chybně a zjistíte to až v okamžiku předávání software zákazníkovi, vyvstává velký problém. Příslušná část aplikace se musí předělat a rozsah víceprací může být značný. Stejně tak chyby v analýze nebo návrhu vás mohou přijít hodně draho.

Proto je vhodné podobně jako revidujete implementační kód, revidovat co nejdříve všechny předimplementační výstupy. Taková revize má svůj formalismus a pokud ji uděláte kvalitně, můžete objevit již v ranné fázi závažné chyby. Pokud máte malý vývojový tým, požádejte kolegu, aby si po vás dokumenty s požadavky, analýzou a návrhem přečetl.

Motivace lidí

Některé z výše uvedených technik selhávají, pokud je používá člen týmu s nedostatečnou motivací pro dosahování kvality. Revize se dají odbýt, testy se dají napsat jen aby se dosáhlo vyššího pokrytí, při psaní kódu lze ošálit statickou analýzu neúčelnou kompatibilitou s validačními pravidly.

Členové vašeho týmu musí být motivováni finančně i nefinančně, aby pro ně bylo přirozené a prioritní zajišťovat vysokou kvalitu svojí práce.

Žádné komentáře:

Okomentovat