Ausnahmenweitergabe: Wann sollte ich Ausnahmen abfangen?

44

MethodA ruft eine MethodB auf, die wiederum MethodC aufruft.

In MethodB oder MethodC gibt es KEINE Ausnahmebehandlung. In MethodA gibt es jedoch eine Ausnahmebehandlung.

In MethodC tritt eine Ausnahme auf.

Jetzt sprudelt diese Ausnahme zu MethodA, die sie entsprechend behandelt.

Was ist daran falsch?

Meiner Meinung nach wird ein Aufrufer irgendwann MethodB oder MethodC ausführen, und wenn in diesen Methoden Ausnahmen auftreten, was sich aus der Behandlung von Ausnahmen in diesen Methoden ergibt, die im Wesentlichen nur ein try / catch / finally-Block statt nur let sind sie sprudeln bis zum angerufenen?

Die Aussage oder der Konsens in Bezug auf die Ausnahmebehandlung lautet, dass ausgelöst werden muss, wenn die Ausführung aus diesem Grund nicht fortgesetzt werden kann - eine Ausnahme. Ich verstehe das. Aber warum nicht die Ausnahme weiter oben in der Kette abfangen, anstatt Try / Catch-Blöcke ganz unten zu haben?

Ich verstehe es, wenn Sie Ressourcen freisetzen müssen. Das ist eine ganz andere Sache.

Daniel Frost
quelle
46
Warum glauben Sie, besteht der Konsens darin, eine Kette von Durchgangsfängen zu haben?
Caleth
Mit einer guten IDE und einem geeigneten Codierungsstil können Sie feststellen, dass beim Aufrufen einer Methode eine Ausnahme ausgelöst werden kann. Der Anrufer entscheidet, ob er es handhaben oder verbreiten darf. Ich sehe kein Problem damit.
Hieu Le
14
Wenn eine Methode die Ausnahme nicht verarbeiten kann und sie lediglich erneut auslöst, würde ich sagen, dass dies ein Codegeruch ist. Wenn eine Methode die Ausnahme nicht verarbeiten kann und beim Auslösen einer Ausnahme keine weiteren Aktionen ausführen muss, ist überhaupt kein try-catchBlock erforderlich .
Greg Burghardt
7
"Was ist daran falsch?" : nichts
Ewan
5
Pass-Through-Catching (das keine Ausnahmen in verschiedene Typen oder Ähnliches einwickelt) besiegt den gesamten Zweck von Ausnahmen. Das Werfen von Ausnahmen ist ein komplexer Mechanismus, der absichtlich erstellt wurde. Wenn Pass-Through-Fänge der beabsichtigte Anwendungsfall wären, müssten Sie lediglich einen Result<T>Typ implementieren (einen Typ, der entweder ein Ergebnis einer Berechnung oder einen Fehler speichert) und ihn von Ihren ansonsten auslösenden Funktionen zurückgeben. Das Übertragen eines Fehlers auf den Stapel würde das Lesen jedes Rückgabewerts, das Überprüfen, ob es sich um einen Fehler handelt, und das Zurückgeben eines Fehlers erfordern, wenn dies der Fall ist.
Alexander

Antworten:

139

Fangen Sie grundsätzlich keine Ausnahmen ab, es sei denn, Sie wissen, was Sie damit tun sollen. Wenn MethodC eine Ausnahme auslöst, aber MethodB keine sinnvolle Möglichkeit hat, damit umzugehen, sollte es der Ausnahme ermöglichen, sich auf MethodA zu verbreiten.

Die einzigen Gründe, warum eine Methode einen Fang- und Rückwurfmechanismus haben sollte, sind:

  • Sie möchten eine Ausnahme in eine andere Ausnahme umwandeln, die für den oben genannten Aufrufer von größerer Bedeutung ist.
  • Sie möchten der Ausnahme zusätzliche Informationen hinzufügen.
  • Sie benötigen eine catch-Klausel, um Ressourcen zu bereinigen, die ohne eine durchgesickert wären.

