it-swarm.com.de

Warum sollte ich nicht jeden Block in "try" - "catch" einwickeln?

Ich war immer der Überzeugung, dass, wenn eine Methode eine Ausnahme auslösen kann, es keinen Sinn macht, diesen Aufruf nicht mit einem sinnvollen try-Block zu schützen.

Ich habe gerade gepostet: Sie sollten IMMER Anrufe einwickeln, die versuchen können, Blöcke zu blockieren. 'zu dieser Frage und man sagte mir, es sei ein "bemerkenswert schlechter Rat" - ich würde gerne verstehen, warum.

410
Konrad

Eine Methode sollte nur dann eine Ausnahme einfangen, wenn sie auf vernünftige Weise damit umgehen kann.

Andernfalls geben Sie ihn weiter, in der Hoffnung, dass eine Methode, die höher im Aufrufstapel liegt, einen Sinn ergeben kann.

Wie bereits erwähnt, ist es eine gute Praxis, einen unbehandelten Ausnahmebehandler (mit Protokollierung) auf der höchsten Ebene des Aufrufstacks zu haben, um sicherzustellen, dass alle schwerwiegenden Fehler protokolliert werden.

318
Mitch Wheat

Wie Mitchund- andere angegeben haben, sollten Sie keine Ausnahme feststellen, die Sie nicht in irgendeiner Weise bearbeiten möchten. Sie sollten berücksichtigen, wie die Anwendung beim Entwurf systematisch mit Ausnahmen umgehen soll. Dies führt in der Regel zu einer auf den Abstraktionen basierenden Ebenen der Fehlerbehandlung. Beispielsweise behandeln Sie alle SQL-bezogenen Fehler in Ihrem Datenzugriffscode so, dass der Teil der Anwendung, der mit Domänenobjekten interagiert, der Tatsache nicht ausgesetzt ist irgendwo ist eine DB unter der Haube.

Es gibt einige verwandte Code-Gerüche, die Sie auf jeden Fall vermeiden möchten, außer dem "alles überall einfangen" - Geruch.

  1. "catch, log, rethrow": Wenn Sie eine bereichsbasierte Protokollierung wünschen, schreiben Sie eine Klasse, die eine Protokollanweisung in ihren Destruktor ausgibt, wenn der Stack aufgrund einer Ausnahme (ala std::uncaught_exception()) abgewickelt wird. Alles was Sie tun müssen, ist eine Protokollierungsinstanz in dem für Sie interessanten Bereich zu deklarieren und voila, Sie haben Protokollierung und keine unnötige trycatch-Logik.

  2. _/"catch, throw übersetzt": Dies weist normalerweise auf ein Abstraktionsproblem hin. Wenn Sie nicht eine Verbundlösung implementieren, bei der Sie mehrere spezifische Ausnahmen in eine generische Ausnahme übersetzen, haben Sie wahrscheinlich eine unnötige Abstraktionsebene ... und sagen nicht "Ich brauche es morgen".

  3. "catch, cleanup, rethrow": Dies ist einer meiner Lieblinge. Wenn Sie viel davon sehen, sollten Sie Resource Acquisition is Initialization Techniken anwenden und den Bereinigungsabschnitt in den Destruktor einer janitor - Objektinstanz platzieren.

Ich betrachte Code, der mit try/catch-Blöcken übersät ist, als gutes Ziel für die Überprüfung und das Refactoring von Code. Es zeigt an, dass entweder die Ausnahmebehandlung nicht gut verstanden wird oder der Code zu einer Amöba geworden ist und dringend Refactoring erforderlich ist.

132
D.Shawley

Da die nächste Frage lautet: "Ich habe eine Ausnahme festgestellt. Was mache ich als Nächstes?" Was wirst du tun? Wenn Sie nichts tun, verbergen sich Fehler und das Programm könnte "einfach nicht funktionieren", ohne dass Sie herausfinden müssen, was passiert ist. Sie müssen verstehen, was genau Sie tun werden, wenn Sie die Ausnahme erkannt haben, und erst dann fangen, wenn Sie es wissen.

46
sharptooth

Herb Sutter schrieb über dieses Problem hier . Mit Sicherheit lesenswert.
Ein Teaser:

"Beim Schreiben von ausnahmesicherem Code geht es im Wesentlichen darum, an den richtigen Stellen 'try' und 'catch' zu schreiben." Diskutieren.

