Ich war immer der Überzeugung, dass es rücksichtslos ist, diesen Aufruf nicht mit einem sinnvollen try-Block zu schützen, wenn eine Methode eine Ausnahme auslösen kann.
Ich habe gerade geschrieben: ' Du solltest IMMER Anrufe einschließen, die versuchen können, Blöcke zu fangen. 'auf diese Frage und wurde gesagt, dass es' bemerkenswert schlechter Rat 'war - ich würde gerne verstehen, warum.
Antworten:
Eine Methode sollte nur dann eine Ausnahme abfangen, wenn sie auf sinnvolle Weise damit umgehen kann.
Andernfalls geben Sie es weiter, in der Hoffnung, dass eine Methode höher im Aufrufstapel Sinn ergibt.
Wie andere angemerkt haben, empfiehlt es sich, einen nicht behandelten Ausnahmebehandler (mit Protokollierung) auf der höchsten Ebene des Aufrufstapels zu haben, um sicherzustellen, dass schwerwiegende Fehler protokolliert werden.
quelle
try
Blöcke Kosten (in Bezug auf generierten Code) verursachen . Es gibt eine gute Diskussion in Scott Meyers "More Effective C ++".try
Blöcke in jedem modernen C-Compiler frei, diese Informationen sind mit Nick datiert. Ich bin auch nicht einverstanden mit einem Ausnahmebehandler der obersten Ebene, da Sie Ortsinformationen verlieren (der tatsächliche Ort, an dem die Anweisung fehlgeschlagen ist).terminate
) . Es ist eher ein Sicherheitsmechanismus. Auchtry/catch
sind mehr oder weniger kostenlos, wenn es keine Ausnahme gibt. Wenn es eine Vermehrung gibt, verbraucht sie jedes Mal Zeit, wenn sie geworfen und gefangen wird, so dass eine Kette vontry/catch
nur einem erneuten Werfen nicht kostenlos ist.Wie Mitch und andere sagten, sollten Sie keine Ausnahme machen, die Sie nicht in irgendeiner Weise handhaben möchten. Sie sollten überlegen, wie die Anwendung Ausnahmen systematisch behandelt, wenn Sie sie entwerfen. Dies führt normalerweise dazu, dass die Fehlerbehandlungsebenen auf den Abstraktionen basieren. Sie behandeln beispielsweise alle SQL-bezogenen Fehler in Ihrem Datenzugriffscode, sodass der Teil der Anwendung, der mit Domänenobjekten interagiert, nicht der Tatsache ausgesetzt ist, dass diese vorhanden sind ist irgendwo eine DB unter der Haube.
Es gibt ein paar verwandte Code-Gerüche, die Sie unbedingt vermeiden möchten, zusätzlich zum Geruch "Alles überall fangen" .
"catch, log, rethrow" : Wenn Sie eine bereichsbasierte Protokollierung wünschen, schreiben Sie eine Klasse, die eine Protokollanweisung in ihrem Destruktor ausgibt, wenn der Stapel aufgrund einer Ausnahme (ala
std::uncaught_exception()
) abgewickelt wird . Alles, was Sie tun müssen, ist eine Protokollierungsinstanz in dem Bereich zu deklarieren, an dem Sie interessiert sind, und voila, Sie haben Protokollierung und keine unnötigetry
/catch
Logik."catch, throw übersetzt" : Dies deutet normalerweise auf ein Abstraktionsproblem hin. Wenn Sie keine Verbundlösung implementieren, bei der Sie mehrere spezifische Ausnahmen in eine allgemeinere übersetzen, haben Sie wahrscheinlich eine unnötige Abstraktionsebene ... und sagen nicht, dass "ich sie morgen brauchen könnte" .
"fangen, aufräumen, neu werfen" : das ist einer meiner Lieblingstiere. Wenn Sie viel davon sehen, sollten Sie die Techniken " Ressourcenerfassung ist Initialisierung" anwenden und den Bereinigungsteil im Destruktor einer Hausmeisterobjektinstanz platzieren .
Ich halte Code, der mit
try
/catch
blocken übersät ist , für ein gutes Ziel für die Codeüberprüfung und das Refactoring. Dies weist darauf hin, dass entweder die Ausnahmebehandlung nicht gut verstanden wird oder der Code zu einer Amöbe geworden ist und dringend überarbeitet werden muss.quelle
Logger
ähnliche Klasse verwendetlog4j.Logger
, die die Thread-ID in jeder Protokollzeile enthält, und eine Warnung im Destruktor ausgegeben, wenn eine Ausnahme aktiv war.Weil die nächste Frage lautet: "Ich habe eine Ausnahme festgestellt. Was mache ich als Nächstes?" Was wirst du machen? Wenn Sie nichts tun - das ist ein Fehler, der sich versteckt, und das Programm könnte "einfach nicht funktionieren", ohne die Chance zu haben, herauszufinden, was passiert ist. Sie müssen verstehen, was genau Sie tun werden, wenn Sie die Ausnahme abgefangen haben, und nur abfangen, wenn Sie es wissen.
quelle
Sie müssen nicht jeden Block mit Try-Catches abdecken, da ein Try-Catch immer noch nicht behandelte Ausnahmen abfangen kann, die in Funktionen weiter unten im Aufrufstapel ausgelöst werden. Anstatt dass jede Funktion einen Try-Catch hat, können Sie eine Logik auf der obersten Ebene Ihrer Anwendung verwenden. Zum Beispiel könnte es eine
SaveDocument()
Routine der obersten Ebene geben, die viele Methoden aufruft, die andere Methoden usw. aufrufen. Diese Untermethoden benötigen keine eigenen Try-Catches, denn wenn sie werfen, wird sie immer noch vonSaveDocument()
's catch' abgefangen .Dies ist aus drei Gründen hilfreich: Es ist praktisch, da Sie nur einen Ort haben, an dem Sie einen Fehler melden können: die
SaveDocument()
Catch-Blöcke. Es ist nicht erforderlich, dies in allen Untermethoden zu wiederholen, und es ist sowieso das, was Sie wollen: ein einziger Ort, an dem der Benutzer eine nützliche Diagnose für etwas erhält, das schief gelaufen ist.Zweitens wird das Speichern abgebrochen, wenn eine Ausnahme ausgelöst wird. Bei jedem Teilverfahren try Fang, wenn eine Ausnahme ausgelöst wird, erhalten Sie zu dieser Methode der catch - Block in der Ausführung verläßt die Funktion, und es trägt auf durch
SaveDocument()
. Wenn bereits etwas schief gelaufen ist, möchten Sie wahrscheinlich genau dort aufhören.Drittens können alle Ihre Untermethoden davon ausgehen, dass jeder Aufruf erfolgreich ist . Wenn ein Aufruf 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:
So könnte das mit Ausnahmen geschrieben werden:
Jetzt ist viel klarer, was passiert.
Hinweis: Ausnahmesicherer Code kann auf andere Weise schwieriger zu schreiben sein: Sie möchten keinen Speicher verlieren, wenn eine Ausnahme ausgelöst wird. Stellen Sie sicher, dass Sie über RAII , STL-Container, intelligente Zeiger und andere Objekte informiert sind , die ihre Ressourcen in Destruktoren freigeben, da Objekte immer vor Ausnahmen zerstört werden.
quelle
try
-catch
Blöcke , die versuchen , auf Fahne jeder etwas andere Permutation eines Fehlers mit einer etwas anderen Nachricht, wenn sie in Wirklichkeit sollten sie alle das gleiche Ende: Transaktion oder Programmfehler und verlassen! Wenn ein ausnahmetauglicher Fehler auftritt, möchte ich wetten, dass die meisten Benutzer nur das retten möchten, was sie können, oder zumindest allein gelassen werden möchten, ohne sich mit 10 Nachrichtenebenen darüber befassen zu müssen.Herb Sutter schrieb über dieses Problem hier . Auf jeden Fall lesenswert.
Ein Teaser:
quelle
Wie in anderen Antworten angegeben, sollten Sie eine Ausnahme nur abfangen, wenn Sie eine sinnvolle Fehlerbehandlung dafür durchführen können.
In der Frage , aus der Ihre Frage hervorgegangen ist, fragt der Fragesteller beispielsweise, ob es sicher ist, Ausnahmen für a
lexical_cast
von einer Ganzzahl zu einer Zeichenfolge zu ignorieren . Eine solche Besetzung sollte niemals scheitern. Wenn es fehlgeschlagen ist, ist im Programm ein furchtbarer Fehler aufgetreten. Was könnten 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 vertrauen kann. Daher kann es am sichersten sein, die Ausnahme nicht zu behandeln.quelle
Wenn Sie Ausnahmen immer sofort im Aufrufer einer Methode behandeln, die eine Ausnahme auslösen kann, werden Ausnahmen unbrauchbar und Sie sollten besser Fehlercodes verwenden.
Der springende Punkt bei Ausnahmen ist, dass sie nicht in jeder Methode in der Aufrufkette behandelt werden müssen.
quelle
Der beste Rat, den ich gehört habe, ist, dass Sie Ausnahmen nur an Stellen abfangen sollten, an denen Sie vernünftigerweise etwas gegen den außergewöhnlichen Zustand unternehmen können, und dass "Abfangen, Protokollieren und Freigeben" keine gute Strategie ist (wenn dies gelegentlich in Bibliotheken unvermeidbar ist).
quelle
Ich stimme der grundlegenden 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 Ausnahme nicht behandeln. Jemand anderes wird dies auf dem Stapel tun." Nach meiner Erfahrung ist das eine schlechte Ausrede, nicht zu denken Ausnahmebehandlung im aktuell entwickelten Code , sodass die Ausnahmebehandlung das Problem einer anderen Person oder später darstellt.
Dieses Problem wächst dramatisch in der verteilten Entwicklung, in der Sie möglicherweise eine von einem Mitarbeiter 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 hätte gehandhabt werden können.
quelle
Der Rat meines Informatikprofessors war einmal: "Verwenden Sie Try and Catch-Blöcke nur, wenn es nicht möglich ist, den Fehler mit Standardmitteln zu behandeln."
Als Beispiel erzählte er uns, dass, wenn ein Programm an einem Ort, an dem es nicht möglich ist, Folgendes zu tun, auf ein ernstes Problem stößt:
Dann sollten Sie try, catch-Blöcke verwenden. Obwohl Sie Ausnahmen verwenden können, um dies zu handhaben, wird dies im Allgemeinen nicht empfohlen, da Ausnahmen in Bezug auf die Leistung teuer sind.
quelle
Wenn Sie das Ergebnis jeder Funktion testen möchten, verwenden Sie Rückkehrcodes.
Der Zweck von Ausnahmen besteht darin, dass Sie die Ergebnisse WENIGER häufig testen können. Die Idee ist, außergewöhnliche (ungewöhnliche, seltenere) Bedingungen von Ihrem gewöhnlichen Code zu trennen. Dies hält den normalen Code sauberer und einfacher - kann aber dennoch mit diesen außergewöhnlichen Bedingungen umgehen.
In gut entworfenem Code können tiefere Funktionen ausgelöst und höhere Funktionen abgefangen werden. Der Schlüssel ist jedoch, dass viele Funktionen "dazwischen" überhaupt nicht mit außergewöhnlichen Bedingungen umgehen müssen. Sie müssen nur "ausnahmesicher" sein, was nicht bedeutet, dass sie fangen müssen.
quelle
Ich möchte dieser Diskussion hinzufügen, dass es seit C ++ 11 sehr sinnvoll ist, solange jeder
catch
Blockrethrow
bis zu dem Punkt, an dem er behandelt werden kann / sollte, die Ausnahme darstellt. Auf diese Weise kann eine Rückverfolgung erzeugt werden . Ich glaube daher, dass die vorherigen Meinungen teilweise veraltet sind.Verwenden Sie
std::nested_exception
undstd::throw_with_nested
Hier und hier wird auf StackOverflow beschrieben , wie dies erreicht werden kann.
Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so aussehen würde:
quelle
Ich hatte die "Gelegenheit", mehrere Projekte zu retten, und Führungskräfte ersetzten das gesamte Entwicklerteam, weil die App zu viele Fehler aufwies und die Benutzer die Probleme und das Durcheinander satt hatten. Diese Codebasen hatten alle eine zentralisierte Fehlerbehandlung auf App-Ebene, wie in der Antwort mit der höchsten Bewertung beschrieben. Wenn diese Antwort die beste Vorgehensweise ist, warum hat sie dann nicht funktioniert und dem vorherigen Entwicklerteam ermöglicht, Probleme zu lösen? Vielleicht funktioniert es manchmal nicht? In den obigen Antworten wird nicht erwähnt, wie lange Entwickler damit verbringen, einzelne Probleme zu beheben. Wenn die Zeit zur Behebung von Problemen die Schlüsselmetrik ist, ist es besser, Code mit try..catch-Blöcken zu instrumentieren.
Wie hat mein Team die Probleme behoben, ohne die Benutzeroberfläche wesentlich zu ändern? Einfach, jede Methode wurde mit try..catch blockiert und alles wurde zum Zeitpunkt des Fehlers mit dem Methodennamen, den Methodenparameterwerten, die in einer Zeichenfolge verkettet waren, die zusammen mit der Fehlermeldung, der Fehlermeldung, dem App-Namen, dem Datum, protokolliert wurde, protokolliert. und Version. Mit diesen Informationen können Entwickler die Fehler analysieren, um die Ausnahme zu identifizieren, die am häufigsten auftritt! Oder der Namespace mit der höchsten Anzahl von Fehlern. Es kann auch überprüft werden, ob ein in einem Modul auftretender Fehler ordnungsgemäß behandelt und nicht aus mehreren Gründen verursacht wird.
Ein weiterer Vorteil davon ist, dass Entwickler einen Haltepunkt in der Fehlerprotokollierungsmethode festlegen können. Mit einem Haltepunkt und einem einzigen Klick auf die Debug-Schaltfläche "Aussteigen" befinden sie sich in der Methode, die mit vollem Zugriff auf den tatsächlichen Fehler fehlgeschlagen ist Objekte zum Zeitpunkt des Ausfalls, bequem im unmittelbaren Fenster verfügbar. Es macht das Debuggen sehr einfach und ermöglicht das Zurückziehen der Ausführung zum Anfang der Methode, um das Problem zu duplizieren und die genaue Zeile zu finden. Ermöglicht die zentralisierte Ausnahmebehandlung einem Entwickler, eine Ausnahme in 30 Sekunden zu replizieren? Nein.
Die Anweisung "Eine Methode sollte eine Ausnahme nur abfangen, wenn sie auf vernünftige Weise damit umgehen kann." Dies bedeutet, dass Entwickler jeden Fehler vorhersagen können oder auf ihn stoßen werden, der vor der Veröffentlichung auftreten kann. Wenn dies eine Top-Ebene wäre, würde kein App-Ausnahmebehandler benötigt und es würde keinen Markt für elastische Suche und Logstash geben.
Mit diesem Ansatz können Entwickler auch zeitweise auftretende Probleme in der Produktion finden und beheben! Möchten Sie ohne Debugger in der Produktion debuggen? Oder möchten Sie lieber Anrufe entgegennehmen und E-Mails von verärgerten Benutzern erhalten? Auf diese Weise können Sie Probleme beheben, bevor andere es wissen, und ohne E-Mail, IM oder Slack mit Support, da alles, was zur Behebung des Problems erforderlich ist, genau dort ist. 95% der Ausgaben müssen nie reproduziert werden.
Um ordnungsgemäß zu funktionieren, muss es mit einer zentralen Protokollierung kombiniert werden, die den Namespace / das Modul, den Klassennamen, die Methode, die Eingaben und die Fehlermeldung erfasst und in einer Datenbank speichert, damit es aggregiert werden kann, um hervorzuheben, welche Methode am häufigsten fehlschlägt zuerst behoben.
Manchmal entscheiden sich Entwickler dafür, Ausnahmen aus einem Catch-Block in den Stapel zu werfen, aber dieser Ansatz ist 100-mal langsamer als normaler Code, der nicht auslöst. Das Fangen und Freigeben mit Protokollierung wird bevorzugt.
Diese Technik wurde verwendet, um eine App schnell zu stabilisieren, die für die meisten Benutzer in einem Fortune 500-Unternehmen, das von 12 Entwicklern über 2 Jahre entwickelt wurde, stündlich fehlschlug. Mit diesen 3000 verschiedenen Ausnahmen wurden innerhalb von 4 Monaten verschiedene Ausnahmen identifiziert, behoben, getestet und bereitgestellt. Dies ergibt einen Durchschnitt von 4 Monaten alle 15 Minuten.
Ich bin damit einverstanden, dass es keinen Spaß macht, alles einzugeben, was zur Instrumentierung des Codes erforderlich ist, und ich bevorzuge es, nicht den sich wiederholenden Code zu betrachten, aber das Hinzufügen von 4 Codezeilen zu jeder Methode lohnt sich auf lange Sicht.
quelle
Neben den oben genannten Ratschlägen verwende ich persönlich einige try + catch + throw; aus folgendem Grund:
quelle
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 Aufgaben ausführen, multiplizieren Sie die Komplexität und fügen sie nicht hinzu.
Mit anderen Worten, eine Methode, die in einen Try-Catch eingeschlossen ist, hat zwei mögliche Ergebnisse. Sie haben das Ergebnis ohne Ausnahme und das Ergebnis mit Ausnahme. Wenn Sie mit vielen Methoden arbeiten, sprengt dies exponentiell unverständlich.
Exponentiell, denn wenn jede Methode auf zwei verschiedene Arten verzweigt, wird jedes Mal, wenn Sie eine andere Methode aufrufen, die vorherige Anzahl potenzieller Ergebnisse quadriert. Wenn Sie fünf Methoden aufgerufen haben, sind mindestens 256 mögliche Ergebnisse möglich. Vergleichen Sie dies nicht bei jeder einzelnen Methode versuchen / fangen, und Sie haben nur einen Pfad, dem Sie folgen müssen.
So sehe ich das im Grunde. 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 genommen undefiniert wird.
Kurz gesagt, try / catches erschweren das Verständnis des Codes erheblich.
quelle
Sie müssen nicht jeden Teil Ihres Codes vertuschen
try-catch
. Die Hauptverwendung destry-catch
Blocks ist die Fehlerbehandlung und das Erhalten von Fehlern / Ausnahmen in Ihrem Programm. Einige Verwendung vontry-catch
-try-catch
block verwenden.quelle
try
/catch
vollständig / orthogonal davon getrennt. Wenn Sie in einem kleineren Umfang Objekte entsorgen wollen, können Sie einfach ein neues öffnen{ Block likeThis; /* <- that object is destroyed here -> */ }
- keine Notwendigkeit , dies zu umwickelntry
/catch
es sei denn , Sie wirklich brauchencatch
etwas, natürlich.