Die klassische Art zu programmieren ist mit try ... catch
. Wann ist es angebracht, try
ohne zu verwenden catch
?
In Python erscheint Folgendes legal und kann sinnvoll sein:
try:
#do work
finally:
#do something unconditional
Der Code hat jedoch catch
nichts. Ähnlich könnte man in Java denken, es wäre wie folgt:
try {
//for example try to get a database connection
}
finally {
//closeConnection(connection)
}
Es sieht gut aus und plötzlich muss ich mich nicht mehr um Ausnahmetypen usw. kümmern. Wenn dies eine gute Praxis ist, wann ist sie eine gute Praxis? Was sind alternativ die Gründe, warum dies keine gute Praxis oder nicht legal ist? (Ich habe die Quelle nicht kompiliert. Ich frage danach, da es sich um einen Syntaxfehler für Java handeln könnte. Ich habe überprüft, ob Python sicher kompiliert.)
Ein damit verbundenes Problem, auf das ich gestoßen bin, ist folgendes: Ich schreibe die Funktion / Methode weiter, an deren Ende etwas zurückgegeben werden muss. Es kann sich jedoch an einem Ort befinden, der nicht erreicht werden sollte und ein Rückgabepunkt sein muss. Also, auch wenn ich die obigen Ausnahmen behandle, gebe ich immer NULL
noch eine leere Zeichenkette zurück oder irgendwann im Code, die nicht erreicht werden sollte, oft das Ende der Methode / Funktion. Ich habe es immer geschafft, den Code so umzustrukturieren, dass er es nicht muss return NULL
, da dies absolut weniger als eine gute Übung zu sein scheint.
quelle
try/catch
ist nicht "die klassische Art zu programmieren". Es ist die klassische C ++ - Art zu programmieren, da C ++ kein richtiges try / finally-Konstrukt besitzt, was bedeutet, dass Sie garantierte reversible Zustandsänderungen mit hässlichen Hacks unter Beteiligung von RAII implementieren müssen. Aber anständige OO-Sprachen haben dieses Problem nicht, denn sie bieten try / finally. Es wird für einen ganz anderen Zweck verwendet als try / catch.Antworten:
Es hängt davon ab, ob Sie mit den Ausnahmen umgehen können, die an dieser Stelle ausgelöst werden können oder nicht.
Wenn Sie die Ausnahmen lokal behandeln können, sollten Sie dies tun, und es ist besser, den Fehler so nahe wie möglich an der Stelle zu behandeln, an der er ausgelöst wird.
Wenn Sie nicht lokal damit umgehen können,
try / finally
ist es durchaus sinnvoll, nur einen Block zu haben - vorausgesetzt, Sie müssen Code ausführen, unabhängig davon, ob die Methode erfolgreich war oder nicht. Zum Beispiel (aus Neils Kommentar ) ist das Öffnen eines Streams und das Übergeben dieses Streams an eine innere Methode zum Laden ein hervorragendes Beispiel dafür, wann Sietry { } finally { }
die finally-Klausel verwenden müssen, um sicherzustellen, dass der Stream unabhängig vom Erfolg oder Erfolg geschlossen wird Fehler beim Lesen.Sie benötigen jedoch weiterhin irgendwo in Ihrem Code einen Ausnahmebehandler - es sei denn, Sie möchten, dass Ihre Anwendung vollständig abstürzt. Es hängt von der Architektur Ihrer Anwendung ab, wo genau sich dieser Handler befindet.
quelle
try { } finally { }
, unter Ausnutzung der finally - Strom , um sicherzustellen , schließlich geschlossen , unabhängig von Erfolg / Fehler.Der
finally
Block wird für Code verwendet, der immer ausgeführt werden muss, unabhängig davon, ob eine Fehlerbedingung (Ausnahme) aufgetreten ist oder nicht.Der Code im
finally
Block wird ausgeführt, nachdem dertry
Block abgeschlossen ist, und, wenn eine festgestellte Ausnahme aufgetreten ist, nachdem der entsprechendecatch
Block abgeschlossen ist. Es wird immer ausgeführt, auch wenn im Blocktry
oder eine nicht erfasste Ausnahme aufgetreten istcatch
.Der
finally
Block wird normalerweise zum Schließen von Dateien, Netzwerkverbindungen usw. verwendet, die imtry
Block geöffnet wurden . Der Grund dafür ist, dass die Datei- oder Netzwerkverbindung geschlossen werden muss, ob der Vorgang mit dieser Datei- oder Netzwerkverbindung erfolgreich war oder fehlgeschlagen ist.Es sollte darauf geachtet werden, dass der
finally
Block selbst keine Ausnahme auslöst. Stellen Sie beispielsweise doppelt sicher, dass Sie alle Variablen aufnull
usw. überprüfen .quelle
try-finally
können durch einewith
Aussage ersetzt werden.try/finally
Konstrukt. C # hatusing
, Python hatwith
, etc.using
undtry-finally
, da dieDispose
Methode nicht vomusing
Block aufgerufen wird , wenn imIDisposable
Konstruktor des Objekts eine Ausnahme auftritt .try-finally
Ermöglicht das Ausführen von Code, auch wenn der Konstruktor des Objekts eine Ausnahme auslöst.Ein Beispiel, in dem try ... finally without a catch-Klausel in Java angemessen (und noch idiomatischer ) ist, ist die Verwendung von Lock in Concurrent Utilities Locks-Paket.
quelle
l.lock()
mal probieren?try{ l.lock(); }finally{l.unlock();}
try
in diesem Ausschnitt Ressourcenzugriff wickeln soll, warum es mit keinem Zusammenhang etwas verschmutzen zu diesenl.lock()
fehlschlägt, wird derfinally
Block weiterhin ausgeführt, wenn erl.lock()
sich innerhalb destry
Blocks befindet. Wenn Sie dies wie von gnat vorgeschlagen tun, wird derfinally
Block nur ausgeführt, wenn bekannt ist, dass die Sperre aktiviert wurde.Auf einer grundlegenden Ebene
catch
undfinally
lösen Sie zwei verwandte, aber unterschiedliche Probleme:catch
wird verwendet, um ein Problem zu behandeln, das von dem aufgerufenen Code gemeldet wurdefinally
wird verwendet, um Daten / Ressourcen zu bereinigen, die der aktuelle Code erstellt / geändert hat, unabhängig davon, ob ein Problem aufgetreten ist oder nichtAlso beide sind irgendwie Probleme (Ausnahmen) verwendet, aber das ist so ziemlich alles , was sie gemeinsam haben.
Ein wichtiger Unterschied besteht darin, dass sich der
finally
Block in derselben Methode befinden muss , in der die Ressourcen erstellt wurden (um Ressourcenlecks zu vermeiden), und nicht im Aufrufstapel auf eine andere Ebene verschoben werden kann.Das ist
catch
jedoch eine andere Sache: Der richtige Ort dafür hängt davon ab, wo Sie die Ausnahme tatsächlich behandeln können . Es hat keinen Sinn, eine Ausnahme an einer Stelle abzufangen, an der Sie nichts dagegen tun können. Deshalb ist es manchmal besser, sie einfach fallen zu lassen.quelle
finally
einer (statisch oder dynamisch) umschließenden try-Anweisung freigeben ... und trotzdem 100% dicht sein.@yfeldblum hat die richtige Antwort: try-finally ohne catch-Anweisung sollte normalerweise durch ein geeignetes Sprachkonstrukt ersetzt werden.
In C ++ werden RAII und Konstruktoren / Destruktoren verwendet. in Python ist es eine
with
Aussage; und in C # ist es eineusing
Aussage.Diese sind fast immer eleganter, da sich der Initialisierungs- und Finalisierungscode an einer Stelle (dem abstrakten Objekt) und nicht an zwei Stellen befindet.
quelle
In vielen Sprachen wird eine
finally
Anweisung auch nach der return-Anweisung ausgeführt. Dies bedeutet, dass Sie Folgendes tun können:Dadurch werden die Ressourcen unabhängig davon freigegeben, wie die Methode mit einer Ausnahme oder einer regulären Rückgabeanweisung beendet wurde.
Ob dies gut oder schlecht ist, steht zur Debatte, ist aber
try {} finally {}
nicht immer auf die Ausnahmebehandlung beschränkt.quelle
Ich könnte der Zorn Pythonistas aufrufen (weiß nicht, wie ich Python nicht viel verwenden) oder Programmierer aus anderen Sprachen mit dieser Antwort, aber meiner Meinung nach die meisten Funktionen sollten nicht einen haben
catch
Block, ideal gesprochen. Um zu zeigen, warum, lassen Sie mich dies mit der manuellen Weitergabe von Fehlercodes vergleichen, wie ich sie bei der Arbeit mit Turbo C Ende der 80er und Anfang der 90er Jahre ausführen musste.Nehmen wir also an, wir haben eine Funktion zum Laden eines Bildes oder dergleichen, wenn ein Benutzer eine zu ladende Bilddatei auswählt, und dies ist in C und Assembly geschrieben:
Ich habe einige Funktionen auf niedriger Ebene weggelassen, aber wir können sehen, dass ich verschiedene Kategorien von Funktionen identifiziert habe, die farbcodiert sind, basierend auf den Verantwortlichkeiten, die sie in Bezug auf die Fehlerbehandlung haben.
Fehlerursache und Wiederherstellung
Nun war es nie schwer, die Kategorien von Funktionen zu schreiben, die ich als "mögliche
throw
Fehlerquellen " (die, die das sind) und die "Fehlerbehebungs- und Berichterstellungsfunktionen" (die, die dascatch
sind) bezeichne.Es war immer trivial, diese Funktionen korrekt zu schreiben, bevor die Ausnahmebehandlung verfügbar war, da eine Funktion, die auf einen externen Fehler stoßen kann, wie z. B. die Nichtzuweisung von Speicher, einfach einen
NULL
oder0
oder-1
oder einen globalen Fehlercode oder etwas in diesem Sinne zurückgeben kann. Und die Fehlerbehebung / -meldung war immer einfach, da Sie, nachdem Sie sich den Call-Stack bis zu einem Punkt durchgearbeitet haben, an dem es sinnvoll war, Fehler zu beheben und zu melden, einfach den Fehlercode und / oder die Meldung nehmen und dem Benutzer melden. Und natürlich ist eine Funktion am Blatt dieser Hierarchie, die niemals ausfallen kann, egal wie sie in Zukunft geändert wird (Convert Pixel
), kinderleicht zu schreiben (zumindest in Bezug auf die Fehlerbehandlung).Fehlerausbreitung
Die mühsamen Funktionen, die für menschliches Versagen anfällig sind, sind die Fehlerausbreitungsprogramme , die nicht direkt auf Fehler stoßen , sondern Funktionen aufrufen, die irgendwo in der Hierarchie versagen könnten. An diesem Punkt
Allocate Scanline
könnte einen Ausfall von handhaben müssenmalloc
und dann einen Fehler zurück nach unten zuConvert Scanlines
, dannConvert Scanlines
würde für diesen Fehler überprüfen müssen und es weitergeben zuDecompress Image
, dannDecompress Image->Parse Image
, undParse Image->Load Image
, undLoad Image
den Benutzer-End - Befehl , wo der Fehler schließlich gemeldet .Hier machen viele Menschen Fehler, da nur ein Fehler-Propagator den Fehler nicht überprüft und weitergibt, damit die gesamte Funktionshierarchie zusammenbricht, wenn es darum geht, den Fehler richtig zu behandeln.
Wenn Fehlercodes von Funktionen zurückgegeben werden, verlieren wir in etwa 90% unserer Codebasis die Fähigkeit, bei Erfolg interessante Werte zurückzugeben, da so viele Funktionen ihren Rückgabewert für die Rückgabe eines Fehlercodes reservieren müssten Scheitern .
Reduzierung menschlicher Fehler: Globale Fehlercodes
Wie können wir also die Möglichkeit menschlicher Fehler reduzieren? Hier könnte ich sogar den Zorn einiger C-Programmierer hervorrufen, aber eine sofortige Verbesserung meiner Meinung nach ist die Verwendung globaler Fehlercodes wie OpenGL mit
glGetError
. Dies gibt den Funktionen zumindest die Möglichkeit, im Erfolgsfall aussagekräftige Werte von Interesse zurückzugeben. Es gibt Möglichkeiten, diesen Thread sicher und effizient zu gestalten, wenn der Fehlercode in einem Thread lokalisiert ist.Es gibt auch Fälle, in denen eine Funktion möglicherweise auf einen Fehler stößt, es jedoch relativ harmlos ist, etwas länger zu warten, bevor sie aufgrund der Entdeckung eines früheren Fehlers vorzeitig zurückkehrt. Dies ermöglicht, dass so etwas passiert, ohne dass 90% der Funktionsaufrufe, die in jeder einzelnen Funktion ausgeführt werden, auf Fehler überprüft werden müssen, sodass eine ordnungsgemäße Fehlerbehandlung möglich ist, ohne dass dies so genau ist.
Reduzierung menschlicher Fehler: Ausnahmebehandlung
Die obige Lösung erfordert jedoch immer noch so viele Funktionen, um den Steuerflussaspekt der manuellen Fehlerausbreitung zu behandeln, selbst wenn sie die Anzahl der Zeilen des manuellen
if error happened, return error
Codetyps verringert haben könnte . Es würde es nicht vollständig beseitigen, da es immer noch häufig mindestens eine Stelle geben müsste, die nach einem Fehler sucht und für fast jede einzelne Fehlerausbreitungsfunktion zurückkehrt. Dies ist also der Zeitpunkt, an dem die Ausnahmebehandlung ins Spiel kommt, um den Tag zu retten (sorta).Der Wert der Ausnahmebehandlung besteht hier jedoch darin, den Kontrollflussaspekt der manuellen Fehlerausbreitung nicht mehr zu berücksichtigen. Dies bedeutet, dass sein Wert an die Fähigkeit gebunden ist, zu vermeiden, dass Sie
catch
in Ihrer gesamten Codebasis eine Bootsladung von Blöcken schreiben müssen . In der obigen Abbildung ist der einzige Ort, an dem eincatch
Block vorhanden sein muss, der Ort, anLoad Image User Command
dem der Fehler gemeldet wird. Nichts anderes sollte idealerweisecatch
irgendetwas zu tun haben, da es sonst langsam so mühsam und fehleranfällig wird wie die Fehlercode-Behandlung.Wenn Sie mich also fragen, ob Sie eine Codebasis haben, die auf elegante Weise wirklich von der Ausnahmebehandlung profitiert, sollte sie die Mindestanzahl von
catch
Blöcken haben (mit Minimum meine ich nicht Null, sondern eher eine für jeden einzelnen High-End-Code). Endbenutzeroperation, die fehlschlagen könnte, und möglicherweise noch weniger, wenn alle High-End-Benutzeroperationen über ein zentrales Befehlssystem aufgerufen werden).Ressourcenbereinigung
Die Ausnahmebehandlung beseitigt jedoch nur die Notwendigkeit, die Steuerflussaspekte der Fehlerausbreitung nicht manuell auf Pfaden zu behandeln, die von normalen Ausführungsabläufen getrennt sind. Oft kann eine Funktion, die als Fehler-Propagator dient, auch dann noch Ressourcen beschaffen, wenn sie dies jetzt automatisch mit EH tut. Zum Beispiel kann eine solche Funktion eine temporäre Datei öffnen, die sie schließen muss, bevor sie von der Funktion zurückkehrt, egal was passiert, oder einen Mutex sperren, den sie entsperren muss, egal was passiert.
Dafür könnte ich den Zorn vieler Programmierer aus allen möglichen Sprachen hervorrufen, aber ich halte den C ++ - Ansatz für ideal. Die Sprache führt Destruktoren ein, die deterministisch aufgerufen werden, sobald ein Objekt den Gültigkeitsbereich verlässt. Aus diesem Grund muss C ++ - Code, der z. B. einen Mutex durch ein Mutexobjekt mit Gültigkeitsbereich mit einem Destruktor sperrt, diesen nicht manuell entsperren, da er automatisch entsperrt wird, sobald das Objekt den Gültigkeitsbereich verlässt, unabhängig davon, was passiert (auch wenn eine Ausnahme vorliegt) angetroffen). Es ist also wirklich kein gut geschriebener C ++ - Code erforderlich, um jemals mit der Bereinigung lokaler Ressourcen fertig zu werden.
In Sprachen ohne Destruktoren müssen sie möglicherweise einen
finally
Block verwenden, um lokale Ressourcen manuell zu bereinigen. Das heißt, es ist immer noch besser, Ihren Code mit manueller Fehlerausbreitung zu verunreinigen, vorausgesetzt, Sie müssen nichtcatch
überall Ausnahmen machen.Umkehrung äußerer Nebenwirkungen
Dies ist das am schwierigsten zu lösende konzeptionelle Problem. Wenn eine Funktion, unabhängig davon, ob es sich um einen Fehlerfortpflanzer oder einen Fehlerort handelt, externe Nebenwirkungen verursacht, muss sie diese Nebenwirkungen zurücksetzen oder "rückgängig machen", damit das System wieder in einen Zustand versetzt wird, als ob der Vorgang niemals statt eines " half-valid "Zustand, in dem die Operation zur Hälfte erfolgreich war. Ich kenne keine Sprachen, die dieses konzeptionelle Problem viel einfacher machen, mit Ausnahme von Sprachen, die lediglich die Notwendigkeit reduzieren, dass die meisten Funktionen externe Nebenwirkungen verursachen, wie funktionale Sprachen, die sich um Unveränderlichkeit und beständige Datenstrukturen drehen.
Dies
finally
ist wohl die eleganteste Lösung für das Problem in Sprachen, die sich um Veränderbarkeit und Nebenwirkungen drehen, da diese Art von Logik häufig sehr spezifisch für eine bestimmte Funktion ist und sich nicht so gut auf das Konzept der "Ressourcenbereinigung" abbilden lässt ". Und ich empfehle,finally
in diesen Fällen großzügig zu verwenden, um sicherzustellen, dass Ihre Funktion Nebenwirkungen in Sprachen umkehrt, die sie unterstützen, unabhängig davon, ob Sie einencatch
Block benötigen oder nicht (und wenn Sie mich fragen, sollte gut geschriebener Code die Mindestanzahl von habencatch
Blöcke, und allecatch
Blöcke sollten sich an Stellen befinden, an denen dies am sinnvollsten ist, wie im obigen Diagramm inLoad Image User Command
).Traumsprache
IMO
finally
ist jedoch nahezu ideal für die Umkehrung von Nebenwirkungen, jedoch nicht ganz. Wir müssen eineboolean
Variable einführen , um Nebenwirkungen im Falle eines vorzeitigen Abbruchs (von einer ausgelösten Ausnahme oder auf andere Weise) effektiv rückgängig zu machen:Wenn ich jemals eine Sprache entwerfen könnte, wäre mein Traum, dieses Problem zu lösen, die Automatisierung des obigen Codes:
... mit Destruktoren, um die Bereinigung lokaler Ressourcen zu automatisieren, sodass wir sie nur benötigen
transaction
,rollback
undcatch
(obwohl ich vielleicht noch hinzufügen möchte, wennfinally
wir beispielsweise mit C-Ressourcen arbeiten, die sich nicht selbst bereinigen). Allerdingsfinally
mit einerboolean
Variable ist die nächste Sache , dies einfach zu machen , dass ich gefunden habe , so weit meine Traumsprache fehlt. Die zweitkomplizierteste Lösung, die ich dafür gefunden habe, sind Scope Guards in Sprachen wie C ++ und D, aber ich fand Scope Guards konzeptionell immer etwas umständlich, da sie die Idee der "Ressourcenbereinigung" und "Umkehrung von Nebenwirkungen" verwischen. Meiner Meinung nach sind das sehr unterschiedliche Ideen, die auf andere Weise angegangen werden müssen.Mein kleiner Wunschtraum einer Sprache würde sich auch stark um Unveränderlichkeit und dauerhafte Datenstrukturen drehen, um das Schreiben effizienter Funktionen, die keine umfangreichen Datenstrukturen in ihrer Gesamtheit kopieren müssen, obwohl die Funktion dies bewirkt, zu vereinfachen, obwohl dies nicht erforderlich ist keine Nebenwirkungen.
Fazit
Abgesehen von meinen Ramblings finde ich, dass Ihr
try/finally
Code zum Schließen des Sockets in Ordnung und großartig ist, wenn man bedenkt, dass Python nicht das C ++ - Äquivalent von Destruktoren hat, und ich persönlich denke, dass Sie das großzügig für Orte verwenden sollten, die Nebenwirkungen umkehren müssen und minimieren Sie die Anzahl der Stellen, an denen Sie arbeiten müssen,catch
zu Stellen, an denen dies am sinnvollsten ist.quelle
Es wird dringend empfohlen, Fehler / Ausnahmen abzufangen und ordnungsgemäß zu behandeln, auch wenn dies nicht vorgeschrieben ist.
Der Grund, warum ich das sage, ist, dass ich glaube, dass jeder Entwickler das Verhalten seiner Anwendung kennen und angehen sollte, andernfalls hat er seine Arbeit nicht ordnungsgemäß abgeschlossen. Es gibt keine Situation, in der ein try-finally-Block den try-catch-finally-Block ersetzt.
Ich gebe Ihnen ein einfaches Beispiel: Angenommen, Sie haben den Code zum Hochladen von Dateien auf den Server geschrieben, ohne Ausnahmen abzufangen. Wenn der Upload aus irgendeinem Grund fehlschlägt, weiß der Client nie, was schief gelaufen ist. Wenn Sie die Ausnahme abgefangen haben, können Sie eine ordentliche Fehlermeldung anzeigen, in der erklärt wird, was schief gelaufen ist und wie der Benutzer Abhilfe schaffen kann.
Goldene Regel: Ausnahme immer abfangen, da das Erraten Zeit braucht
quelle
catch
Klausel immer vorhanden sein sollte, wenn es eine gibttry
. Erfahrene Autoren, einschließlich mir, glauben, dass dies ein schlechter Rat ist. Ihre Argumentation ist fehlerhaft. Die Methode, die das enthält,try
ist nicht der einzige mögliche Ort, an dem die Ausnahme abgefangen werden kann. Es ist oft am einfachsten und besten, Ausnahmen von der obersten Ebene abzufangen und zu melden, anstatt Fangklauseln im gesamten Code zu duplizieren.