482 Stimmen

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

Ich war schon immer der Überzeugung, dass es fahrlässig ist, einen Aufruf, der eine Ausnahme werfen kann, nicht mit einem sinnvollen try-Block zu schützen.

Ich habe gerade "Sie sollten IMMER Aufrufe, die eine Ausnahme werfen können, in try-catch-Blöcke einpacken." zu dieser Frage gepostet und mir wurde gesagt, dass es sich um 'bemerkenswert schlechten Rat' handelt - Ich würde gerne verstehen, warum.

399voto

Mitch Wheat Punkte 287474

Eine Methode sollte eine Ausnahme nur dann abfangen, wenn sie diese auf sinnvolle Weise behandeln kann.

Ansonsten sollte sie weitergegeben werden in der Hoffnung, dass eine Methode weiter oben im Aufrufstapel damit umgehen kann.

Wie bereits von anderen erwähnt, ist es eine gute Praxis, einen nicht behandelten Ausnahme-Handler (mit Protokollierung) auf der höchsten Ebene des Aufrufstapels zu haben, um sicherzustellen, dass alle schwerwiegenden Fehler protokolliert werden.

12 Stimmen

Es ist auch erwähnenswert, dass try-Blöcke Kosten (in Form generierten Codes) verursachen. Es gibt eine gute Diskussion in Scott Meyers' "More Effective C++".

33 Stimmen

Tatsächlich sind try-Blöcke in jedem modernen C-Compiler kostenlos, diese Information ist veraltet, Nick. Ich bin auch anderer Meinung, was das Vorhandensein eines Top-Level-Ausnahmehandlers betrifft, weil man dann die Lokalitätsinformationen verliert (den tatsächlichen Ort, an dem die Anweisung fehlgeschlagen ist).

36 Stimmen

@Blind: Der obere Ausnahmehandler ist nicht dafür da, um die Ausnahme zu behandeln, sondern vielmehr um laut zu verkünden, dass es eine unbehandelte Ausnahme gab, ihre Nachricht zu geben und das Programm auf eine elegante Weise zu beenden (statt einem Aufruf von terminate eine Rückgabe von 1). Es handelt sich eher um einen Sicherheitsmechanismus. Außerdem sind try/catch mehr oder weniger kostenlos, wenn es keine Ausnahme gibt. Wenn eine Ausnahme auftritt und weitergereicht wird, verbraucht es jedes Mal Zeit, wenn sie geworfen und gefangen wird. Deshalb ist eine Ketten von try/catch, die nur weiterreichen, nicht kostenlos.

153voto

D.Shawley Punkte 56313

Wie Mitch und andere festgestellt haben, sollten Sie keine Ausnahme abfangen, die Sie nicht auf irgendeine Weise behandeln möchten. Sie sollten darüber nachdenken, wie die Anwendung Ausnahmen systematisch behandeln wird, wenn Sie sie entwerfen. Dies führt normalerweise dazu, dass Ebenen von Fehlerbehandlung basierend auf Abstraktionen erstellt werden - zum Beispiel behandeln Sie alle SQL-bezogenen Fehler in Ihrem Datenzugriffscode, so dass der Teil der Anwendung, der mit Domänenobjekten interagiert, nicht damit konfrontiert wird, dass irgendwo im Hintergrund eine DB vorhanden ist.

Es gibt ein paar verwandte Code-Rußen, die Sie definitiv vermeiden möchten, zusätzlich zum "Alles überall fangen"-Ruß.

  1. "fangen, protokollieren, neu werfen": Wenn Sie ein protokollbasiertes Protokollieren wünschen, schreiben Sie eine Klasse, die beim Entfernen des Stapels aufgrund einer Ausnahme ein Protokoll erstellt (ala std::uncaught_exception()). Alles, was Sie tun müssen, ist eine Protokollinstanz im Bereich zu deklarieren, an dem Sie interessiert sind, und voilà, Sie haben Protokollierung und keine unnötige try/catch Logik.

  2. "fangen, werfen übersetzt": Dies deutet normalerweise auf ein Abstraktionsproblem hin. Wenn Sie nicht gerade eine föderierte Lösung implementieren, bei der Sie mehrere spezifische Ausnahmen in eine allgemeinere übersetzen, haben Sie wahrscheinlich eine unnötige Abstraktionsebene... und sagen Sie nicht "Vielleicht brauche ich es morgen".

  3. "fangen, aufräumen, neu werfen": Dies ist eine meiner Lieblingsaversionen. Wenn Sie viel davon sehen, sollten Sie Resource Acquisition is Initialization-Techniken anwenden und den Aufräumteil im Destruktor einer janitor-Objektinstanz platzieren.

Ich halte Code, der mit try/catch Blöcken übersät ist, für ein gutes Ziel für Codeüberprüfung und Refactoring. Es zeigt an, dass entweder die Ausnahmebehandlung nicht gut verstanden wird oder der Code zu einem Amöben geworden ist und dringend einer Überarbeitung bedarf.

9 Stimmen

1 ist für mich neu. +1 dafür. Außerdem möchte ich eine häufige Ausnahme zu #2 festhalten, nämlich wenn man eine Bibliothek entwirft, möchte man oft interne Ausnahmen in etwas übersetzen, was von der Schnittstelle Ihrer Bibliothek festgelegt ist, um die Kopplung zu reduzieren (das könnte das sein, was Sie mit "föderierter Lösung" meinen, aber mir ist dieser Begriff nicht vertraut).

3 Stimmen

Grundsätzlich das, was du gesagt hast: parashift.com/c++-faq-lite/exceptions.html#faq-17.13

1 Stimmen

