sobota 19. května 2012

Produktivní rychlokodér

... aneb ovládněte základy Vašeho řemesla

Je součástí Vaší profese vytváření programového kódu? Píšete kód produktivním způsobem? Znáte a používáte nástroje, které Vám pomáhají produktivitu zvyšovat? Saháte často při psaní kódu na myš?

Stává se téměř denně, že objevím nějakou novou fintu, kterou se snažím využít pro zlepšení své produktivity. Může se jednat o novou klávesovou zkratku, nový nástroj, novou techniku, zrychlení často se opakujícího postupu, apod. Pokud chceme svoji práci dělat co nejlépe, je potřeba systematicky pracovat na zvyšování naší kodérské produktivity.

Tento příspěvek se týká aspektů rychlosti vytváření programového kódu. Rád bych shrnul elementární dovednosti, které jsou nutným základem pro práci kodéra. Příklady v textu se týkají platformy Visual Studia, kterou používám. Předpokládám však, že pro jiná IDE existují ekvivalentní nástroje.

Hbité kodérovy prsty

Kolika prsty píšete a jak rychle? Díváte se při psaní na klávesnici? Děláte často chyby a musíte se při psaní vracet o odmazávat?

Někdo nepovažuje rychlost psaní u kodéra za příliš důležitou. Sami však tušíte, že je rozdíl mezi datlováním dvěma prsty a symfonií všech deseti. Rychlost se uvádí v úhozech (keystrokes) za minutu nebo v počtu napsaných slov za minutu (WPM). Změřte si svou rychlost např. na http://10fastfingers.com. Průměr je mezi 50 a 70 wpm. Pokud se dostanete nad 100 wpm, patříte mezi 20% nejrychlejších psavců. Raději nebudu uvádět kolik jsem naměřil sobě. :)

A jak tuto dovednost zlepšovat? Zkuste se naučit hmatovou metodu, která využívá všech deseti prstů. Pořiďte si ergonomickou klávesnici, která Vám bude vyhovovat.

Můžete si také přečíst, co si o dovednostech pro psaní textu myslí respektovaný Jeff Atwood v příspěvku We Are Typists First, Programmers Second.

Učte se klávesové zkratky a nesahejte na myš

Důležitost konkrétní klávesové zkratky pochopíte v momentě, kdy se ji naučíte. Od tohoto okamžiku se bez ní neobejdete. Zkratka většinou supluje akci, kterou můžete udělat myší nebo posloupností více klávesových akcí. Obě metody jsou obvykle pomalejší než zkratka (od toho je to zkratka :). Základní sadu klávesových zkratek má vaše IDE, další dodají dodatečná rozšíření. Pro Visual Studio platí, že základ je sice ok, ale teprve s rozšířením typu CodeRush nebo ReSharper povýšíte z akumulační vrtačky na pořádnou příklepovku! Investice do kvalitních rozšíření se určitě vyplatí.

Doporučuji vytisknout cheat sheety s přehledem klávesových zkratek Vašeho IDE a rozšíření. Zdroje pro Visual Studio (Microsoft, DoFactory), CodeRush a ReSharper. Postup učení pak může být takový, že si nejprve přečtete referenční přehled zkratek, abyste dostali do podvědomí, co všechno se dá "zkracovat". Pak se snažíte před každou požadovanou akcí vzpomenout si na příslušnou zkratku. Několikrát požádáte papír o nápovědu a podle konkurenceschopnosti vaší střednědobé paměti si zkratku zapamatujete. Zkratek bývá velké množství a jsou strukturovány do menších oblastí. Zřejmě i zde platí Paretovo pravidlo, že 20% zkratek budete využívat v 80% případů. Naučte se tedy alespoň těch 20%.

Automatické doplňování a kontextový našeptávač

Ve VS je k dispozici nástroj IntelliSense. Podle aktuálního kontextu, části zadaného jména a s využitím reflexe, Vám například nabídne rychlé vložení příslušného jména identifikátoru. Předpokládám, že není nikdo, kdo by nabízenou pomoc od IntelliSense odmítal. Ne každý však umí IntelliSense vyvolat explicitně (Ctrl + J), zobrazit si informace o metodě a jejích parametrech (Ctrl + Shift + Spacebar), apod.

