Warum sollte ich nicht jeden Block in "try" - "catch" einschließen?

430

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.

Konrad
quelle
4
Wenn ich gewusst hätte, dass eine Methode eine Ausnahme auslösen würde, hätte ich sie zuerst korrigiert. Weil ich nicht weiß, wo und warum Code Ausnahmen auslöst, fange ich sie an der Quelle ab (jede Methode - ein generischer Try-Catch). Zum Beispiel gab mir ein Team eine Codebasis mit einer früheren Datenbank. Viele Spalten fehlten und wurden erst nach dem Hinzufügen von try catch in der SQL-Ebene abgefangen. Zweitens kann ich den Methodennamen und die Nachricht für das Offline-Debugging protokollieren - ansonsten weiß ich nicht, woher sie stammen.
Chakra

Antworten:

340

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.

Mitch Wheat
quelle
12
Es ist auch erwähnenswert, dass tryBlöcke Kosten (in Bezug auf generierten Code) verursachen . Es gibt eine gute Diskussion in Scott Meyers "More Effective C ++".
Nick Meyer
28
Tatsächlich sind tryBlö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).
Blindy
31
@Blindly: Der Top-Exception-Handler ist nicht da, um die Ausnahme zu behandeln, sondern um laut zu schreien, dass es eine nicht behandelte Ausnahme gab, seine Nachricht zu geben und das Programm auf ordnungsgemäße Weise zu beenden (1 anstelle eines Aufrufs an zurückgeben terminate) . Es ist eher ein Sicherheitsmechanismus. Auch try/catchsind 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 von try/catchnur einem erneuten Werfen nicht kostenlos ist.
Matthieu M.
17
Ich bin nicht der Meinung, dass Sie immer mit einer nicht erfassten Ausnahme abstürzen sollten. Modernes Software-Design ist sehr unterteilt. Warum sollten Sie also den Rest der Anwendung (und vor allem den Benutzer!) Bestrafen, nur weil ein Fehler aufgetreten ist? Wenn Sie das absolut Letzte, was Sie tun möchten, zum Absturz bringen, versuchen Sie zumindest, dem Benutzer ein kleines Codefenster zu geben, in dem er Arbeit sparen kann, auch wenn auf den Rest der Anwendung nicht zugegriffen werden kann.
Kendall Helmstetter Gelner
21
Kendall: Wenn eine Ausnahme zu einem Top-Level-Handler gelangt, befindet sich Ihre Anwendung per Definition in einem undefinierten Zustand. Obwohl in bestimmten Fällen die Aufbewahrung der Benutzerdaten von Nutzen sein kann (die Wiederherstellung von Dokumenten in Word ist denkbar), sollte das Programm keine Dateien überschreiben oder in eine Datenbank übertragen.
Hugh Brackett
136

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" .

  1. "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ötige try/ catchLogik.

  2. "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" .

  3. "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/ catchblocken ü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.

D. Shawley
quelle
6
# 1 ist neu für mich. +1 dafür. Außerdem möchte ich eine häufige Ausnahme zu Nummer 2 erwähnen. Wenn Sie häufig eine Bibliothek entwerfen, möchten Sie interne Ausnahmen in etwas übersetzen, das von Ihrer Bibliotheksschnittstelle angegeben wird, um die Kopplung zu verringern (dies ist möglicherweise das, was Sie meinen durch "föderierte Lösung", aber ich bin mit diesem Begriff nicht vertraut).
Rmeador
3
Grundsätzlich, was Sie gesagt haben: parashift.com/c++-faq-lite/exceptions.html#faq-17.13
Björn Pollex
1
# 2, wo es kein Code-Geruch ist, aber Sinn macht, kann verbessert werden, indem die alte Ausnahme als verschachtelte beibehalten wird.
Deduplikator
1
In Bezug auf # 1: std :: uncaught_exception () sagt Ihnen, dass es im Flug eine nicht erfasste Ausnahme gibt, aber mit AFAIK können Sie nur mit einer catch () -Klausel bestimmen, was diese Ausnahme tatsächlich ist. Während Sie also die Tatsache protokollieren können, dass Sie einen Bereich aufgrund einer nicht erfassten Ausnahme verlassen, können Sie nur mit einem beiliegenden Try / Catch Details protokollieren. Richtig?
Jeremy
@ Jeremy - du bist richtig. Normalerweise protokolliere ich die Ausnahmedetails, wenn ich die Ausnahme behandle. Es ist sehr nützlich, eine Spur der dazwischenliegenden Frames zu haben. Im Allgemeinen müssen Sie die Thread-ID oder einen identifizierenden Kontext protokollieren, um die Protokollzeilen zu korrelieren. Ich habe eine Loggerähnliche Klasse verwendet log4j.Logger, die die Thread-ID in jeder Protokollzeile enthält, und eine Warnung im Destruktor ausgegeben, wenn eine Ausnahme aktiv war.
D. Shawley
48

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.