Anderenfalls führt das Abfangen von Ausnahmen auf der falschen Ebene dazu, dass der Code unbemerkt fehlschlägt, ohne dem aufrufenden Code (und letztendlich dem Benutzer der Software) eine nützliche Rückmeldung zu geben. Die Alternative, eine Ausnahme abzufangen und sie dann sofort erneut zu werfen, ist sinnlos.

Simon B
quelle
28
@ GregBurghardt Wenn Ihre Sprache etwas ähnliches hat try ... finally ..., dann verwenden Sie das, nicht fangen und werfen
Caleth
19
"eine ausnahme abfangen und dann sofort wieder wegwerfen ist sinnlos" Je nach sprache und vorgehensweise kann dies aktiv schädlich für die codebasis sein. Häufig entfernen Benutzer, die dies versuchen, viele Informationen über die Ausnahme, z. B. den ursprünglichen Stacktrace. Ich habe mich mit Code befasst, bei dem der Anrufer eine Ausnahme bekommt, die völlig irreführend ist, was wo passiert ist.
JimmyJames
7
msgstr "fange keine Ausnahmen ab, wenn du nicht weißt, was du mit ihnen machen sollst". Das klingt auf den ersten Blick vernünftig, bereitet aber später Probleme. Was Sie hier tun, ist die Weitergabe von Implementierungsdetails an Ihre Anrufer. Stellen Sie sich vor, Sie verwenden ein bestimmtes ORM in Ihrer Implementierung, um Daten zu laden. Wenn Sie die spezifischen Ausnahmen von ORM nicht abfangen, sondern nur in die Luft sprudeln lassen, können Sie Ihre Datenebene nicht ersetzen, ohne die Kompatibilität mit vorhandenen Benutzern zu beeinträchtigen. Das ist einer der offensichtlicheren Fälle, aber es kann ziemlich heimtückisch werden und ist schwer zu fassen.
Voo
11
@Voo In deinem Beispiel weißt du , was du damit machen sollst . Packen Sie es in eine dokumentierte Ausnahme, die spezifisch für Ihren Code ist, LoadDataExceptionund fügen Sie die ursprünglichen Ausnahmedetails entsprechend Ihren Sprachfunktionen hinzu, damit zukünftige Betreuer die Ursache erkennen können, ohne einen Debugger anhängen und herausfinden zu müssen, wie das Problem reproduziert werden kann.
Colin Young
14
@Voo Sie haben anscheinend den Grund "Sie möchten eine Ausnahme in eine andere konvertieren, die für den obigen Aufrufer sinnvoller ist" für Catch / Rethrow-Szenarien verpasst.
jpmc26
21

Was ist daran falsch?

Absolut gar nichts.

Jetzt sprudelt diese Ausnahme zu MethodA, die sie entsprechend behandelt.

"Behandelt es angemessen" ist der wichtige Teil. Das ist der Kern der strukturierten Ausnahmebehandlung.

Wenn Ihr Code mit einer Ausnahme etwas "Nützliches" bewirken kann, versuchen Sie es. Wenn nicht, dann lass es gut sein.

. . . Warum nicht die Ausnahme weiter oben in der Kette abfangen, anstatt Try / Catch-Blöcke ganz unten zu haben?

Genau das sollten Sie tun. Wenn Sie Code lesen, der Handler / Rethrower "ganz unten" hat, dann lesen Sie [wahrscheinlich] ziemlich schlechten Code.

Leider sehen einige Entwickler Catch-Blöcke nur als "Boiler-Plate" -Code, den sie in jede Methode, die sie schreiben, einschleusen (kein Wortspiel beabsichtigt), oft, weil sie die Ausnahmebehandlung nicht wirklich "verstehen" und denken, dass sie etwas hinzufügen müssen dass Ausnahmen nicht "entkommen" und ihr Programm beenden.

Ein Teil der Schwierigkeit dabei ist, dass dieses Problem die meiste Zeit nicht einmal bemerkt wird, da Ausnahmen nicht die ganze Zeit geworfen werden, aber wenn dies der Fall ist , wird das Programm sehr viel Zeit und Zeit verschwenden Versuchen Sie nach und nach, die Auswahl des Aufrufstapels aufzuheben, um zu einem Punkt zu gelangen, der mit der Ausnahme tatsächlich etwas Nützliches bewirkt.