Vkládání bloků kódu

Psaní kódu není psaní beletrie, ale má charakter kompozice jednoduchých opakujících se bloků kódu do složitějších celků. Nabízí se tedy s těmito bloky pracovat efektivněji. Ve většině IDE je k dispozici podpora code snippetů. Jedná se o předpřipravené kusy kódu s možností definice parametrických částí. Vložení těchto kusů kódu provedete zadáním zkratky snippetu a odesláním řídícího znaku (ve VS tabulátoru). Napište např. foreach a stiskněte tabulátor. Editor v IDE Vám doplní celý blok příkazu foreach s možností změnit názvy parametrizovatelných částí.

V základu vašeho IDE je k dispozici množina předdefinovaných snippetů. Zajímavé jsou však i snippety, které si vytvoříte sami. Pokud používáte nějakou typickou sekvenci nebo blok kódu, určitě si pro něj vytvořte snippet. VS nemá ideální podporu pro vytváření vlastních snippetů a proto musíte vzít zavděk některými externími editory. Např. Snippy nebo Snippet Editor. Doporučuji vytvořit snippety pro Váš projekt a spravovat je centrálně v repository. Budou je mít k dispozici všichni členové týmu.

Rozšíření CodeRush nabízí aparát šablon. V daném kontextu je k dispozici zkratka, která odpovídá klíčovému slovu, názvu datového typu nebo nějaké složitější sekvenci. Stačí pak zadat např. t a po stisku mezerníku bude doplněno true. Šablony jsou inteligentní a jajich použití je variabilní a uživatelsky rozšiřitelné.

Dogenerování chybějícího kódu

Poměrně často používám klávesovou zkratku Ctrl + ., která podle aktuálního kontextu doplňuje chybějící kód. Umí doplnit using pro relativně odkazovanou třídu, vygenerovat pole nebo vlastnost třídy, vytvořit kostru neexistující metody (při použití metody shora-dolů), provést implementaci rozhraní, apod. Bez této funkce (klávesové zkratky) bych se už neobešel.

Vylepšování kódu pomocí refaktorizace

Znalost refaktorizačních technik by měla být v repertoáru každého kodéra. Mezi základní techniky patří přejmenování, vyčlenění kódu do samostatné metody, práce s argumenty metody, apod. Další typy refaktorizací mohou přidat rozšíření. Refaktorizaci použijete v situaci, kdy chcete stávající kód vylepšit (zlepšit čitelnost a udržovatelnost). Pěkné příklady jsou například na sourcemaking.com.

Generování kódu

Ke generování větších a složitějších bloků kódu můžete využít podporu T4 šablony. Generovat se dá z logických DSL modelů, z databázových dotazů a dalších kolekcí. Jedná se o efektivní a dobře použitelný nástroj. Můžete mrknout na ukázku.

Navigace v kódu

Umět se rychle dostat na určité místo kódu je neméně důležité jako umět kód vytvářet. Čím je projekt rozsáhlejší, tím jsou nároky na navigaci v kódu vyšší. Pokud však ovládáte klávesové zkratky pro přechod k deklaraci, na předchozí místo v kódu, do konkrétní třídy, apod. nebude pro Vás tato činnost problém. Některé techniky, jako například Test Driven Development, vyžadují cyklické provádění stejné posloupnosti akcí. V těchto případech mohou být rozdíly v produktivitě markantní.

Důležitá je i navigace mezi částmi IDE. Např. přechod do okna s chybami kompilace, do okna s testy, do Solution Exploreru, apod. Na většinu akcí by opět měly být k dispozici klávesové zkratky. Jen se je naučit.

Jak se dále zlepšovat?

  • Vyzkoušejte párové programování a pečlivě sledujte Vašeho spolukodéra. Snažte se pochytit jeho dobré návyky.
  • Sledujte blogy, které píší o používání Vašich nástrojů a zvyšování produktivity.
  • Pokud máte problém, snažte se jej vyřešit automatizací i za cenu (přiměřeně) vyšších startovních nákladů na naučení.
  • Účastněte se programátorských cvičení typu CodeRetreat.
  • Trénujte. ;)