2, wo es kein Code-Geruch ist, sondern Sinn macht, kann durch Beibehaltung der alten Ausnahme als verschachtelte Ausnahme verbessert werden.

55voto

sharptooth Punkte 162790

Da die nächste Frage lautet "Ich habe eine Ausnahme gefangen, was mache ich als nächstes?" Was wirst du tun? Wenn du nichts tust - das ist Fehlerversteckung und das Programm könnte einfach nicht funktionieren, ohne die Möglichkeit zu haben, herauszufinden, was passiert ist. Du musst verstehen, was genau du tun wirst, sobald du die Ausnahme gefangen hast, und nur fangen, wenn du es weißt.

36voto

AshleysBrain Punkte 21511

Du musst nicht jeden Block mit try-catches abdecken, denn ein try-catch kann immer noch unbehandelte Ausnahmen abfangen, die in Funktionen weiter unten im Aufrufstapel geworfen werden. Anstatt also jedet Funktion ein try-catch haben zu lassen, kannst du eine auf der Top-Level-Logik deiner Anwendung haben. Zum Beispiel könnte es eine SaveDocument() Top-Level-Routine geben, die viele Methoden aufruft, die wiederum andere Methoden aufrufen usw. Diese Untermethoden benötigen keine eigenen try-catches, denn wenn sie eine Ausnahme werfen, wird diese immer noch vom SaveDocument()-Catch abgefangen.

Dies hat drei Vorteile: Praktisch ist es, weil du einen einzigen Ort hast, um einen Fehler zu melden: die SaveDocument() Catch-Blöcke. Es ist nicht nötig, dies in allen Untermethoden zu wiederholen, und es ist sowieso das, was du willst: einen einzigen Ort, an dem du dem Benutzer eine nützliche Diagnose über etwas geben kannst, das schief gelaufen ist.

Zweitens wird der Speichervorgang abgebrochen, wenn eine Ausnahme geworfen wird. Bei jedem try-catch in den Unterfunktionen wird, wenn eine Ausnahme geworfen wird, in den entsprechenden Catch-Block der Methode gesprungen, die Ausführung verlässt die Funktion, und es geht durch SaveDocument() weiter. Wenn bereits etwas schief gelaufen ist, möchtest du wahrscheinlich genau an dieser Stelle stoppen.

Drittens gehen alle deine Unterfunktionen davon aus, dass jeder Aufruf erfolgreich ist. Falls ein Aufruf fehlschlägt, springt die Ausführung zum Catch-Block und der nachfolgende Code wird nie ausgeführt. Das kann deinen Code viel sauberer machen. Zum Beispiel, hier ist es mit Fehlercodes:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* irgendeine Diagnose */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* irgendeine Diagnose */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* irgendeine Diagnose */
    return;
}

So könnte es mit Ausnahmen geschrieben werden:

// diese werfen bei einem Fehler, werden im SaveDocument-Catch abgefangen
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Jetzt ist viel klarer, was passiert.

Beachte, dass das Schreiben von code, der Ausnahmesicher ist, auf andere Weise knifflig sein kann: Du möchtest keinen Speicherlecks haben, wenn eine Ausnahme geworfen wird. Achte darauf, dass du über RAII, STL Container, Smart Pointer und andere Objekte Bescheid weißt, die ihre Ressourcen in Destruktoren freigeben, da Objekte immer vor Ausnahmen zerstört werden.

2 Stimmen

Prächtige Beispiele. Ja, fange so hoch wie möglich ab, in logischen Einheiten, wie um eine 'Transaktions'-Operation herum, wie laden/speichern/usw. Nichts sieht schlimmer aus als Code, der mit wiederholten, redundanten try-catch-Blöcken durchsetzt ist, die versuchen, jede leicht unterschiedliche Permutation eines Fehlers mit einer leicht unterschiedlichen Nachricht anzuzeigen, wenn sie in Wirklichkeit alle dasselbe enden sollten: Transaktions- oder Programmfehler und Exit! Wenn ein ausnahmebedingter Fehler auftritt, wette ich, dass die meisten Benutzer nur das retten wollen, was sie können, oder zumindest alleine gelassen werden wollen, ohne sich mit 10 Ebenen von Nachrichten darüber befassen zu müssen.

0 Stimmen

So wollte nur sagen, dass dies eine der besten "früh werfen, spät fangen" Erklärungen ist, die ich je gelesen habe: prägnant und die Beispiele veranschaulichen Ihre Punkte perfekt. Danke!

28voto

Herb Sutter schrieb über dieses Problem hier. Auf jeden Fall lesenswert.
Ein Vorgeschmack:

"Das Schreiben von ausnahme-sicherem Code dreht sich im Wesentlichen darum, 'try' und 'catch' an den richtigen Stellen zu schreiben." Diskutieren Sie.

Um es offen zu sagen, diese Aussage spiegelt ein grundlegendes Missverständnis von der Ausnahme-Sicherheit wider. Ausnahmen sind nur eine andere Form der Fehlermeldung, und wir wissen sicherlich, dass das Schreiben von fehlersicherem Code nicht nur darum geht, wo man Rückgabewerte überprüft und Fehlerbedingungen behandelt.

Tatsächlich stellt sich heraus, dass Ausnahme-Sicherheit selten mit dem Schreiben von 'try' und 'catch' zu tun hat - und je seltener, desto besser. Vergessen Sie auch nie, dass die Ausnahme-Sicherheit das Design eines Codes beeinflusst; es ist nie nur ein nachträglicher Gedanke, der mit ein paar zusätzlichen catch-Anweisungen nachgerüstet werden kann, als würde man sie nur als Gewürz hinzufügen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X