Debugging: ako nájsť chybu

Každý programátor robí chyby, začiatočník, či profesionál. Anglické slovo bug znamená chrobák alebo hmyz. Debugging je teda doslova „odchrobáčikovanie“, čiže odstraňovanie chýb z programu.

Najznámejší príbeh o tomto slove sa odohral 9. septembra 1947 na Harvardovej univerzite. Tím, v ktorom pracovala aj slávna programátorka Grace Hopperová, sa trápil s obrovským počítačom Mark II. Ten zaberal celú miestnosť a namiesto čipov mal tisíce elektromechanických relé. Počítač robil chyby a nikto nevedel prečo. Keď ho technici prehľadali, našli v jednom relé zaseknutú skutočnú moľu. Vybrali ju, prilepili lepiacou páskou do denníka prác a pripísali k nej, že ide o prvý prípad, keď sa našiel naozajstný „bug“.

Zaujímavosťou je, že slovo bug pre poruchu používali inžinieri už skôr, napríklad vynálezca Thomas Edison v 19. storočí. Príbeh s moľou je však taký výstižný, že si ho pamätá celý programátorský svet. Stránka denníka s prilepenou moľou sa dodnes zachovala a je uložená v múzeu v americkom Smithsonovom inštitúte.

Prečo je debugging dôležitý?

Niektoré chyby sú ľahké. Python vypíše červenú chybovú hlášku, ukáže číslo riadku a vy hneď viete, kde je problém. Oveľa zákernejšie sú chyby, pri ktorých program beží bez problémov, ale vypíše zlý výsledok. Počítač vás neupozorní, lebo z jeho pohľadu je všetko v poriadku. Robí presne to, čo ste mu napísali, nie to, čo ste chceli.

Takéto chyby môžete hľadať tak, že budete dookola čítať kód a hádať. Alebo použijete debugger, nástroj na krokovanie programu. Debugging vás navyše naučí rozumieť vlastnému kódu, lebo vidíte, ako program beží krok za krokom a pochopíte cykly, funkcie aj premenné.

Krokovanie a breakpoint

Bežne program prebehne celý naraz za zlomok sekundy. Pri debuggovaní ho však môžete spomaliť a vykonávať po jednom riadku. Tomu sa hovorí krokovanie.

Aby ste nemuseli krokovať celý program od začiatku, môžete si na niektorý riadok položiť breakpoint (bod prerušenia). Program sa normálne spustí a na tomto riadku zastaví. Odtiaľ už pokračujete krok po kroku.

Napríklad v editore Thonny spustíte debugovanie tlačidlom s obrázkom chrobáka. Vo Visual Studio Code klávesom F5, breakpoint tam vložíte kliknutím vľavo vedľa čísla riadku.

Čo je watching variables?

Watching variables, po slovensky sledovanie premenných, je najužitočnejšia vec, ktorú vám debugger ponúka. Počas krokovania vidíte v špeciálnom okne, akú hodnotu má práve teraz každá premenná. Po každom kroku sa hodnoty aktualizujú, takže presne vidíte, kedy a ako sa premenná zmenila.

Do sledovania si môžete pridať konkrétne premenné, ktoré vás zaujímajú. Hľadanie chyby potom vyzerá takto: viete, akú hodnotu by premenná mala mať, a sledujete, či ju naozaj má. V momente, keď sa očakávanie a skutočnosť rozídu, ste na stope chyby.

Pri sledovaní premenných si všimnete ešte jednu vec: globálne premenné (vytvorené mimo funkcie) vidíte počas celého behu programu. Lokálne premenné (vytvorené vo funkcii) sa objavia, až keď program vojde do funkcie, a po jej skončení zmiznú.

Úloha: nájdite chybu v programe

Tento program má vypočítať cenu topánok a trička po 10 % zľave. Program beží bez jedinej chybovej hlášky, a predsa v ňom chyba je. Nájdete ju?

# Program vypočíta cenu tovaru po 10 % zľave

def cena_po_zlave(cena, percento):
    zlava = cena_topanok * percento / 100    # lokálna premenná
    nova_cena = cena - zlava                 # lokálna premenná
    return nova_cena

#MAIN BODY
cena_topanok = 50      # globálna premenná
cena_tricka = 20       # globálna premenná

print("Topánky po zľave:", cena_po_zlave(cena_topanok, 10))
print("Tričko po zľave:", cena_po_zlave(cena_tricka, 10))

Ako postupovať:

Skôr ako program spustíte, vypočítajte si na papieri, koľko by mali stáť topánky a koľko tričko po 10 % zľave. Riešenie vám neprezradíme, chybu nájdete sami. Tip: dávajte pozor najmä na to, ktorá premenná sa kde používa.

Doplnok pre náročnejších: priemer známok

Ak ste zľavy zvládli, skúste ťažšiu úlohu. Tento program počíta priemer známok žiaka. Výsledok, ktorý vypíše, vyzerá ako úplne normálny priemer, a práve preto je chyba taká zákerná.

# Program vypočíta priemer známok žiaka

def vypocitaj_priemer(zoznam):
    sucet = 0                     # lokálna premenná
    for i in range(1, len(zoznam)):
        sucet = sucet + zoznam[i]
    priemer = sucet / len(zoznam)  # lokálna premenná
    return priemer

#MAIN BODY
znamky = [1, 2, 1, 3, 2]          # globálna premenná

vysledok = vypocitaj_priemer(znamky)   # globálna premenná
print("Priemer známok je:", vysledok)

Opäť si najprv vypočítajte priemer ručne. Potom položte breakpoint na riadok sucet = 0 a sledujte premenné i, sucet a zoznam[i]. Krokujte cyklus a pri každom prechode si zapisujte, ktorá známka sa práve pripočítala. Koľkokrát cyklus prebehne? A koľko známok má žiak?

Keď program vyjde z funkcie, všimnite si, ktoré premenné v okne zmizli a ktoré zostali. Tak na vlastné oči uvidíte rozdiel medzi lokálnymi a globálnymi premennými.

Vtipná pesnička na záver

10 malých chýb v kóde
10 malých chýb v kóde
1 chyba opravená…program skompilovaný…
11 malých chýb v kóde

Riešenia úloh (rozbaľte, až keď to vzdáte alebo si chcete overiť výsledok)

Riešenie: zľavy

Správne ceny sú: topánky 45 € a tričko 18 €. Program však pre tričko vypíše 15 €.

Chyba je v riadku so zľavou. Namiesto parametra cena sa tam používa globálna premenná cena_topanok. Funkcia preto vždy počíta zľavu z ceny topánok, teda 5 €, nech jej pošleme akýkoľvek tovar.

Opravený riadok:

    zlava = cena * percento / 100

Ponaučenie: Funkcia by mala pracovať len s tým, čo dostane cez parametre. Python vás neupozorní, ak vo funkcii omylom použijete globálnu premennú.

Riešenie: priemer známok

Správny priemer je (1 + 2 + 1 + 3 + 2) / 5 = 9 / 5 = 1,8. Program však vypíše 1,6.

Chyba je v cykle range(1, len(zoznam)). Cyklus začína od indexu 1, ale v zoznamoch má prvý prvok index 0. Prvá známka sa preto vôbec nezapočíta.

Ponaučenie: Indexy v zoznamoch začínajú od nuly. Chyba „o jedna vedľa“ (anglicky off-by-one) patrí medzi najčastejšie chyby vôbec, a to aj u skúsených programátorov.