scharfer Zahn
quelle
29

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 von SaveDocument()'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:

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 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.

AshleysBrain
quelle
2
Herrliche Beispiele. Ja, fangen Sie so hoch wie möglich in logischen Einheiten, z. B. um eine 'Transaktions'-Operation wie Laden / Speichern / etc. Nichts sieht schlimmer als Code gespickt mit sich wiederholend, überflüssig try- catchBlö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.
underscore_d
Ich wollte nur sagen, dass dies eine der besten Erklärungen ist, die ich je gelesen habe: Prägnant und die Beispiele veranschaulichen Ihre Punkte perfekt. Vielen Dank!
corderazo00
27

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

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

Diese Aussage spiegelt ein grundlegendes Missverständnis der Ausnahmesicherheit wider. Ausnahmen sind nur eine andere Form der Fehlerberichterstattung, und wir wissen sicherlich, dass es beim Schreiben von fehlersicherem Code nicht nur darum geht, Rückkehrcodes zu überprüfen und Fehlerbedingungen zu behandeln.

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

Tadeusz Kopec
quelle
15

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_castvon 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.

Kristopher Johnson
quelle
12

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.

Sternenblau
quelle
9

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).

Donal Fellows
quelle
2
@ KeithB: Ich würde es als zweitbeste Strategie betrachten. Es ist besser, wenn Sie das Protokoll auf andere Weise schreiben können.
David Thornley
1
@KeithB: Es ist eine Strategie "besser als nichts in einer Bibliothek". "Fangen, loggen, richtig damit umgehen" ist wo möglich besser. (Ja, ich weiß, dass es nicht immer möglich ist.)
Donal Fellows
6

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.

Bananeweizen
quelle
5

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:

