Gå til indholdet

Fejlhåndtering

Fejl er en uundgåelig del af programmering. At lære at forstå, forebygge og håndtere fejl er en kritisk færdighed for enhver udvikler. I dette afsnit ser vi på de forskellige typer fejl og hvordan man håndterer dem.

Det er vigtigt at huske, at fejl ikke betyder, at du er dårlig til at programmere. Fejlmeddelelser, mærkelige resultater og programmer, der ikke opfører sig som forventet, er en helt normal del af arbejdet. Det vigtige er, at du lærer at bruge fejl som information.

Typer af fejl

Syntaksfejl

Syntaksfejl opstår, når koden ikke følger sprogets grammatiske regler. Computeren kan ikke forstå koden og vil ikke køre programmet.

# Syntaksfejl: manglende kolon
if alder > 18
    print("Voksen")

# Syntaksfejl: forkert indrykning
def min_funktion():
print("Hej")  # Skal være indrykket

Syntaksfejl opdages normalt af din IDE eller compiler, før programmet overhovedet kan køres.

Eksempel på fejlmeddelelse i konsol

Runtime-fejl (Køretidsfejl)

Runtime-fejl opstår, mens programmet kører. Koden er syntaktisk korrekt, men der sker noget uventet under udførelsen.

# Division med nul
tal = 10 / 0  # ZeroDivisionError

# Adgang til ikke-eksisterende indeks
liste = [1, 2, 3]
print(liste[10])  # IndexError

# Brug af udefineret variabel
print(ukendt_variabel)  # NameError

Logiske fejl

Logiske fejl er de sværeste at finde. Programmet kører uden at crashe, men giver forkerte resultater.

# Logisk fejl: forkert formel for gennemsnit
def gennemsnit(tal_liste):
    total = sum(tal_liste)
    return total / len(tal_liste) - 1  # Fejl: trækker 1 fra

# Programmet kører, men resultatet er forkert
print(gennemsnit([10, 20, 30]))  # Giver 19 i stedet for 20

Et andet typisk eksempel er en betingelse, der næsten er rigtig, men ikke helt:

alder = 18

if alder > 18:
    print("Du må stemme")

Her kører programmet uden fejl, men logikken er forkert. En 18-årig får ikke lov i programmet, selv om det var meningen.

Undtagelseshåndtering

De fleste moderne programmeringssprog har indbygget understøttelse for at fange og håndtere fejl, så programmet ikke nødvendigvis crasher.

try-except (Python)

try:
    tal = int(input("Indtast et tal: "))
    resultat = 100 / tal
    print(f"100 divideret med {tal} er {resultat}")
except ValueError:
    print("Det var ikke et gyldigt tal!")
except ZeroDivisionError:
    print("Du kan ikke dividere med nul!")
except Exception as e:
    print(f"Der skete en uventet fejl: {e}")
finally:
    print("Dette kører altid, uanset hvad der sker")

try-catch (C#)

try
{
    Console.Write("Indtast et tal: ");
    int tal = int.Parse(Console.ReadLine());
    int resultat = 100 / tal;
    Console.WriteLine($"100 divideret med {tal} er {resultat}");
}
catch (FormatException)
{
    Console.WriteLine("Det var ikke et gyldigt tal!");
}
catch (DivideByZeroException)
{
    Console.WriteLine("Du kan ikke dividere med nul!");
}
finally
{
    Console.WriteLine("Dette kører altid");
}

Debugging

Debugging er processen med at finde og rette fejl i din kode. Der er flere teknikker og værktøjer til dette.

Når et program fejler, er det en god idé at læse fejlmeddelelsen roligt og prøve at forstå, hvad den faktisk siger. Ofte peger den direkte på den linje eller funktion, hvor problemet opstod.

Den simpleste form for debugging er at indsætte print-statements for at se, hvad der sker i programmet.

def beregn_rabat(pris, procent):
    print(f"DEBUG: pris = {pris}, procent = {procent}")

    rabat = pris * procent / 100
    print(f"DEBUG: rabat = {rabat}")

    endelig_pris = pris - rabat
    print(f"DEBUG: endelig_pris = {endelig_pris}")

    return endelig_pris

resultat = beregn_rabat(200, 25)
print(f"Resultat: {resultat}")

Debugger-værktøjer

Moderne IDE’er har indbyggede debuggere, der giver dig mulighed for at:

  • Sætte breakpoints: Stoppe programmet på bestemte linjer
  • Trinvis gennemgang: Køre koden linje for linje
  • Inspicere variabler: Se værdien af variabler på et givet tidspunkt
  • Call stack: Se kæden af funktionskald

En call stack eller stack trace viser typisk den vej, programmet har taget frem til fejlen. Det gør det lettere at forstå, hvilken funktion der kaldte hvilken, og hvor problemet begyndte.

Lær din debugger at kende

Invester tid i at lære din IDE’s debugger. Det vil spare dig enorme mængder tid i det lange løb.

Almindelige fejlfindingsteknikker

  1. Læs fejlmeddelelsen: Fejlmeddelelser fortæller ofte præcis, hvad der er galt og hvor.

  2. Isoler problemet: Kommenter kode ud for at finde den præcise linje, der forårsager fejlen.

  3. Tjek antagelser: Sørg for at dine variabler har de værdier, du forventer.

  4. Rubber duck debugging: Forklar problemet højt til en gummiand eller et andet objekt. Ofte finder du problemet undervejs.

  5. Søg online: Kopier fejlmeddelelsen og søg på Google, Stack Overflow, eller spørg AI.

Defensive programmering

Defensive programmering handler om at skrive kode, der forventer og håndterer potentielle problemer.

Man kan også sige det mere enkelt: skriv kode, som tager højde for, at brugere, input og data ikke altid opfører sig, som du håber.

Inputvalidering

Valider altid brugerinput før du bruger det:

def divider(a, b):
    # Tjek for ugyldigt input
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("Begge argumenter skal være tal")

    if b == 0:
        raise ValueError("Kan ikke dividere med nul")

    return a / b

Assertions

Assertions er tjek, der bekræfter, at visse betingelser er sande. De bruges typisk under udvikling.

def beregn_kvadratrod(tal):
    assert tal >= 0, "Tal skal være positivt"
    return tal ** 0.5

Logging

I stedet for print-statements kan du bruge logging i produktionskode:

import logging

logging.basicConfig(level=logging.DEBUG)

def beregn_noget(x):
    logging.debug(f"Starter beregning med x = {x}")
    resultat = x * 2
    logging.info(f"Beregning færdig: {resultat}")
    return resultat

Best practices

  1. Test tidligt og ofte: Jo tidligere du finder en fejl, jo nemmere er den at rette.

  2. Brug versionsstyring: Git giver dig mulighed for at gå tilbage til en fungerende version.

  3. Skriv læsbar kode: Læsbar kode er nemmere at debugge.

  4. Brug meningsfulde variabelnavne: kunde_alder er bedre end x.

  5. Kommenter kompleks kode: Forklar hvorfor, ikke bare hvad.

  6. Håndter edge cases: Tænk over, hvad der sker med tomme lister, nul-værdier, etc.

  7. Bliv ikke frustreret: Fejl er en naturlig del af programmering. Se dem som læringsmuligheder.

Næste skridt

Når du først har arbejdet med fejl, bliver det naturligt også at tænke på, hvordan kode kan skrives mere læsbart og vedligeholdeligt fra starten. Fortsæt med Kodekvalitet.