Ladění kódu programu (Debugging) ================================ "Ladění je dvakrát složitější než psaní kódu. Píšete-li tedy kód tak šikovně a chytře, jak jen to jde, jste samozřejmě dostatečně bystří a inteligentní, abyste uměli kód také ladit." - Brian W. Kernighan, spoluautor jazyka C a OS UNIX = proces rozpoznání příčiny chyby (hledání chyby) a opravení příčin (opravení chyby), testování = detekce chyby - (leckdy) neobtížnější část programování, 90 procent práce - není (podobně jako testování) cesta ke kvalitnímu SW, pouze riskantní východisko z nouze - lepší se chybám vyvarovat: jazyk, konvence, štábní kultura, komentáře, samostatné části (moduly), způsob psaní kódu (inkrementální) apod. - defenzivní programování (code hardening): předcházení chybám (počítání s nimi, zvýšení robustnosti), ochrana před neplatnými daty (ošetření uživ./síť./IO vstupů, kontrola a ošetření vstupů a výstupů procedur), alternativy v podmínkách, samoidentifikace strukturovaných datových typů, výpis chybových kódů, aserce (samokontrola), výjimky (signalizace vyjímečného stavu volajícímu), používání ladicích programů, refaktorizace, nesmí se přehnat (čitelnost kódu, výkon, věnování se algoritmu atd.) a po ladění odstranit (výkon) - ofenzivní programování: nadbíhánání chybám, vytváření extrémních stavů a navozování extrémních podmínek (vyčerpaná paměť, plné soubory, neplatná práva apod.) Syntaktické chyby ----------------- - projevení při překladu, před spuštěním - použít inteligentní editor kódu (podpora syntaxe jazyka - opravy chyb, šablony konstrukcí, štábní kultura) - překlad s co nejvíce hlášeními - ignorance je horší než nic (nechci vidět chyby), lepší se naučení jazyka - varování považovat za chybu (brát jej vážně), později se jí může stát (obtížně odhalitelnou) - nepřesný řádek v hlášení překladače, proč? (pochopení chyby) - příliš přesné hlášení a více hlášení pro tutéž chybu - vyřešit první chybu a znovu přeložit, nevěnovat se dalším (mohou souviset s první) - opravit všechny chyby - těžko opravovat rafinované, když zůstávají očividné - nalezení - odstraňování částí kódu (binární vyhledávání = půlení intervalu, komentář, příkazy preprocesoru (podmíněný překlad)) - nástroje pro kontrolu syntaxe (a částečně i sémantiky) - dále sémantické (významové, špatné použití jazyka, dnes už některé zvládnou i překladače - varování) a logické (špatný algoritmus) chyby, projevení až za běhu programu Obecný pohled ------------- - bug ... velký mol v počítači Mark I, když určité obvody nefungovaly - chyba způsobena programátorem - žádné chyby jazyka, překladače, počítače, ... vesmíru - program se chová pořád stejně, chyba mezi klávesnicí a židlí = dobrý předpoklad - chyba jako přehlédnutí, překlep, omyl: lepší poznání programu, vyvarování pro příště, rychlejší (ale i podstatně obtížnější!) odhalení a oprava, další podobné?, lepší psaní kódu - chyba jako nepochopení jazyka nebo algoritmu, funkce programu?: ne ladění, ale jak porozumět, jak program vytvářet - neefektivní a nebezpečné: náhodně tisk dat a průzkum výstupu, rychlé náhodné změny (bez pochopení problému a jejich sledování), oprava jen příznaků a ne příčiny (spíše další problémy) apod. - nutný systematický přístup a pochopení problému Hledání chyby ------------- - chyba detekována testováním - nejobtížnější fáze ladění - než hádání (zkouška-omyl), dedukce, klasická vědecká metoda: 1. informace z opakovaných experimentů 2. hypotéza z informací 3. experiment prokazující nebo vyvracející hypotézu 4. prokázání nebo vyvrácení hypotézy na základě experimentu 5. opakování postupu - pro ladění: a) stabilizace chyby - spolehlivé vyvolání znovu (různými způsoby, minimálními modelovými případy vylučovací metodou, překlad a spuštění na jiném systému), jinak velmi obtížné (typicky neinicializovaná proměnná, neplatná paměť (meze pole) nebo race condition u paralelních programů (!) - nepředvídatelné) b) hledání chyby - rozpoznání příčiny (postaty problému): 1. data vyvolávající chybu - z a) 2. analýza dat a hypotéza o příčině - všechny dostupné informace 3. způsob ověření hypotézy - testování nebo analýza kódu (velice obtížné a náročné u paralelních programů - analýza všech možných histori!!) 4. ověření hypotézy 5. opakování c) opravení chyby d) prověření opravy - testování e) další podobné? Tipy - čím horší kód, tím delší nalezení chyby - ze začátku více rychle ověřených hypotéz, v omezeném čase! - testování nebo analýza kódu menších samostatných částí (modulů, paralelně běžících částí) a zužování podezřelé části kódu (ne nahodile, ale binárním vyhledáváním = půlením intervalu, od celého programu (chyba nemusí být právě tam, kde si myslíme)) - uvažovat i negativní testy - zúžení oblasti hledání - prověřit i části kódu, kde byly chyby - může být víc nebo s opravami byly zaneseny nové - použití dostupných nástrojů: překladač (volby, speciální), interaktivní debuggery (překlad s ladicími informacemi), programy/knihovny pro kontrolu paměti aj. Metody a postupy testování nebo analýzy kódu - zběžná analýza částí kódu (algoritmu) - použití kontrolního seznamu častých chyb - vlastního (?), globálního, typicky pro sémantické chyby - tisk dat na klíčových místech (tvorba protokolu) a analýza výstupu - důkladná analýza částí kódu (algoritmu) - odstraňování částí kódu - komentář, příkazy preprocesoru (podmíněný překlad, ladicí makra), příkazy jazyka (ukončení procedury, programu) - porovnání verzí kódu (verzovací systém) - vyhledání chyby ve změnách přidaných ke starším verzím - debugger: na neoptimalizovaném kódu (!), body přerušení, krokování (s přeskakováním částí kódu - volání procedur), historie volání, průzkum dat atd. - jiné nástroje: speciální překladač, pro kontrolu paměti - vysvětlit kód někomu jinému (,,zodpovědné ladění'') - "moment, tohle to nedělá, to je ono, diky", ten neřekne ani slovo - odpočinek - únava, zabřednutí, ztráta orientace, "přestává mi to myslet", nervozita, psychiatrické stavy, ..., nenadálý nápad ,,odnikud'' - hrubá síla: po předchozím (časově omezeném!), pracná a časově náročná, ale zaručená (zvážení výhodnosti), důkladná analýza celého kódu, přepsání celé části kódu, automatizované (regresní) testy po každé (sebemenší) změně, debugování bez přeskakování částí kódu - při modifikaci kódu během hledání chyby mít zálohovaný původní kód! Oprava chyby ------------ - relativně snadná, ale! ... (stejně jako všechno snadné) náchylné k chybám, první oprava bývá 50% chybná - problém (příčinu chyby) je nutné pochopit: bez pochopení nelze opravit, jinak zavlečení dalších chyb, k modelovým případům reprodukce chyby také případy, které by chybu reprodukovat neměly, ne styl pokus-omyl a náhodné změny ("nechapu, asi překročení mezí pole -> přidám k řídící proměnné -1, ne, tak 1, jo, opraveno") - podobné opravení jen příznaků, neúčinné, hrozí úplné rozpadnutí kódu pod rukama, ,,voodoo programování'' (vůbec netušíme, co program dělá) - nutné pochopit program (větší okolí chyby, kontext), nejen (malé) okolí chyby - potvrzení (ujištění správnosti) diagnostiky chyby před opravou: všechny modelové případy reprodukce chyby - v klidu, ne ve spěchu, bez ověření opravy ("poslední drobná chybka"): ukvapenost se může prodražit, neúplná oprava, může skončit nefunkčním programem, mnoha dalšími chybami, ... vyhazovem - mít zálohu původního kódu (verzovací systém): i pro pozdější porovnání a statistiky (ChangeLog) - opravit příčinu, ne jen příznaky!: žádná "lepicí páska (duck tape)" (např. funkce nevrací správný výsledek pro x=10 -> za ní if x=10 nastav správný výsledek, nevrací správný taky při x=0 -> další if ...), přináší spíše komplikace než řešení - většinu čase nebude fungovat, není řešení (pro všechny neodhalené případy), není udržovatelné (nekonečné provizorium), atd. - opravovat jen jednu chybu - jinak zavlečení dalších chyb, ztráta přehledu o chybách a opravách - prověřit správnost opravy: modelové případy reprodukce chyby, (automatizované regresní) testování - přidání testu odhalení chyby - vyhledat a opravit všechny podobné chyby: chyby obvykle ve skupinách (soustředěné ve vysoce chybových částech kódu), potřeba pochopení chyby (test pochopení = nevím, jak hledat podobné chyby) Ladicí nástroje --------------- Zdrojový kód - prohledávání (a nahrazování) v souborech kódu, tvorba křížových odkazů: při hledání proměnných, procedur, apod., nahrazení na všech místech při opravě - porovnání zdrojového kódu: při změnách kódu během hledání chyby, hledání chyby v různých verzích kódu, šíření opravy (záplata - patch) - verzovací systémy: uložení verzí během hledání a po opravení chyb - (speciální) překladač: úroveň s nejvíce hlášeními, varování = chyba, standardní nastavení pro všechny členy týmu (vyhnutí se problémům při integraci) - kontroly syntaxe (a sémantiky): nejtypičtější chyby - překlepy, neinicializované proměnné, chybějící bloky (Běžící) Program - preprocesor: ladicích informace a části kódu - ladicí programy: odhalení (logických, běhových) chyb - profilovací a trasovací programy (pro ladění výkonu): pro skryté vady nebránící běhu programu, ale část programu je zřetelně pomalejší než by měla být - testovací systémy: pro tvorbu modelových případů, testovací frameworky pro jazyky (CUnit, CppUnit, Juint, aj.) - integrované vývojové prostředí (IDE): vše v jednom Ladicí programy (debuggery) - dnes umožňují hodně (překračují původní účel), ale nenechat se svést (nemyslet při programování a spoléhat se na nástroje, argument odpůrců, ale zneužít se dá všechno) a používat rozum, nenahradí mozek! - spolupráce s editorem zdrojového kódu: zobrazování aktuální části kódu - body přerušení (breakpoints): na řádku, proceduře, adrese, při podmínce, n-té iteraci cyklu, změně proměnné - krokování (tracking): po řádcích, s přeskakováním a procházením do procedur - historie volání (backtracking): průzkum programového zásobníku, pro nalezení chybného stavu - protokolování příkazů jazyka: místo výpisů - prozkoumání dat (inspect): včetně strukturovaných, dynamicky alokovaných, uživatelských, dotazy - změny hodnot proměnných: za běhu - pohled na kód generovaný překladačem (assembler), paměť a programový zásobník, registry procesoru apod. - podpora více jazyků a architektur: debugování pro jinou architekturu - debugování více vláken a procesů programu zároveň: změna časování - úpravy systémových zdrojů přidělených programu: ovlivňování množství paměti, práv k souborům apod. - debugování již běžícího a ukončeného programu (z paměti programu): debugování chyby vyskytující se až po nějaké době běhu - pamatování nastavení pro další sezení Ladění intelektuálně náročné (psychologie): potřeba přepnout z kreativního myšlení (programování) na rigorózní kritické, boj s familiérností, egem (říká, že kód je bez chyby) a slepotou při ladění (viděním věcí jak bych chtěl) - podobná jména proměnných a procedur (psychologická vzdálenost), "jasné" podmínky, bloky, kde nejsou - v dobrém stylu to vynikne "Nikdy nelaďte programy vestoje." - Gerald M. Weinberg, autor 'The Psychology of Computer Programming', knihy o psychologii vývoje SW, SW konzultant