Håndtere krasj

Krasj av programmet en bra ting, fordi vi ønsker å vite at noe er feil så fort som mulig. Men i noen tilfeller ønsker vi at programmet skal håndtere krasjen selv; dette gjelder egentlig bare når vi vet på forhånd hva slag krasj som kan oppstå, og er ikke et lurt triks å bruke dette for å skyve problemer under teppet.

Generelt vil jeg anbefale å være sparsom med bruken av try og except; hvis det kan håndteres uten på en enkel og grei måte, er det som oftest å foretrekke. Kode som er basert på mye try og except i kontrollflyten er litt mer utfordrende å feilsøke. Samtidig, dersom å bruke try/except sparer mye omstendelig kode kan det være å foretrekke likevel.



Krasjhåndtering med try/except
# Håndtere en krasj
#  -- Prøv å gi programmet noe som ikke er et tall
#  -- Prøv å gi programmet et tall som er for stort
# Se: programmet krasjer ikke selv om brukeren gir dårlig input svar!

animals = ["katt", "hund", "kanin", "hamster", "krokodille"]
user_input = input(f"Velg ett tall [0-{len(animals) - 1}]:")

try:
    i = int(user_input)
    animal = animals[i]
except:
    # Kjøres dersom try-blokken krasjet
    print("Ugyldig valg!")
else:
    # Kjøres dersom try-blokken gikk bra
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

Bruk krasjhåndtering (try/except) med varsomhet. Å bruke mye krasjhåndetring kan gjøre koden din litt vanskeligere å feilsøke.

# FARE!! bruk av except: håndterer for mange krasjer --> vanskelig å feilsøke!
#  -- Prøv nå å gi programmet en GYLDIG tall som input
# Se: programmet krasjer ikke, men gjør i stedet en logisk feil! FYFYFY!

animals = ["katt", "hund", "kanin", "hamster", "krokodille"]
user_input = input(f"Velg ett tall [0-{len(animals) - 1}]:")

try:
    i = int(user_input)
    animal = animal[i] # Skrivefeil (s mangler)! Vi VIL krasje her med NameError!
except:
    print("Ugyldig valg!") # Oops! Kommer hit selv om input er gyldig
else:
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

Man bør alltid spesifisere hvilken type krasj man håndterer.

# Spesifiser hvilken type krasj du håndterer
# 
#  -- Prøv å gi programmet en GYLDIG tall som input (se: krasjer)
#  -- Fiks kodefeilen (skrivefeilen) ved å rette koden der det krasjer
#  -- Prøv nå å gi programmet et tall som er for stort
#  -- Prøv nå å gi programmet noe som ikke er et tall

animals = ["katt", "hund", "kanin", "hamster", "krokodille"]
user_input = input(f"Velg ett tall [0-{len(animals) - 1}]:")

try:
    i = int(user_input)
    animal = animal[i] # Skrivefeil (s mangler) -- men nå krasjer vi! YAY!
except ValueError:
    print("Ugyldig valg, du må oppgi et tall!")
except IndexError:
    print("Tallet du oppgav er ugyldig!")
else:
    print("Gratulerer, du fikk en ny", animal)

print("Nå er programmet ferdig")

# NameError blir ikke fanget av except nå,
# så vi oppdager det nå hvis variabeler har feil navn.
Stilguide for krasjhåndtering
  • Bruk krasjhåndtering for å håndtere andre sine feil; ikke for å skjule egne feil i koden.
    • Eksempler på andre sine feil: en bruker skriver inn ugyldig input, filen du prøver å lese fra finnes ikke eller har feil innhold, nettverket er nede, etc.
    • Eksempler på egne feil: du har skrevet feil variabelnavn, du har gitt feil argumenter til en funksjon, du ufører en beregning på gal måte etc.
  • Hvis du enkelt kan løse problemet uten try/except, er det ofte en bedre løsning.
    • For eksempel: benytt if-setninger for å håndtere hjørnetilfeller som er enkle å sjekke.
  • Ha minst mulig kode i try-blokken.
    • Flytt så mye kode som mulig utenfor try-blokken: enten før try-blokken begynner eller inn i else-blokken.
  • Alltid angi hvilken type feil du håndterer.
Krasje på egen hånd

Kodeordet raise brukes for å krasje på egen hånd. Dette kan brukes for å krasje med en feilmelding som gir mer detaljert informasjon enn ellers, eller for å krasje programmet så tidlig som mulig dersom noe er galt.

Å krasje med raise gir noenlunde samme funksjonalitet som å skrive assert False (se kursnotater om feil og debugging). Forskjellen ligger primært i at du kan lage flere ulike typer feil med raise. En krasj forårsaket av assert False vil alltid krasje med typen AssertionError.

# Krasj programmet dersom input ikke er gyldig
food = input()
if food not in ["salat", "tomat", "agurk", "paprika"]:
    raise ValueError(f"Maten '{food}' er ikke akseptabel.")

print("Takk for maten!")
# Krasj programmet dersom argumenter ikke er gyldige

def process_payment(amount, card_number):
    if amount <= 0:
        raise ValueError("Cannot process payment for amount <= 0")
    if len(card_number) != 16:
        raise ValueError("Card number must be 16 digits long")

    ... # do the actual payment processing here
    print("Payment processed successfully")

process_payment(-40, "1234567890123456")

Kan brukes for å gi ekstra informasjon ved krasj

def foo(i, j):
    y = j
    for x in range(i, j):
        try:
            y += abs(y/x)
        except ZeroDivisionError as err: # err variabel som 'husker' krasjen
            # Skriver ut debug-informasjon
            print("Divisjon med 0")
            print("Lokale variabler: ", locals())
            raise err # Kaster samme krasj på nytt
    return x

print(foo(-5, 10))