0%

Wenn die Außenwelt Probleme macht · Übung

Dort abfangen, wo der Fehler eine Bedeutung hat

Der inklusive Filter ist korrigiert. Der Befehl hat noch eine zu weit gefasste Grenze aus Kapitel 8:

try:
    document = convert_catalog(input_path, output_path, minimum_quantity)
except (FileNotFoundError, UnicodeDecodeError, csv.Error, ValueError) as exc:
    ...

Dieses Tupel nennt nur erwartete Typen, doch der try-Block umfasst Lesen, Auswählen, Validieren und Schreiben. Der Ort ist genauso wichtig wie die Klasse. Ein ValueError beim Einlesen einer beschädigten CSV-Zeile bedeutet eine erwartete ungültige externe Eingabe. Ein ValueError aus der Validierung der Schreibfunktion bedeutet, dass Projektcode seiner eigenen Schreibfunktion ein ungültiges internes Dokument übergeben hat. Beide gleich zu behandeln würde diesen Unterschied auslöschen.

Eine Grenze um eine verstandene Operation ziehen

Erstelle run_catalog(input_path, output_path, minimum_quantity). Seine erste Grenze umschließt nur das Lesen:

try:
    items = read_catalog_csv(input_path)
except (OSError, UnicodeDecodeError, csv.Error, ValueError) as exc:
    print(f"Could not read {input_path}: {exc}", file=sys.stderr)
    return 1

OSError ist die Familie der Betriebssystemfehler, zu der fehlende Pfade, Berechtigungsfehler und andere fehlgeschlagene Dateioperationen gehören. Hier hat sie eine Bedeutung: Die angeforderte Eingabe konnte nicht geöffnet oder gelesen werden. UnicodeDecodeError, csv.Error und ValueError bedeuten, dass aus den Bytes, der CSV-Syntax oder dem festgelegten Schema kein Katalog werden konnte.

Die Meldung enthält den genauen Eingabepfad und die begrenzte Ursache. stdout bleibt leer. Besonders wichtig: Die Schreibfunktion ist noch nicht gelaufen, deshalb bleibt eine bereits vorhandene Ausgabedatei Byte für Byte unverändert.

Führe die Auswahl nach dieser Fehlerbehandlung außerhalb jedes try aus:

selected = select_items(items, minimum_quantity)

Wenn die Auswahl KeyError, TypeError, ValueError, RuntimeError oder sogar OSError auslöst, gelangt die ursprüngliche Ausnahme nach außen. Die Auswahl greift nicht auf die Außenwelt zu. Ihren Fehler als Lese- oder Schreibfehler zu bezeichnen wäre daher falsch.

Der Ausgabeoperation eine eigene Grenze geben

Umschließe nur die Schreibfunktion:

try:
    document = write_catalog_json(selected, output_path)
except OSError as exc:
    print(f"Could not write {output_path}: {exc}", file=sys.stderr)
    return 1

Hier wird nur ein Schreibfehler des Betriebssystems erwartet. Ein ValueError oder TypeError aus der Validierung verletzt eine interne Invariante und muss unverändert nach außen gelangen.

Gib die Erfolgszeile erst aus, nachdem write_catalog_json zurückgekehrt ist. „Wrote N items“ ist eine Aussage über einen abgeschlossenen Vorgang. Eine frühere Ausgabe wäre eine als Erfolg getarnte Falschaussage.

Die tatsächliche Grenze für die Ergebnisdatei

Dieses Projekt schreibt direkt an den angeforderten Pfad. Scheitert das Betriebssystem vor dem Öffnen, kann eine alte Datei unverändert bleiben. Passiert der Fehler nach dem Leeren oder nachdem einige Bytes geschrieben wurden, kann die Datei fehlen oder unvollständig sein. Dieses Kapitel verspricht keine Rückabwicklung, die es nicht implementiert hat.

Es gibt auch keinen automatischen Wiederholungsversuch. Einen fehlenden Pfad oder einen Berechtigungsfehler ohne Änderung erneut zu versuchen wiederholt nur denselben Fall. Die Diagnose liefert dem Aufrufer Hinweise; sie erfindet keine Wiederherstellungsstrategie.

Aktualisiere main(argv=None) so, dass argparse weiterhin für Hilfe und Grammatikfehler zuständig ist. Gib dann seine drei eingelesenen Werte an run_catalog weiter. Der vollständige Vertrag lautet:

SituationstdoutstderrStatus
ErfolgWrote N items to PATH.leer0
Lese-/EingabefehlerleerCould not read INPUT: cause1
OSError bei der AusgabeleerCould not write OUTPUT: cause1
ParserfehlerleerAufrufhinweis und Fehler von argparse2
unerwarteter Defektkeine Zusageursprünglicher Tracebackungleich null

Implementiere die beiden Grenzen, die jeweils eine Operation umfassen.

Ob du es richtig gemacht hast, zeigt sich nicht daran, dass das Programm nicht mehr abstürzt. Entscheidend ist, ob dieselbe Ausnahmeklasse je nach Herkunft unterschiedlich behandelt wird. Ein ValueError aus der Lesefunktion sollte eine kurze Meldung erzeugen; ein ValueError aus der eigenen Validierung der Schreibfunktion weiterhin einen Traceback. Wenn ein breites try beide abdeckt, kannst du sie nicht unterscheiden. Die Person, die dein Programm ausführt, kann es dann ebenfalls nicht.

Ein ValueError erreicht deinen Code. Seine Herkunft entscheidet, was zu tun ist. Warum?

Warum läuft select_items außerhalb jedes try-Blocks?

Aufgabe

Strukturiere die CLI-Steuerung in run_catalog(input_path, output_path, minimum_quantity) um.

  • Fange OSError, UnicodeDecodeError, csv.Error und ValueError nur um read_catalog_csv herum ab. Gib dann Could not read INPUT_PATH: CAUSE auf stderr aus und gib 1 zurück.

  • Führe select_items außerhalb jedes try-Blocks aus.

  • Fange nur OSError um write_catalog_json herum ab. Gib dann Could not write OUTPUT_PATH: CAUSE auf stderr aus und gib 1 zurück.

  • Gib nach erfolgreichem Schreiben Wrote N items to OUTPUT_PATH. auf stdout aus und gib 0 zurück.

  • Lass main(argv=None) Argumente einlesen und run_catalog(...) zurückgeben.

Behalte bei argparse Hilfe mit Status 0 und Grammatikfehler mit Status 2 bei. Lass Auswahlfehler und Validierungsfehler der Schreibfunktion unverändert nach außen gelangen. Ergänze weder Wiederholungsversuche noch Zusagen über Rückabwicklung.