Exception Handling: Aufwand gegen Nutzen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Hallo zusammen!
Ich arbeite seit einiger Zeit an einem GUI Tool, welches Datenbanken von einem in ein anderes Format konvertiert, Teildatenbanken zusammenfügt, Backups erstellt etc.

Bisher habe ich eher unwahrscheinliche Exceptions rudimentär mit einer einfachen print Routine behandelt, etwa so:

Code: Alles auswählen

try:
	os.rename(source, destination)
	move_index +=1
except:
	print("Error while moving database files.")
	write_result("FAILED", "red")
Das ist nicht wirklich zufriedenstellend, also habe ich es folgendermaßen verbessert:

Code: Alles auswählen

try:
	os.rename(source, destination)
	move_index += 1
except OSError as os_error:
	write_result("FAILED", 'red')
	write_message("\nSTOPPED", 'black')
	error_disp(1, "Unexpected error",
       		   "An unexpected error occured while\n"
       		   "trying to move database files!\n"
             	+ str(os_error)
             	+ "\n\nProcessing has been STOPPED!"
	)
Nun wird jedes mal eine Fehlermeldung in einer Art Terminal-Fenster angezeigt und ein tk Fenster mit einer entsprechenden Fehlermeldung erzeugt. Das Problem: ich habe sehr viele dieser try:... except:... Blöcke und deren Eintreten ist sehr unwahrscheinlich. Im Grunde tritt ein Fehler nur auf, wenn ein erzeugtes Verzeichnis "plötzlich" verschwindet oder die Zugriffsberechtigung ändert sich, aus welchen Gründen auch immer. Mein Programm erstellt solche Verzeichnisse, sofern nicht schon vorhanden, löscht sie aber niemals! Also eine "Race Condition" kann innerhalb meines Programms nicht auftreten.

Die Frage ist nun: sollte man wirklich dermaßen unwahrscheinliche Fehler-Ereignisse abfangen oder ist das übertrieben? Denn schließlich kostet es aufgrund der Anzahl solcher I/O Operationen (Kopieren, Löschen, Anlegen, Verschieben ...) in meinem Programm eine Menge Tipp-Arbeit und bläht den Quellcode reichlich auf.
Zuletzt geändert von Anonymous am Samstag 15. Juli 2017, 21:07, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Grendel: Was ich mich grundsätzlich Frage ist ob Du tatsächlich nur ein `print()` oder etwas äquivalentes machst und dann einfach so weiter machst als wäre das gerade nicht passiert. Wenn das umbenennen nicht geklappt hat und man einfach weiter macht als hätte es geklappt — entstehen dadurch nicht Folgefehler?

Ansonsten könntest Du doch auch einfach auf der höchsten Ebene die Ausnahmen behandeln.

Egal wo: Wenn die Ausnahmen letztlich ”verschluckt” werden, sollte man sie nicht nur generisch ausgeben sondern mindestens in einer Datei protokollieren, und zwar komplett mit Traceback. Sonst kann die Fehlersuche bei so einem Programm echt schwierig werden.
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Hallo,
die print Statements waren lediglich "Platzhalter", es war von vorn herein klar, dass ich sie ersetzen werde. Letztlich handelt es sich um eine Art doppelten Boden, für den unwahrscheinlichen Fall, dass eben doch plötzlich ein Verzeichnis nicht mehr an der erwarteten Stelle ist oder das Anlegen eines Verzeichnisses fehl schlägt. Die Fehler sind klassifiziert. Sollte ein Fehler der Klasse 1 auftreten (wie im genannten Code-Beispiel), so hält das Programm und alle folgenden Aktionen werden nicht mehr ausgeführt. Da das Programm mit Datenbanken hantiert, habe ich an allen Ecken und Enden solche Sicherheiten eingebaut.

Mir geht es nun grundsätzlich um die Frage, welchen Aufwand man treiben sollte. Macht es Sinn, auch sehr unwahrscheinliche Fehler abzufangen?

PS: wie bekommt man den Code so ansehnlich hin?
PPS: generell mal vielen Dank an dich! Offensichtlich bist du mit Abstand am aktivsten hier.
BlackJack

@Grendel: Die Frage ob das Sinn macht kannst Du letztlich nur selbst beantworten. Es *kann* Sinn machen *jede* Ausnahme zu behandeln, allerdings muss das ja nicht an jeder Stelle passieren wo sie auftreten können. Das ist ja das schöne an Ausnahmen, das sie bis zum obersten Aufruf durchschlagen wenn man nichts macht und *dort* kann man sie dann behandeln in dem man sie beispielsweise protokolliert, um die Fehlersuche zu ermöglichen.

Ob das behandeln einer Ausnahme an einer Stelle Sinn macht, musst Du für jeden Einzelfall entscheiden: Was passiert wenn die Ausnahme nicht behandelt wird? Und: Kann man sie überhaupt sinnvoll behandeln, und wenn ja wie und wo?

Beim Beispiel hoffe ich ganz stark das die 1 im `error_disp()`-Aufruf nicht dort irgendwo für einen Abbruch sorgt. Das wäre schwer nachvollziehbar.