čtvrtek 5. dubna 2012

Programová chyba jako test charakteru

Někdy je zajímavé sledovat, jak programátoři reagují na nahlášení chyby, za kterou jsou zodpovědní. Málo co odhalí lidský charakter tak, jako chování v relativně krizové situaci. Pokusil jsem se s mírnou nadsázkou popsat typologií programátorů právě podle jejich reakcí na chybu. Možná se poznáte nebo třeba někoho z vašich kolegů. :)

Já přeci chyby nedělám

Zastánce této techniky, pan Božský, se snaží za každou cenu zbavit zodpovědnosti za chybu. Případné přiznání chyby by mu narušilo vlastní pocit idealismu, který chová vůči své osobě.

"Chyba sice nastala v mojí třídě, ale vůbec jsem netušil, že ji budeš používat takto!
To je cílené chování aplikace dle specifikace."

Přestože se aplikace chová objektivně špatně a specifikace vůbec neexistuje. Dost času ztrácíte tím, že musíte dotyčného přesvědčit o jeho zodpovědnosti za chybu.

Hulk

Nahlásit chybu takovému programátorovi se obvykle trochu bojíte. Víte, že jeho počáteční reakce je dost agresivní. Zbledne, pak zezelená, natlakuje se, začne prskat a hledat nějaký cíl, na který by se mohla svalit část viny za vznik chyby.

"Sakra, zase to někdo pořádně neotestoval!
Přeci jsem upozorňoval, že úprava může způsobit problémy!
No a co, že jsem to dal k otestování těsně před uzavřením iterace!"

Na hrubý pytel, hrubá záplata :)

Hlavně nenápadně a zatloukat

Programátor se doslechne o chybě, rychle ji opraví a tváří se jako by o žádné chybě ani nevěděl.

"Mně se chybu nedaří navodit. Prosím prověř (testere) chování v aktuální verzi.
Vidíš, že se to chová správně!"

Přiznání, že tam chyba opravdu byla, se od něho dobrovolně nedočkáte. Po urgencích se možná přizná, ale pachuť neupřímnosti zůstává.

Sebemrskačství

"Hrozně mě ta chyba mrzí! To snad není možné! Jak se to mohlo stát?! Takový jsem si dával pozor!"

Každá chyba je pro něho stresovou záležitostí. Každé šlápnutí vedle ještě více sráží jeho již tak nízké sebevědomí. Pošlete jej na kurs asertivního chování!

Alibismus ukrytý v pseudosložitosti

Zaplavit kolegy hromadou nadbytečných informací, které navodí dojem toho, že prostě chyba v tak komplikované situaci musela zákonitě vzniknout.

"Databáze je taková a maková, nastavení bylo netypické, plán optimalizace nepředpokládatelný. S tím jsem sice mohl počítat, ale těch možných stavů je tolik, bla bla bla ..."

Prostě to oprav a neztrácej svůj ani můj čas zbytečným vysvětlováním!

Jsem z planety pokročilých uživatelů

"Proč se tohle UI uživatelům nelíbí, vždyť je tam tolik funkcí a všechny jsou pěkně vidět naráz?!
Spousta informací pěkně rozmístěná po velkém formuláři.
Barvy jsou dostatečně výrazné, aby upozornily na důležité části UI.
Proč by to nemohlo zobrazit vždy dialogovou zprávu o úspěšném ukončení zpracování."

Takový člověk podle sebe a svých subjektivních vjemů chybně předpokládá chování ostatních uživatelů. Pokud nezná nebo nechápe základní pravidla uživatelské použitelnosti (UX), zkuste mu je vysvětlit. Ale ať už nediskutuje a upraví to!

A jaká by měla být správná reakce?