Kurz gesagt, diese Aussage spiegelt ein grundlegendes Missverständnis der Ausnahmesicherheit wider. Ausnahmen sind nur eine andere Form der Fehlerberichterstattung, und wir wissen sicherlich, dass das Schreiben von fehlersicherem Code nicht nur darin besteht, Rückgabecodes zu prüfen und Fehlerbedingungen zu behandeln.

Tatsächlich stellt sich heraus, dass es bei Ausnahmesicherheit nur selten darum geht, "try" und "catch" zu schreiben - und je seltener desto besser. Vergessen Sie auch nicht, dass die Ausnahmesicherheit das Design eines Codes beeinflusst. es ist nie nur ein nachträglicher einsatz, der mit ein paar zusätzlichen catch-aussagen wie zum würzen nachgerüstet werden kann.

24
Tadeusz Kopec

Sie müssen every-Block nicht mit try-catches abdecken, da ein try-catch noch unbehandelte Ausnahmen fangen kann, die in Funktionen weiter unten im Aufrufstapel ausgelöst werden. Anstatt zu haben, dass jede Funktion einen Try-Catch hat, können Sie eine Logik auf der obersten Ebene Ihrer Anwendung haben. Beispielsweise könnte es eine SaveDocument()-Routine auf oberster Ebene geben, die viele Methoden aufruft, die andere Methoden aufrufen usw. Diese Untermethoden benötigen keine eigenen Try-Catches, denn wenn sie geworfen werden, werden sie immer noch von SaveDocument()s catch abgefangen.

Dies ist aus drei Gründen Nizza: Es ist praktisch, weil Sie eine einzige Stelle haben, an der Sie einen Fehler melden können: den SaveDocument()-Catch-Block (s). Es ist nicht nötig, dies in allen Untermethoden zu wiederholen, und es ist auch das, was Sie wollen: eine einzige Stelle, an der der Benutzer eine nützliche Diagnose für etwas erhalten kann, das nicht funktioniert hat.

Zweitens wird das Speichern abgebrochen, wenn eine Ausnahme ausgelöst wird. Wenn bei jeder Untermethode versucht wird, eine Ausnahme auszulösen, gelangen Sie in den catch-Block dieser Methode, die Ausführung verlässt die Funktion und setzt fort durch SaveDocument(). Wenn schon etwas schief gelaufen ist, möchten Sie wahrscheinlich gleich dort aufhören.

Drei, alle Ihre Untermethoden kann davon ausgehen, dass jeder Aufruf erfolgreich ist. Wenn ein Anruf fehlgeschlagen ist, springt die Ausführung zum catch-Block, und der nachfolgende Code wird niemals ausgeführt. Dies kann Ihren Code viel sauberer machen. Zum Beispiel hier mit Fehlercodes:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

So könnte das mit Ausnahmen geschrieben werden:

// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Jetzt ist viel klarer, was passiert.

Hinweis Ausnahmesicherer Code kann auf andere Weise schwieriger zu schreiben sein: Sie möchten keinen Speicherplatz verlieren, wenn eine Ausnahme ausgelöst wird. Stellen Sie sicher, dass Sie überRAII, STL-Container, intelligente Zeiger und andere Objekte Bescheid wissen, die ihre Ressourcen in Destruktoren freigeben, da Objekte vor Ausnahmen immer zerstört werden.

24
AshleysBrain

Wie bereits in anderen Antworten erwähnt, sollten Sie nur dann eine Ausnahme feststellen, wenn Sie eine vernünftige Fehlerbehandlung durchführen können.

Zum Beispiel fragt der Fragesteller in der Frage , die Ihre Frage hervorgerufen hat, ob es sicher ist, Ausnahmen für einen lexical_cast von einer Ganzzahl in einen String zu ignorieren. Eine solche Besetzung sollte niemals versagen. Wenn es fehlgeschlagen ist, ist im Programm etwas schief gelaufen. Was können Sie möglicherweise tun, um sich in dieser Situation zu erholen? Es ist wahrscheinlich am besten, das Programm einfach sterben zu lassen, da es sich in einem Zustand befindet, dem man nicht trauen kann. Es kann also am sichersten sein, die Ausnahme nicht zu behandeln.

14

Wenn Sie im Aufrufer einer Methode, die eine Ausnahme auslösen kann, immer sofort mit Ausnahmen umgehen, werden Ausnahmen unbrauchbar und Sie sollten besser Fehlercodes verwenden.

Die einzige Ausnahme ist, dass sie nicht in jeder Methode der Anrufkette behandelt werden müssen.

11
starblue