Bei Datenbanken braucht man doch im Grunde auch nicht bei jeder Operation eine Ausnahmebehandlung, sondern nur dort wo man sinnvoll etwas machen kann. Wenn das nirgends ist, dann eine Behandlung recht weit ”oben”, die über `commit()` oder `rollback()` entscheidet. Das ist ja eine schöne Eigenschaft von Datenbanken, das man erst einmal loslegen kann und wenn es Probleme gibt, den Zustand zurücksetzen kann und sich damit weniger Detailgedanken um Aufräumaktionen machen muss.

Du könntest auch eigene Ausnahmen einführen und Behandlungen die so ähnlich aussehen wie das was Du da zeigst vereinfachen in dem dort nur die eigene Ausnahme ausgelöst wird, mit der spezifischen Information, und dann das protokollieren/anzeigen weiter oben in der Aufrufhierarchie nur einmal schreiben.
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Okay, danke erstmal. Die "1" im Funktionsaufruf entspricht der genannten Fehlerklasse. Sie bestimmt, was in einem Fehlerfall passieren soll. In diesem Fall hält das Programm an, erzeugt eine tkinter Messagebox mit Fehlerbeschreibung und eine Text-Ausgabe in einem Protokoll-Fenster, in welchem alle Aktionen des Programms protokolliert werden. Es gibt auch ein Log-File, in das wird jedoch nur hinein geschrieben, wenn die Datenbank fehlerhaft ist. Es handelt sich übrigens nicht um eine SQL Datenbank.

Der Hintergrund meiner vielen Ausnahmebehandlungen ist der, dass ich Programmabstürze auf jeden Fall vermeiden will. Für die Windows Version werde ich ein Excecutable bauen, und das Ding soll "rock solid" laufen. Aber der Aufwand ist eben recht hoch.
BlackJack

@Grendel: Wie gesagt halte ich das mit der 1 für zu magisch und zu undurchsichtig. Da würde ich *mindestens* eine Konstante für definieren an der man deutlich ablesen kann das dadurch das Programm angehalten wird. Falls es eine GUI-Anwendung ist verhindert die Tk-Hauptschleife ja schon einen Programmabsturz. Ansonsten würde ich etwas Äquivalentes einbauen.

Und ich stelle (mir) immer noch die Frage warum man solche Ausnahmebehandlung *oft* im Programm stehen hat und nicht nur an wenigen strategischen Stellen wo die Ausnahmen dann sowieso aufschlagen werden. Bei einer Tk-Anwendung beispielsweise überall dort wo etwas von der Tk-Hauptschleife aufgerufen wird. Da das Funktionen oder Methoden sind, könnte man das in einen Dekorator stecken.
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Hallo nochmal,

die Ausnahmebehandlungen stehen deshalb so oft im Programm, weil beinahe alle Datei-Operationen innerhalb einer Funktion stattfinden. Schiebe die die Ausnahmebehandlung nun eine Ebene höher, kann ich sie zwar auch abfangen, aber nicht mehr zuordnen.
BlackJack

@Grendel: Das ist doch dann aber eine Frage für *Dich* ob sich der Aufwand lohnt. Wenn Du jede kleine Operation einzeln behandeln *willst*, dann lohnt sich der Aufwand, wenn nicht, dann nicht. Wenn Du es eine Ebene höher behandelst und den Traceback mit ausgibst/protokollierst, dann solltest Du aber immer noch alles zuordnen können.
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Hm ja, dann könnte man per tkinter eine allgemeine Fehlermeldung ausgeben und den kompletten Traceback z.B. in ein Logfile schreiben. Ich muss das abwägen vor dem Hintergrund, dass ich im Fehlerfall mit den Nutzern kommunizieren muss. Im Augenblick sieht's halt so aus, dass jede Exception eine auf sie zugeschnittene Fehlermeldung auslöst, welche dem Nutzer in einer tkinter MessageBox präsentiert wird. Mir war gar nicht bewusst, dass es möglich ist, den ganzen Kram mittels des traceback Moduls handhaben zu können. :)
BlackJack

@Grendel: Man könnte auch parallel ein Log führen wo die Arbeitsschritte drin stehen. Also statt beim verschieben der Datenbankdateien die Fehlerbehandlung zu machen, dort einfach nur protokollieren das jetzt die Datenbankdateien verschoben werden, das dann tun, und in einer Ebene darüber die Ausnahme behandeln und dann das Protokoll anzeigen wo man sieht bis wohin der Gesamtvorgang gekommen ist. Wenn man das dann alles in dem Dialog anzeigt, kann der Benutzer, der ja wahrscheinlich keinen Traceback verstehend lesen kann, zumindest sagen was als letztes im Protokoll steht. Und wenn man den Dialog so gestaltet das man die Texte — Ausnahme als Zeichenkette, Log, und Traceback — auch kopieren oder in einer Textdatei speichern kann, könnten Benutzer auch relativ einfach Fehlerberichte mit allen nötigen Details an Dich übermitteln.
Grendel
User
Beiträge: 50
Registriert: Samstag 19. Dezember 2015, 16:06

Ich denke, so werde ich es machen. Bei selten vorkommenden Fehlern macht es mehr Sinn, sie übergeordnet incl. Traceback abzufangen.
Danke.
Antworten