Jedná se o velmi zjednodušený postup, ale můžete zkusit třeba:

  1. Zajistěte si kompletní informace o chybě. Potřebujete znát kontext spuštěné aplikace (zákazník, operační systém, typ prohlížeče, apod.), postup navození chyby, popis co je vlastně chybou a jak by se měla aplikace správně chovat. Vše by mělo být součástí hlášení o chybě ve vašem systému na sledování chyb.
  2. Někdo by měl chybu klasifikovat. Určit její závažnost a přiřadit jí prioritu, která ovlivní rychlost její opravy. Měl by také domyslet nápravná opatření, např. opravu dat v databázi, apod.
  3. Chyba je směrovaná na vás. Objektivně rozhodněte, zda-li je problém opravdu na vaší straně nebo ji směrujte na příslušného správce části aplikace, se kterou chyba souvisí. Zkuste omezit počet přehazování chyby mezi řešiteli. Ztrácíte čas váš i ostatních.
  4. V rámci řešení můžete získat další informace o důsledcích chyby (výši škody). Zveřejněte tyto informace pro ostatní zainteresované kolegy. Buďte transaparentní, upřímní a nic nezatloukejte. Zvýšíte tím efektivitu řešení problému.
  5. Odstraňte systémově příčiny problému tak, aby se chyba neopakovala. Přijměte nápravná opatření na související chyby. Napište programový test, který se bude provádět automaticky a upozorní na případné vrácení podobné chyby.
  6. Informujte o vyřešení chyby. Sdělte právě tolik informací, kolik postižená strana potřebuje vědět.

středa 21. března 2012

Automatické testování proti databázi

Proti databázi budete zřejmě spouštět testy pokrývající datovou vrstvu. U vyšších vrstev aplikace, použijete testovací databázi v situacích, kdy není vhodné datovou vrstvu mockovat. Testovací databáze se bude hodit v případě zátěžových a výkonnostních testů.

Dostupnost testovací databáze

Testy potřebujete spouštět na pracovních stanicích vývojářů, na buildovacím serveru nebo na jiném počítači, který nemusí být nutně připojen k počítačové síti. Je obvyklé, že databáze se zpřístupní v okamžiku spouštění testů a po jejich provedení se zahodí.

Není také vhodné, aby více běhových testovacích prostředí přistupovalo na stejnou instanci databáze. Mohly by se vzájemně ovlivňovat v operacích, které nejdou schovat do transakce (např. DDL příkazy).

Z uvedených důvodů se používají lokální databáze. V odůvodněných případech můžete použít i serverovou instanci databáze, ale toto řešení přináší jistá omezení plynoucí ze společného přístupu více testovacích klientů.

Rychlost vytvoření lokální databáze

Jeden z důležitých požadavků kladených na testy je jejich rychlost provádění. A databázové testy patří mezi ty nejpomalejší. Pokud používáte lokální databázi, musíte počítat s časovými režijemi na vytvoření instance databáze.

Nejrychleji startují paměťové (in-memory) databáze (např. SQLite) a databáze pracující nad datovými soubory. Pokud je vaše datová vrstva nezávislá na konkrétním typu databáze, můžete pro účely testů použít některou z rychlých variant, která neodpovídá produkční databázi. Např. u zákazníků běží vaše aplikace na MS SQL Serveru a na Oracle, ale testujete proti SQLite.

Jak to funguje u nás

V naší firmě jsme zvolili testovací databázi stejnou jako produkční a to MS SQL Server. Každý počítač, na kterém chceme spouštět testy, má instalován plnotučný MS SQL Server nebo alespoň MS SQL Server Express (na vývojářských stanicích). Při této variantě je nutné databázi obnovit (příkaz restore) z lokálního .bak souboru. Naše databáze má více jak 1000 tabulek a její obnovení trvá do 10 sekund. Optimalizací je to, že databázi obnovujeme pouze jednou, na začátku spouštění sady testů.

Soubor s databází je vytvářen ze serverové databázové instance, ke které je omezený přístup. Jsou na ní spouštěny pouze schválené změnové skripty a přímo na ní se netestuje. Soubor je uložen v systému pro správu zdrojových kódů a je synchronizován spolu se zdrojovými produkčními kódy a testy na počítač, kde se testy spouští.