int f()
{
    // Do stuff

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

int condition = f();

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

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.

Mike Bailey
quelle
7
Das ist eine Strategie, aber viele Leute empfehlen, niemals Fehlercodes oder Fehler- / Erfolgsstatus von Funktionen zurückzugeben, sondern stattdessen Ausnahmen zu verwenden. Ausnahmebasierte Fehlerbehandlung ist häufig einfacher zu lesen als fehlercodebasierter Code. (Ein Beispiel finden Sie in der Antwort von AshleysBrain auf diese Frage.) Denken Sie auch immer daran, dass viele Informatikprofessoren nur sehr wenig Erfahrung mit dem Schreiben von echtem Code haben.
Kristopher Johnson
1
-1 @Sagelika Ihre Antwort besteht darin, Ausnahmen zu vermeiden, sodass kein Try-Catch erforderlich ist.
Vicente Botet Escriba
3
@Kristopher: Andere große Nachteile des Rückkehrcodes sind, dass man leicht vergisst, einen Rückkehrcode zu überprüfen, und dass der Anruf nicht unbedingt der beste Ort ist, um das Problem zu lösen.
David Thornley
ehh, es kommt darauf an, aber in vielen Fällen (abgesehen von Leuten, die werfen, wenn sie es wirklich nicht sollten) sind Ausnahmen aus so vielen Gründen den Rückgabecodes überlegen. In den meisten Fällen ist die Vorstellung, dass Ausnahmen die Leistung beeinträchtigen, ein großes Problem.
Unterstrich_d
3

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.

blauer Hund
quelle
2

Ich möchte dieser Diskussion hinzufügen, dass es seit C ++ 11 sehr sinnvoll ist, solange jeder catchBlock rethrowbis 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_exceptionundstd::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:

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"
GPMueller
quelle
2

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.

user2502917
quelle
1
Das Umwickeln jedes Blocks scheint übertrieben. Es macht Ihren Code schnell aufgebläht und schmerzhaft zu lesen. Das Protokollieren eines Stacktraces von einer Ausnahme auf höheren Ebenen zeigt Ihnen, wo das Problem aufgetreten ist und dass in Kombination mit dem Fehler selbst im Allgemeinen genügend Informationen vorhanden sind, um fortzufahren. Ich wäre gespannt, wo Sie das nicht ausreichend fanden. Nur damit ich die Erfahrung eines anderen sammeln kann.
user441521
1
"Ausnahmen sind 100- bis 1000-mal langsamer als normaler Code und sollten niemals erneut ausgelöst werden" - diese Aussage trifft auf die meisten modernen Compiler und Hardware nicht zu.
Mitch Wheat
Es scheint übertrieben zu sein und erfordert ein wenig Eingabe, ist jedoch die einzige Möglichkeit, Analysen für Ausnahmen durchzuführen, um die größten Fehler zuerst zu finden und zu beheben, einschließlich zeitweise auftretender Fehler in der Produktion. Der catch-Block behandelt bei Bedarf bestimmte Fehler und verfügt über eine einzelne Codezeile, die protokolliert.
user2502917
Nein, Ausnahmen sind sehr langsam. Die Alternative sind Rückkehrcodes, Objekte oder Variablen. Siehe diesen Stapelüberlauf-Beitrag ... "Ausnahmen sind mindestens 30.000 Mal langsamer als Rückkehrcodes" stackoverflow.com/questions/891217/…
user2502917
1

Neben den oben genannten Ratschlägen verwende ich persönlich einige try + catch + throw; aus folgendem Grund:

  1. An der Grenze eines anderen Codierers verwende ich try + catch + throw in dem von mir selbst geschriebenen Code, bevor die Ausnahme an den von anderen geschriebenen Aufrufer ausgelöst wird. Dies gibt mir die Möglichkeit, einen in meinem Code aufgetretenen Fehlerzustand zu erkennen Dieser Ort ist viel näher an dem Code, der anfänglich die Ausnahme auslöst. Je näher, desto leichter ist es, den Grund zu finden.
  2. An der Grenze der Module, obwohl verschiedene Module geschrieben werden können, meine gleiche Person.
  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 Ausnahmen aus, daher ist dieser Fall in C ++ selten. Aber C # hat in C # eine riesige Bibliothek und eine ausgereifte Ausnahmehierarchie. Der C # -Bibliothekscode löst Unmengen von Ausnahmen aus. Theoretisch sollte ich (und Sie) alle Ausnahmen von der von Ihnen aufgerufenen Funktion kennen und den Grund / Fall dafür kennen Diese Ausnahme wird ausgelöst und Sie wissen, wie Sie mit ihnen umgehen (vorbeigehen oder sie an Ort und Stelle fangen und handhaben). Leider ist es in der Realität sehr schwierig, alles über die möglichen Ausnahmen zu wissen, bevor ich eine Codezeile schreibe. Also fange ich alles ein und lasse meinen Code laut sprechen, indem ich mich (in der Produktumgebung) anmelde / den Dialog (in der Entwicklungsumgebung) bestätige, wenn wirklich eine Ausnahme auftritt. Auf diese Weise füge ich nach und nach Code zur Ausnahmebehandlung hinzu. Ich weiß, dass es mit guten Ratschlägen in Konflikt steht, aber in Wirklichkeit funktioniert es für mich und ich kenne keinen besseren Weg für dieses Problem.
zhaorufei
quelle
1

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.

user875234
quelle
-2

Sie müssen nicht jeden Teil Ihres Codes vertuschen try-catch. Die Hauptverwendung des try-catchBlocks ist die Fehlerbehandlung und das Erhalten von Fehlern / Ausnahmen in Ihrem Programm. Einige Verwendung von try-catch-

  1. Sie können diesen Block verwenden, wenn Sie eine Ausnahme behandeln möchten, oder Sie können einfach sagen, dass der Block des geschriebenen Codes eine Ausnahme auslösen kann.
  2. Wenn Sie Ihre Objekte sofort nach ihrer Verwendung entsorgen möchten, können Sie try-catchblock verwenden.
Amit Kumawat
quelle
1
"Wenn Sie Ihre Objekte sofort nach ihrer Verwendung entsorgen möchten, können Sie den Try-Catch-Block verwenden." Wollten Sie damit RAII / minimale Objektlebensdauer fördern? Wenn ja, ist try/ catchvollstä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 umwickeln try/ catches sei denn , Sie wirklich brauchen catchetwas, natürlich.
underscore_d
# 2 - Das Entsorgen von Objekten (die manuell erstellt wurden) in der Ausnahme erscheint mir seltsam. Dies kann zweifellos in einigen Sprachen nützlich sein, aber im Allgemeinen tun Sie dies in einem Versuch / schließlich "innerhalb des Versuchs / Außer-Blocks" und nicht speziell im Ausnahmeblock selbst - da das Objekt selbst möglicherweise in erster Linie die Ursache für die Ausnahme war und somit eine weitere Ausnahme und möglicherweise einen Absturz verursacht.
TS