Hoofdstuk 12 · oefening
Fouten als onderdeel van programmaontwerp
Gooi een bruikbare ingebouwde fout op
Hoofdstuk 5 gebruikte raise ValueError om een vraag zonder antwoord te weigeren en ging verder. Dit hoofdstuk neemt fouten serieus, omdat ze in een programma van samenwerkende objecten geen toevalligheden zijn. Ze maken deel uit van de interface.
Elke methode die je schrijft doet twee beloften: wat die teruggeeft als alles goed gaat en wat die doet als dat niet zo is. De tweede was tot nu toe impliciet.
Weigeren is niet falen
Er zijn drie duidelijke antwoorden die een methode kan geven als die niet kan doen wat er is gevraagd:
Een onjuist antwoord is geen fout in het programma. Het is een gewone uitkomst met een gewone waarde en 0 teruggeven is juist.
Vergelijk dat met het opvragen van een vraag die niet bestaat. Geen enkel getal betekent ‘die vraag bestond niet’. Er een verzinnen is hoe programma’s uiteindelijk een score van -1 aan een cursist melden.
De drie antwoorden:
| Situatie | Antwoord |
|---|---|
| Een gewone uitkomst, ook een ongewenste | geef een waarde terug |
| Afwezigheid wordt verwacht en de aanroeper zal controleren | geef None terug |
| De aanroeper vroeg om iets onmogelijks | gooi een exceptie op |
Het onderscheid is of de aanroeper dit redelijkerwijs had kunnen verwachten. Een lege QuestionBank kan een normale tussenstand zijn terwijl vragen worden samengesteld. Of een uitvoerbare Quiz leeg mag zijn is een afzonderlijke aanmaakafspraak, die in het hoofdstukproject expliciet wordt gemaakt. Een quizindex van 99 in een bank met twee vragen is een fout in de aanroepende code en die verbergen helpt niemand.
Als raise wordt uitgevoerd, stopt de functie onmiddellijk en wordt de exceptie doorgegeven aan de aanroeper totdat een passende grens die afhandelt. Daardoor is de validatievolgorde deel van correctheid: doe controles die kunnen mislukken voordat je toevoegt of toestand verandert, zodat een geweigerde bewerking het object laat zoals het was.
Het type kiezen
Pythons ingebouwde excepties zijn geen versiering. Elke exceptie vertelt de aanroeper iets anders en de juiste kiezen is het grootste deel van het werk:
| Gooi op | Wanneer |
|---|---|
ValueError | Het juiste soort ding, maar een onbruikbare waarde. Een lege vraagtekst, een negatief puntentotaal. |
TypeError | Het verkeerde soort ding. Een lijst waar een vraag werd verwacht. |
KeyError | Een ontbrekende sleutel in iets dat met sleutels werkt. |
IndexError | Een positie buiten een reeks. |
LookupError | Een van de laatste twee, als je niet wilt beloven welke. |
quiz.question_at(9)
De aanroeper kent nu het verschil tussen ‘je gaf het verkeerde soort ding door’ en ‘je vroeg om een positie die er niet is’ en kan ze desgewenst verschillend afhandelen. De exacte typecontrole is bewust: True en False gedragen zich in sommige Pythonbewerkingen als de gehele getallen 1 en 0, maar het zijn keuzes, geen vraagposities, dus deze API weigert ze.
quiz.question_at("first") wordt aangeroepen met een string. Welke fout past?
Berichten die het zoeken beëindigen
Een bericht wordt gelezen door iemand die je code niet ziet, in een traceback, mogelijk met haast. Het hoort te beantwoorden: wat werd verwacht, wat kwam binnen en waar?
raise IndexError("bad index")
raise IndexError(f"no question at {index}; this quiz has {len(self._questions)}")
Het tweede bericht beëindigt het onderzoek. Het eerste begint het.
Neem de ongeldige waarde op. Dat kost één f-string en is het nuttigste gegeven dat een bericht kan bevatten.
Wat je niet opgooit
Exception zelf. raise Exception("something went wrong") geeft de aanroeper niets om specifiek op te vangen, dus de enige opties zijn alles of niets opvangen.
assert voor invoercontrole. Asserties zijn voor dingen waarvan je denkt dat ze niet kunnen gebeuren en Python kan worden uitgevoerd met asserties uitgeschakeld. Een regel over de invoer van een aanroeper is geen aanname; het is een belofte en beloften hebben raise nodig.
De oefening geeft één klasse drie methoden die nu onzin accepteren.
Opdracht
QuestionBank accepteert nu onzin en geeft onzin terug. Geef elke methode de juiste weigering.
question_at(index) gooit TypeError op als het type van de index niet exact int is, ook bij True en False, en IndexError als de index buiten de bank ligt. Het TypeError-bericht noemt het ongeldige type (zoals str of bool); het IndexError-bericht noemt de ongeldige index.
add(prompt, answer, points) gooit ValueError op bij een lege vraagtekst of een vraagtekst met alleen witruimte, of bij punten onder nul, waarbij het bericht de negatieve punten noemt. Valideer voordat je toevoegt, zodat een weigering de bank niet verandert. Nul punten is hier toegestaan: een onbeoordeelde enquêtevraag bestaat echt.
points_for(index, response) geeft de punten terug voor een juist antwoord en 0 voor een onjuist antwoord. Een onjuist antwoord is een gewone uitkomst, geen fout in het programma, dus daarvoor wordt niets opgegooid.
find(prompt) geeft de overeenkomende vraag terug, of None als er geen is. Afwezigheid wordt hier verwacht en de aanroeper zal controleren.
Drie verschillende antwoorden op ‘ik kan je niet geven wat je vroeg’, en daartussen kiezen is de oefening.