Fehler als Teil des Programmentwurfs · Übung
Fehler an der Grenze zur Benutzerschnittstelle abfangen
Wenn unterwegs keine Methode eine Ausnahme abfangen sollte, muss es irgendwann jemand tun. Diese Lektion beantwortet die Frage, wer.
Die Regel
Fange dort ab, wo du etwas dagegen tun kannst.
Das ist nicht der Ort, an dem der Fehler aufgetreten ist, und meistens auch nicht in seiner Nähe. Es ist der Ort, an dem das Programm weiß, was der Fehler für die Person bedeutet, die es benutzt. In einem Terminalprogramm ist das der Programmeinstieg. Auf einer Webseite ist es die Verarbeitung der Anfrage. In einem Test ist es der Testrunner.
Diese Orte haben einen Namen: die Grenze. Innerhalb liegt deine Domäne, die berechnet und ablehnt. Außerhalb steht eine Person, die einen verständlichen Satz statt eines Tracebacks braucht.

Die unbrauchbare Antwort führte zu einer lesbaren Zeile, und das Quiz lief weiter. Question weiß weiterhin nichts von Menschen, Ausgaben oder dem Fortsetzen des Ablaufs und musste nicht geändert werden.
Gezielt abfangen
Zwei Regeln halten die Fehlerbehandlung verlässlich.
Fange den engsten Typ ab, den du tatsächlich behandeln kannst. except TypeError sagt, dass du über TypeError nachgedacht hast. except Exception sagt, dass du aufgegeben hast. Es verschluckt auch den AttributeError, der durch deinen eigenen Tippfehler entsteht.
Schreibe nie ein bloßes except: ohne Angabe dahinter. Es fängt auch KeyboardInterrupt ab und kann damit die übliche Tastatureingabe zum Beenden des Programms verschlucken.
An einer Stelle ist breites Abfangen richtig: in der äußersten Fehlerbehandlung eines dauerhaft laufenden Programms, die alles Unerwartete protokollieren und den Betrieb aufrechterhalten soll. Selbst dort wird erneut ausgelöst oder protokolliert, niemals stillschweigend weitergemacht.
Ein Quiz.score() ruft Question.points_for auf, das TypeError auslösen kann. Wohin gehört das try?
Verschlucken
Die Gefahr beim Abfangen ist nicht, zu wenig abzufangen. Sie ist dies:
try:
score = score + question.points_for(response)
except Exception:
pass
Das Programm läuft mit einem unbemerkt falschen Punktestand weiter, und jede Spur der Ursache ist verschwunden. Das ist schlimmer als ein Absturz, denn ein Absturz wird behoben.
Wenn du etwas abfängst, tue mindestens eines davon: Informiere jemanden, protokolliere es, verwende eine begründbare Ersatzlösung oder löse erneut aus. pass ist nur vertretbar, wenn die Ausnahme tatsächlich „Hier ist nichts zu tun“ bedeutet. Dann verdient sie einen Kommentar, der das erklärt.
Mit finally aufräumen
Manchmal muss etwas geschehen, unabhängig davon, ob ein Fehler aufgetreten ist:
scored_run(questions, ["def", 42])
Der finally-Block wurde beide Male ausgeführt, auch beim Verlassen des fehlgeschlagenen Ablaufs. Genau dafür ist er da: etwas freigeben, etwas schließen oder unabhängig vom Ergebnis festhalten, dass ein Versuch stattgefunden hat.
finally behandelt den Fehler nicht. Die Ausnahme wandert danach weiter, was meistens richtig ist: Aufräumen und entscheiden, was ein Fehler bedeutet, sind unterschiedliche Aufgaben.
Die Übung legt die Grenze an einer einzigen Stelle fest und lässt die Domäne unverändert.
Aufgabe
Platziere die Fehlerbehandlung an der Grenze und nirgendwo sonst.
run_quiz(questions, responses) ist die Grenze. Die Funktion gibt ein Paar (score, problems) zurück. Dabei ist problems eine Liste von Strings im Format "<prompt>: <message>", einer für jede übersprungene Antwort. Eine unbrauchbare Antwort wird übersprungen, und das Quiz geht weiter.
Fange gezielt TypeError und ValueError ab. Fange nicht Exception ab: Ein Tippfehler in deinem eigenen Code soll weiterhin deutlich zum Absturz führen und nicht als schlechte Antwort eines Lernenden gemeldet werden.
Question.points_for und Attempt.record bleiben exakt unverändert. Keine der beiden Methoden kann entscheiden, was ein Fehler bedeutet. Deshalb darf keine ein try erhalten.
Das vorgegebene audited_run zeigt die separate Rolle von finally: Es zeichnet auf, wie viele Antworten untersucht wurden, unabhängig davon, ob der Lauf zurückkehrt oder ein unerwarteter Fehler nach außen weiterwandert. Lies es und führe es aus, konzentriere deine Implementierung aber auf run_quiz.