Fehler als Teil des Programmentwurfs · Abschlussprojekt
Abschlussprojekt: Projekt: Aussagekräftige Quizfehler entwerfen
Die Aussage dieses Kapitels in einem Satz: Ein Fehler gehört zu den Zusagen einer Methode und wird deshalb entworfen statt improvisiert.
Das Projekt führt alle Entscheidungen in einem kleinen Programm zusammen.
Der Entwurf
QuizError the base: anything this program refuses
QuestionNotFound no question at that position
InvalidResponse the answer could not be used
QuizConfigError the quiz itself is malformed
Drei Typen unter einer Basisklasse. Jeder wird an einer anderen Stelle abgefangen. Dieses Kriterium muss ein neuer Ausnahmetyp erfüllen.
Eingebaute Ausnahmen behalten ihre bisherigen Aufgaben. Ein falscher Typ ist ein TypeError. Das ist keine Bequemlichkeit: TypeError wird von allen verstanden, die Python programmieren. Ein privater Name dafür wäre schlechter.
Welche Entscheidungen die Bewertung prüft
| Entscheidung | Wo |
|---|---|
| Ablehnen statt einen Wert erfinden | question_at, respond |
| Eine eingebaute Ausnahme wählen, wenn eine passt | Der TypeError für einen falschen Indextyp |
| Einen Fehler aus einer tieferen Ebene übersetzen | load_points, unter Erhalt der Ursache |
| Werte als Attribute mitgeben | QuestionNotFound, InvalidResponse |
| Nur an der Grenze abfangen | run_quiz, und nirgendwo sonst |
| Nicht verschlucken, was du nicht behandeln kannst | Der Pfad für TypeError |
Question.points_for löst eine Ausnahme aus, Attempt.record ruft es auf, und run_quiz ruft wiederum dieses auf. Warum hat nur run_quiz ein try?
Woran du ein gutes Ergebnis erkennst
Lies dein run_quiz und frage dich, was Lernende bei jeder Art von Fehler sehen:
Eine falsche Antwort bringt null Punkte und wird nicht erwähnt.
Eine leere Antwort wird übersprungen, mit einer Zeile, die die betroffene Frage nennt.
Eine fehlende Frage wird übersprungen, mit einer Zeile, die die Anzahl vorhandener Fragen nennt.
Ein fehlerhaft aufgebautes Quiz beendet den Lauf, weil es nichts gibt, womit er fortgesetzt werden könnte.
Ein Fehler in deinem eigenen Code führt zum Absturz, mit unverändertem Traceback.
Fünf verschiedene Ergebnisse, und keines davon lautet „Das Programm hat eine unbemerkt falsche Zahl ausgegeben“. Genau darum ging es in diesem Kapitel.
Aufgabe
Baue die Familie von Ausnahmen und das Programm, das sie verwendet.
QuizError(Exception) ist die Basisklasse. Darunter liegen QuestionNotFound(index, available) und InvalidResponse(prompt, response), die jeweils ihre Werte als Attribute festhalten und mit super().__init__ eine Meldung nach oben weiterreichen, sowie QuizConfigError, das keine Attribute benötigt.
Quiz.__init__ löst QuizConfigError aus, wenn keine Fragen übergeben werden: Ein Quiz ohne Inhalt lässt sich nicht durchführen und sollte deshalb gar nicht erst erstellt werden.
Quiz.question_at(index) löst TypeError aus, sofern der Typ des Index nicht exakt int ist, also auch bei True und False, und QuestionNotFound, wenn er außerhalb des Quiz liegt.
Quiz.points_for(index, response) löst bei einer leeren Antwort InvalidResponse aus, vergleicht die Antwort nach dem Entfernen umgebenden Leerraums und der Umwandlung in Kleinbuchstaben und gibt bei einer richtigen Antwort die Punkte zurück, bei einer falschen 0. Eine falsche Antwort ist kein Fehler.
load_points(raw) übersetzt den Fehler von int() in QuizError und erhält das Original als Ursache.
run_quiz(quiz, answers, log) ist die einzige Stelle, die Ausnahmen abfängt. Die Funktion gibt den Punktestand zurück. Jeder QuizError wird übersprungen und als "<TypeName>: <message>" aufgezeichnet. Ein TypeError wird genauso aufgezeichnet und dann erneut ausgelöst, denn ein Fehler im aufrufenden Code ist keine Ablehnung. Unabhängig vom Ergebnis hängt die Funktion mithilfe von finally zuletzt "answered N" an. Dabei zählt N die untersuchten Antwortpaare, einschließlich falscher Antworten, übersprungener Ablehnungen und des Paars, das einen TypeError auslöst. Paare nach diesem Fehler werden weder untersucht noch gezählt. Bei einer leeren Antwortsequenz wird "answered 0" aufgezeichnet.