Der beste Rat, den ich gehört habe, ist, dass Sie Ausnahmen nur an Stellen erhalten sollten, an denen Sie etwas zu dem außergewöhnlichen Zustand tun können, und dass "Fangen, Protokollieren und Freigeben" keine gute Strategie ist (wenn gelegentlich in Bibliotheken unvermeidbar).

9
Donal Fellows

Ich stimme der grundsätzlichen Richtung Ihrer Frage zu, so viele Ausnahmen wie möglich auf der untersten Ebene zu behandeln.

Einige der vorhandenen Antworten lauten wie folgt: "Sie müssen die Ausnahmebedingung nicht behandeln. Jemand anderes erledigt den Stack." Nach meiner Erfahrung ist dies eine schlechte Ausrede, um nicht darüber nachzudenken über die Ausnahmebehandlung an dem derzeit entwickelten Code, wodurch die Ausnahmebehandlung das Problem einer anderen Person oder später betrifft.

Dieses Problem wächst dramatisch in der verteilten Entwicklung, bei der Sie möglicherweise eine von einem Kollegen implementierte Methode aufrufen müssen. Und dann müssen Sie eine verschachtelte Kette von Methodenaufrufen untersuchen, um herauszufinden, warum er/sie eine Ausnahme auf Sie wirft, die bei der tiefsten verschachtelten Methode viel einfacher gehandhabt werden könnte.

6
Bananeweizen

Der Rat, den mir mein Informatikprofessor einst gab, lautete: "Try and Catch-Blöcke nur verwenden, wenn der Fehler nicht mit Standardmitteln behandelt werden kann."

Als Beispiel sagte er uns, wenn ein Programm an einem Ort auf ein ernstes Problem stößt, wo es nicht möglich ist, etwas zu tun wie:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

Dann sollten Sie try, catch blocks verwenden. Sie können zwar mit Ausnahmen umgehen, dies wird jedoch im Allgemeinen nicht empfohlen, da Ausnahmen eine hohe Leistung darstellen.

5
Mike Bailey

Wenn Sie das Ergebnis jeder Funktion testen möchten, verwenden Sie Rückkehrcodes. 

Der Zweck von Exceptions besteht darin, die Ergebnisse LESS oft zu testen. Die Idee ist, außergewöhnliche (ungewöhnliche, seltenere) Bedingungen von Ihrem gewöhnlicheren Code zu trennen. Dies hält den normalen Code sauberer und einfacher - und ist dennoch in der Lage, diese außergewöhnlichen Bedingungen zu bewältigen.

In gut entworfenem Code können tiefere Funktionen ausgelöst werden, und höhere Funktionen könnten sich verfangen. Aber der Schlüssel ist, dass viele Funktionen "dazwischen" überhaupt nicht mit außergewöhnlichen Bedingungen umgehen müssen. Sie müssen nur "ausnahmesicher" sein, was nicht heißt, dass sie fangen müssen.

2
bluedog

Neben dem oben genannten Rat benutze ich persönlich try + catch + throw; aus folgendem Grund:

  1. An der Grenze eines anderen Codierers verwende ich try + catch + throw in dem von mir geschriebenen Code, bevor die Ausnahme, die von anderen an den Anrufer geschrieben wird, geworfen wird. Dies gibt mir die Möglichkeit, einen Fehler in meinem Code zu erfahren, und Dieser Ort ist dem Code, der anfänglich die Ausnahme auslöst, viel näher, je näher der Grund ist.
  2. An der Grenze der Module, obwohl verschiedene Module von derselben Person geschrieben werden können.
  3. Learning + Debug Zweck, in diesem Fall verwende ich catch (...) in C++ und catch (Exception ex) in C #. Für C++ löst die Standardbibliothek nicht zu viele Exceptions aus, daher ist dieser Fall in C++ selten. In C # gibt es jedoch eine riesige Bibliothek und eine ausgereifte Ausnahmehierarchie. Der Code für die C # -Bibliothek wirft eine Vielzahl von Ausnahmen. In der Theorie sollte ich (und Sie) alle Ausnahmen von der von Ihnen aufgerufenen Funktion kennen und den Grund/Fall wissen Diese Ausnahmebedingungen werden geworfen und wissen, wie sie behutsam damit umgehen (vorbeigehen oder fangen und damit umgehen). Leider ist es in der Realität sehr schwer, alles über die möglichen Ausnahmen zu erfahren, bevor ich eine Codezeile schreibe. Also fange ich alles und lasse meinen Code laut durch Protokollieren (in der Produktumgebung)/Durchsetzungsdialogfeld (in der Entwicklungsumgebung) sprechen, wenn tatsächlich eine Ausnahme auftritt. Auf diese Weise füge ich Ausnahmebehandlungscode schrittweise hinzu. Ich weiß, dass es mit guten Ratschlägen vereinbar ist, aber in Wirklichkeit funktioniert es für mich und ich kenne keinen besseren Weg für dieses Problem.