Vývoj a spouštění testů probíhá nad více vývojovými větvemi. Pro každou větev je k dispozici kompatibilní databázový soubor s příslušnou strukturou.

Testovací data

Aby bylo možné validovat výsledek spuštění každého testu, musí být splněny vstupní podmínky. V případě testů proti databázi musí být k dispozici taková testovací data, která předpokládá test. Ideální situací je prázdná databáze před testem. Všechna data jsou pak v inicializaci testu do databáze vložena skriptem. Složitost databázových skriptů se liší podle testovaného problému a složitosti vazeb do podřízených tabulek.

Vyhněte se přípravě testovacích dat pomocí programových tříd. Může se stát, že test selže ve fázi právě této přípravy testovacích dat. Tím se celý test znehodnotí a chybně indikuje problém, který je zřejmě jinde než v testované části kódu. Navíc zbytečně zvyšujete složitost kódu testů.

Někdy může být výhodné mít v databázi část dat předvyplněných. Jedná se o data, která mají referenční charakter (jsou stejná na všech instalacích) nebo o data, která využívá většina testů - testovací aplikační uživatelé, testovací osoby, apod. Taková data musí být neměnná a testy s nimi musí počítat.

SQL skripty generující testovací data ukládejte do repository. Jeden skript může být použit pro celou sadu testů. Dokonce si dovedu představit, že skript, který generuje např. testovací objednávky, bude použit v testech datové vrstvy objednávek i funkcionality WCF služby pro práci s objednávkami.

Užitečné tipy

Uzavřete provádění celého testu do transakce, která vždy skončí příkazem rollback. Pro promítnutí změn do databáze použijte příslušnou funkci vašeho perzistentního frameworku. Např. pro (N)Hibernate je to funkce Flush() databázové relace.

Databázový uživatel, pod kterým spouštíte testy, by měl mít stejná oprávnění jako v produkčním prostředí. Vyhněte se uživatelům s DBA právy a uživateli, který je vlastníkem databázových struktur. Pod takovými uživateli může test projít, ale v produkci dojde k chybě.

Pokud se chcete vyhnout časově náročné obnově databáze před každým testem, můžete si pomoci "úklidem" databáze. Před nebo po každém testu spustíte skript, který smaže všechna data. V případě, že se vám daří provádět testy v transakci s rollbackem, nebude zřejmě úklid nutný.

Závěrem

Vyberte si vhodnou strategii, která bude vyhovovat vašim potřebám. Efektivní vytváření a spouštění testů zvýší vaši produktivitu práce a vnese potřebnou jistotu do udržovatelnosti aplikace.

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.

neděle 12. února 2012

Jak nepsat jednotkové testy

... aneb pozor na lháře, křiklouna a místního hrdinu