Phill W.
quelle
7
Was noch schlimmer ist, ist, dass die Anwendung die Ausnahme abfängt und sie dann protokolliert (wobei sie hoffentlich nicht für immer dort bleibt) und versucht, wie gewohnt fortzufahren, auch wenn dies wirklich nicht möglich ist.
Solomon Ucko
1
@SolomonUcko: Nun, es kommt darauf an. Wenn Sie beispielsweise einen einfachen RPC-Server schreiben und eine nicht behandelte Ausnahme bis zur Hauptereignisschleife auftritt, können Sie sie nur protokollieren, einen RPC-Fehler an den Remote-Peer senden und die Verarbeitung von Ereignissen fortsetzen. Die andere Alternative besteht darin, das gesamte Programm zu beenden, wodurch Ihre SREs verrückt werden, wenn der Server in der Produktion abstirbt.
Kevin
@ Kevin In diesem Fall sollte catchauf der höchstmöglichen Ebene eine einzige vorhanden sein, die den Fehler protokolliert und eine Fehlerantwort zurückgibt. Nicht catchüberall verstreute Blöcke. Wenn Sie nicht alle möglichen überprüften Ausnahmen (in Sprachen wie Java) auflisten möchten, schließen Sie sie einfach in ein ein, RuntimeExceptionanstatt sie dort zu protokollieren, fortzufahren und auf weitere Fehler oder sogar Schwachstellen zu stoßen.
Solomon Ucko
8

Sie müssen einen Unterschied zwischen Bibliotheken und Anwendungen machen.

Bibliotheken können unaufgefangene Ausnahmen frei werfen

Wenn Sie eine Bibliothek entwerfen, müssen Sie sich irgendwann Gedanken machen, was schief gehen kann. Parameter liegen möglicherweise im falschen Bereich oder nullexterne Ressourcen sind möglicherweise nicht verfügbar usw.

Ihre Bibliothek wird meist nicht in der Lage sein , vernünftig mit ihnen umzugehen. Die einzig sinnvolle Lösung besteht darin, eine entsprechende Ausnahme auszulösen und den Entwickler der Anwendung damit zu beauftragen.

Bewerbungen sollten immer irgendwann Ausnahmen fangen

Wenn eine Ausnahme abgefangen wird, möchte ich sie entweder als Fehler oder als schwerwiegende Fehler kategorisieren . Ein regulärer Fehler bedeutet, dass ein einzelner Vorgang in meiner Anwendung fehlgeschlagen ist. Beispielsweise konnte ein geöffnetes Dokument nicht gespeichert werden, da das Ziel nicht beschreibbar war. Es ist nur sinnvoll, dass die Anwendung den Benutzer darüber informiert, dass der Vorgang nicht erfolgreich abgeschlossen werden konnte. Geben Sie für den Benutzer lesbare Informationen zum Problem und lassen Sie den Benutzer entscheiden, was als Nächstes zu tun ist.

Ein schwerwiegender Fehler ist ein Fehler, den die Hauptanwendungslogik nicht beheben kann. Wenn beispielsweise der Grafikgerätetreiber in einem Videospiel abstürzt, kann die Anwendung den Benutzer nicht ordnungsgemäß informieren. In diesem Fall sollte eine Protokolldatei geschrieben und der Benutzer nach Möglichkeit auf die eine oder andere Weise informiert werden.

Selbst in solch einem schwerwiegenden Fall sollte die Anwendung diese Ausnahme auf sinnvolle Weise behandeln. Dies kann das Schreiben einer Protokolldatei, das Senden eines Absturzberichts usw. umfassen. Es gibt keinen Grund, warum die Anwendung auf die Ausnahme in irgendeiner Weise nicht reagiert.