1
zhaorufei

Ich möchte dieser Diskussion hinzufügen, dass seit C++ 11 sehr sinnvoll ist, solange jeder catch-Block rethrow die Ausnahme bis zu dem Punkt ist, an dem er behandelt werden kann/soll. Auf diese Weise kann eine Rückverfolgung kann erzeugt werden. Ich glaube daher, dass die früheren Stellungnahmen teilweise überholt sind.

Verwenden Sie std::nested_exception und std::throw_with_nested

Es wird auf StackOverflow hier und hier beschrieben, wie Sie dies erreichen.

Da dies mit jeder abgeleiteten Ausnahmeklasse möglich ist, können Sie einer solchen Rückverfolgung eine Menge Informationen hinzufügen! Sie können sich auch meine MWE auf GitHub anschauen, wo eine Rückverfolgung so aussehen würde :

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
1
GPMueller

Ich fühle mich gezwungen, eine weitere Antwort hinzuzufügen, obwohl die Antwort von Mike Wheat die wichtigsten Punkte ziemlich gut zusammenfasst. Ich denke so darüber nach. Wenn Sie Methoden haben, die mehrere Dinge tun, multiplizieren Sie die Komplexität, ohne sie zu addieren.

Mit anderen Worten, eine Methode, die in einen Versuchsfang eingeschlossen ist, hat zwei mögliche Ergebnisse. Sie haben das Non-Exception-Ergebnis und das Exception-Ergebnis. Wenn Sie mit vielen Methoden zu tun haben, explodiert dies exponentiell und unfassbar.

Exponentiell, denn wenn sich jede Methode auf zwei verschiedene Arten verzweigt, werden bei jedem Aufruf einer anderen Methode die vorherigen potenziellen Ergebnisse quadriert. Wenn Sie fünf Methoden aufgerufen haben, stehen Ihnen mindestens 256 mögliche Ergebnisse zur Verfügung. Vergleichen Sie dies mit not try/catch in jeder einzelnen Methode, und Sie haben nur einen Pfad, dem Sie folgen müssen.

Im Grunde sehe ich das so. Sie könnten versucht sein zu argumentieren, dass jede Art von Verzweigung dasselbe tut, aber try/catches sind ein Sonderfall, da der Status der Anwendung im Grunde undefiniert wird.

Kurz gesagt, Try/Catches erschweren das Verständnis des Codes erheblich.

0
user875234

Wenn Sie zeitweilige Produktionsprobleme problemlos beheben möchten, sollten Sie jeden Codeblock in einen try..catch-Block einschließen. Dies ist im Wesentlichen für den Code vorgesehen, um umfangreiche Debug-Informationen bereitzustellen, mit denen Sie ohne Debugger in der Produktion debuggen können. Benutzer müssen nicht per E-Mail oder Chat mit dem Support kommunizieren, und alle Informationen, die zur Behebung des Problems erforderlich sind, befinden sich direkt dort. Es besteht keine Notwendigkeit, Probleme zu reproduzieren.

Um ordnungsgemäß zu funktionieren, muss es mit einer umfangreichen Protokollierung kombiniert werden, die den Namespace/das Modul, den Klassennamen, die Methode, die Eingaben sowie die Fehlernachrichten und das Speichern in einer Datenbank erfasst, sodass sie zusammengefasst werden kann, um hervorzuheben, welche Methode am meisten fehlschlägt zuerst behoben. 

Ausnahmen sind 100- bis 1000-mal langsamer als normaler Code und sollten niemals neu geschrieben werden. Erstellen Sie auch keine Ausnahme und werfen Sie sie aus. Das ist sehr entmutigend. Ausnahmen werden aufgefangen, so dass sie mit regulärem Code behoben werden können.

Diese Technik wurde verwendet, um eine fehlerhafte App in einem Fortune-500-Unternehmen, das von 12 Entwicklern über zwei Jahre hinweg entwickelt wurde, schnell zu stabilisieren. Mit dieser Funktion wurden in 4 Monaten 3000 Korrekturen identifiziert, repariert, erstellt und 3000 Korrekturen bereitgestellt. In diesem Fall meldete das System keine Ausnahmen mehr, da alle behandelt wurden. Dies bedeutet im Durchschnitt 4 Monate lang alle 15 Minuten einen Fixpunkt.

0
user2502917