Uvádím seznam nejfrekventovanějších antivzorů (špatných technik) pro psaní jednotkových testů. Vyvarujte se jich a Vaše testy budou v dobré kondici ;-)

  • Lhář (The Liar). Jednotkový test, který prochází pro každý testovací případ. Optimismus nás však přejde, pokud se podíváme blíže na implementaci testu a zjistíme, že ve skutečnosti požadovanou vlastnost netestuje.

  • Nadměrná příprava (Excessive Setup). Test, který potřebuje rozsáhlou přípravu ještě před samotným spuštěním testovaného kódu. Stovky řádků a velké množství objektů, které test vyžaduje, způsobí, že je obtížné ověřit testovanou funkcionalitu. Test může selhat z mnoha jiných příčin, než z vlastní chyby v testovaném kódu.

  • Obr (The Giant). Jednotkový test, který pokrývá velké množství testovacích případů a je v rozsahu stovek až tisíců řádků. Přestože validně testuje testovaný kód, jedná se zřejmě o jednotkový test nad božským objektem (God object). Tedy chybný návrh třídy, která v sobě slučuje více zodpovědností.

  • Imitátorna (The Mockery). Používání mock objektů je v mnoha případech dobré a šikovné. V některých případech však mohou vývojáři ztratit kontrolu sami nad sebou a nadměrným používáním mock, fake a stub objektů potlačit vlastní testovanou funkcionalitu. Dochází pak spíše k testování dat, které vracejí jednotlivé mock objekty. Definice mock objektů je navíc velmi křehká a může být snadno rozbita a chybně způsobí selhání testu. To je samo o sobě proti principům TDD. Příliš mnoho závislostí a komplikované vazby na další třídy může indikovat testování božského objektu. V takovém případě by měla být testovaná třída refaktorována nebo by mělo být jedno volání nahrazeno sekvencí více volání metod s menším rozsahem funkcionality. Každé dílčí volání by pak mohlo být otestováno samostatně a není nutné znovu testovat celou sekvenci jako celek.

  • Inspektor (The Inspector). Inspektor je jednotkový test, který ví příliš mnoho o vnitřní struktuře testovaného kódu. Je napsán na míru z důvodu 100% pokrytí kódu. V případě, že je testovaný kód refaktorován a přestože se jeho validita nemění, tzn. měl by procházet, začne test neprocházet. To vynutí současnou změnu i v jednotkovém testu.

  • Velkorysé zbytky (Generous Leftovers). Jeden jednotkový test vytvoří data, která jsou někde persistována (uchována). Jiný test tato data využívá pro svoje vlastní (původnímu testu neznámé) účely. Pokud je takový "test-generátor" spuštěn později nebo pouze částečně, dochází chybně k selhání v testu, který je na něm závislý.

  • Místní hrdina (The Local Hero). Test je napsaný tak, že obsahuje závislosti na prostředí, ve kterém byl vyvinut. V případě, že je spuštěn v těchto podmínkách, pak v pořádku prochází. Pokud se však test spustí v jiném prostředí, selže.

  • Hnidopich (The Nitpicker). Test, který prověřuje kompletní výstup, přestože významná je pouze část vrácené informace. Pokud se změní nevýznamová část vrácené informace, začne test chybně selhávat. Takové testy jsou typické při testování webových aplikací.

  • Tajemný lovec (The Secret Catcher). Na první pohled takový test vypadá, že nic netestuje, neboť nemá žádné asserty. Ovšem v tomto případě platí rčení, že "ďábel je ukryt v detailech". Test předpokládá, že v případě selhání vyvolá testovaný kód výjimku, kterou zachytí a zpracuje testovací framework. Řešením by mohlo být odchycení výjimky do proměnné určitého typu a porovnání na null hodnotu. Nebo můžete použít atribut ExpectedException.

  • Ulejvák (The Dodger). Jednotkový test, který testuje několik méně podstatných aspektů testovaného kódu a vyhýbá se otestování chování zásadního. Obvykle z důvodu vyšší složitosti takového otestování.

  • Křikloun (The Loudmouth). Jednotkový test nebo sada testovacích případů, které posílají na konzoli velké množství testovacích, ladících nebo protokolovacích zpráv, i v případě, že testy procházejí. Jedná se obvykle o pozůstatky ručního ladění. Tyto nevýznamné zprávy znepřehledňují výsledký protokol o výsledku spuštěných testů.

  • Nenasytný lovec (The Greedy Catcher). Jednotkový test, který zachytí výjimku a "polkne" ji včetně trasování zásobníku. Tuto výjimku někdy nahradí informačně méně hodnotnou zprávou. Někdy dokonce chybu pouze zaloguje a test nechá projít.

  • Řadič (The Sequencer). Jednotkový test, jehož procházení je závisle na určitém pořadí assertů, u kterých by na pořadí záležet nemělo.

  • Skrytá závislost (Hidden Dependency). Hodně podobný "Místnímu hrdinovi". Test přepokládá, že před jeho spuštěním jsou připravena data, na kterých je závislý. Pokud tomu tak není, test selže. Vývojář obdrží omezenou informaci o problému a je nucen projít velké množství kódu a zjistit problém se závislými daty. Například starší .dll knihovny mohou být závislé na ini souboru, který řídí jejich chování. Je obvykle složité dopátrat se vlastní příčiny selhání testu a této skryté závisloti.

  • Výčet (The Enumerator). Jednotkový test s testovacími případy, které se jmenují podobně. Např. TestMethod1(), TestMethod2(), ... V tomto případě není možné z názvů testovacích případů určit jejich význam. V případě selhání je vývojář nucen procházet přímo zdrojový kód testu a snažit se pochopit jeho význam.

  • Cizinec (The Stranger). Testovací případ, který nepatří do jednotkového testu. Ve skutečnosti testuje jiný typ objektu, který je využíván a vrácen testovaným objektem.

  • Kazatel operačního systému (The Operating System Evangelist). Jednotkový test, který se opírá o specifické prostředí nebo vlastnosti operačního systému. Příkladem může být assert v testovacím případě, který ověřuje sekvenci znaků pro nový řádek a předpokládá Windows konvenci. Takový test selže při spuštní na Linuxu.

  • Úspěch zaručen (Success Against All Odds). Test, který byl napsán nejdříve tak, aby prošel, místo aby selhal. Naneštěstí pak takový testovací případ prochází i v situacích, kdy by měl selhat.

  • Jízda zadarmo (The Free Ride). Namísto vytvoření nové metody testovacího případu pro otestování další vlastnosti nebo funkcionality, se přidá pouze nový assert k již existujícím.

  • Jedinečný (The One). Kombinace několika vzorů, zejména "Jízdy zadarmo" a "Obra". Test obsahuje jeden testovací případ, který testuje celou sadu funkcionalit testovaného objektu. Obvyklým ukazatelem je to, že testovací metoda se jmenuje stejně jako jednotkový test.

  • Vykukující kocour (The Peeping Tom). Test, který skrze sdílené zdroje vidí na výsledná data jiných testů. Na základě těchto dat může test selhat, přestože testovaný systém je pro testovací případ validní. Toto se běžně stávalo ve FitNesse, kde se používaly statické členské proměnné pro uchování kolekcí, které nebyly korektně vyčištěny po proběhnutí testu. Tento problém se objevoval neočekávaně při některých bězích testů. Vzor známý také jako "Nezvaní hosté" (The Uninvited Guests).

  • Pomalé dloubnutí (The Slow Poke). Jednotkový test, který běží neúnosně pomalu. Když jej vývojář spustí, může si zajít do koupelny nebo zakouřit. V nejhorším případě jej může spustit na konci šichty před odchodem domů.

  • Štastná cesta (Happy Path). Test probíhá pouze po hladké, bezproblémové cestě. Netestuje hraniční hodnoty a výjimky.

  • Podřadní občané (Second Class Citizens). Testovací kód není tak dobře refaktorovaný jako testovaný kód, obsahuje duplicity a je špatně udržovatelný.

  • Spoutaní řetězem (Chain Gang). Dvojice testů, které musí být spuštěné v určitém pořadí. Například jeden test nastaví globální stav systému (globální proměnná, databázová data) a druhý test je na tomto stavu závislý. Např. u databázových testů se může stát, že pokud není provádění uzavřeno ve chráněném bloku a test selže, není po testu korektně uklizeno.

  • Bezejmenný test (The Test With No Name). Test, který byl vytvořen, aby reprodukoval nalezenou chybu a jeho autor nepovažoval za důležité vymyslet mu významové jméno. Namísto posílení (rozšíření) existujícího testu, je vytvořen nový test s názvem TestProChybu123. Po dvou letech, kdy tento test začne selhávat, musíte do systému pro sledování chyb a hledáte Chybu123, abyste pochopili účel tohoto testu.

  • Spáč (The Sleeper). Test, který selže v určitou dobu nebo po určitém datu. Jedná se často o nekorektní kontrolu hraničních hodnot při testování kódu, který pracuje s objekty typu Date nebo Calendar. Problémový může být také běh o půlnoci. Chyba je na straně kódu testu.

Informační zdroje

Článek jsem sestavil ze dvou níže uvedených zdrojů a dokořenil jej vlastními vsuvkami. Může se stát, že se mi nepodařilo trefit ideální český ekvivalent pro název některého antivzoru. Navrhněte lepší, rád upravím.