MechMK1
quelle
Wenn Sie eine Bibliothek für Festplattenschreibvorgänge oder andere Hardware-Manipulationen haben, kann dies zu unerwarteten Ereignissen führen. Was ist, wenn beim Schreiben eine Festplatte herausgerissen wird? Was für ein CD-Laufwerk ist beim Lesen kurzgeschlossen? Das ist außerhalb Ihrer Kontrolle und während Sie könnten etwas tun ( zum Beispiel so tun , es war erfolgreich), ist es oft eine gute Praxis , eine Ausnahme von den Bibliotheksbenutzern zu werfen und lassen Sie sie entscheiden. Möglicherweise HDDPluggedOutDuringWritingExceptionkann ein Problem behandelt werden und ist für die Anwendung nicht schwerwiegend. Das Programm kann entscheiden, was damit geschehen soll.
VLAZ
1
@VLAZ Was tödlich oder nicht tödlich ist, muss die Anwendung entscheiden. Die Bibliothek sollte erzählen, was passiert ist. Die Anwendung muss entscheiden, wie sie darauf reagieren soll.
MechMK1
0

Was an dem von Ihnen beschriebenen Muster falsch ist, ist, dass Methode A nicht in der Lage ist, drei Szenarien zu unterscheiden:

  1. Methode B schlug in vorweggenommener Weise fehl.

  2. Methode C schlug in einer Weise fehl, die von Methode B nicht erwartet wurde, während Methode B jedoch eine Operation ausführte, die sicher abgebrochen werden konnte.

  3. Methode C schlug in einer Weise fehl, die von Methode B nicht erwartet wurde, aber während Methode B eine Operation ausführte, die die Dinge in einen vermeintlich vorübergehenden inkohärenten Zustand versetzte, den B aufgrund des Fehlers von C nicht bereinigte.

Die einzige Möglichkeit, wie Methode A diese Szenarien unterscheiden kann, besteht darin, dass die von B geworfene Ausnahme Informationen enthält, die für diesen Zweck ausreichen, oder dass der Stapel, der für Methode B abgewickelt wird, das Objekt in einem explizit ungültig gemachten Zustand belässt . Leider machen die meisten Ausnahmeframeworks beide Muster umständlich, was Programmierer dazu zwingt, "weniger böse" Entwurfsentscheidungen zu treffen.

Superkatze
quelle
2
Szenario 2 und 3 sind Fehler in Methode B. Methode A sollte nicht versuchen, diese zu beheben.
Sjoerd
@Sjoerd: Wie soll Methode B alle Arten vorwegnehmen, in denen Methode C fehlschlagen könnte?
Supercat
Durch bekannte Muster wie das Ausführen aller Operationen, die in temporäre Variablen geworfen werden könnten, und dann mit Operationen, die nicht geworfen werden können - z. B. Tauschen - den alten mit dem neuen Zustand tauschen. Ein anderes Muster definiert Vorgänge, die sicher wiederholt werden können, sodass Sie den Vorgang wiederholen können, ohne befürchten zu müssen, dass es zu Fehlern kommt. Es gibt volle Bücher über das Schreiben von "Exception Safe Code", daher kann ich Ihnen hier nicht alles erzählen.
Sjoerd
Dies ist ein guter Punkt, um überhaupt keine Ausnahmen zu verwenden (was imho eine ausgezeichnete Entscheidung wäre). Aber ich denke, es beantwortet die Frage nicht wirklich, da das OP anscheinend beabsichtigt, Ausnahmen zu verwenden, und nur fragt, wo der Fang sein soll.
13.
@Sjoerd Methode B ist viel einfacher zu überlegen, wenn die Sprache Ausnahmen verbietet. Denn in diesem Fall sehen Sie tatsächlich alle Kontrollflusspfade durch B und müssen nicht raten, welche Operatoren in einer auslösenden Weise (C ++) überladen sein könnten, um Szenario 3 zu vermeiden. Wir zahlen eine Menge an Code Klarheit und Sicherheit, um faul zu sein, Fehler durch "einfaches" Auslösen von Ausnahmen zurückzugeben. Denn am Ende der Fehlerbehandlung ist ein wichtiger Teil des Codes.
13.