Ich kann mehrere Posts sehen, in denen die Bedeutung der Behandlung von Ausnahmen an zentraler Stelle oder an Prozessgrenzen als gute Praxis hervorgehoben wurde, anstatt jeden Codeblock um try / catch herum zu verunreinigen. Ich bin der festen Überzeugung, dass die meisten von uns die Wichtigkeit verstehen, aber ich sehe, dass die Leute immer noch mit Catch-Log-Rethrow-Anti-Pattern enden, hauptsächlich, weil sie, um die Fehlerbehebung in Ausnahmefällen zu vereinfachen, mehr kontextspezifische Informationen protokollieren möchten (Beispiel: Methodenparameter) übergeben) und der Weg ist, die Methode um try / catch / log / rethrow zu wickeln.
public static bool DoOperation(int num1, int num2)
{
try
{
/* do some work with num1 and num2 */
}
catch (Exception ex)
{
logger.log("error occured while number 1 = {num1} and number 2 = {num2}");
throw;
}
}
Gibt es einen richtigen Weg, um dies zu erreichen und gleichzeitig die bewährten Verfahren für die Ausnahmebehandlung beizubehalten? Ich habe dafür von AOP-Frameworks wie PostSharp gehört, möchte aber wissen, ob mit diesen AOP-Frameworks Nachteile oder erhebliche Leistungskosten verbunden sind.
Vielen Dank!
quelle
Antworten:
Das Problem ist nicht der lokale Catch-Block, das Problem ist das Protokoll und der Rethrow . Behandeln Sie die Ausnahmebedingung oder schließen Sie sie mit einer neuen Ausnahmebedingung ab, die zusätzlichen Kontext hinzufügt, und lösen Sie diese aus. Andernfalls werden Sie für dieselbe Ausnahme auf mehrere doppelte Protokolleinträge stoßen.
Die Idee hier ist, die Fähigkeit zu verbessern , Ihre Anwendung zu debuggen.
Beispiel 1: Behandeln Sie es
Wenn Sie die Ausnahme behandeln, können Sie die Wichtigkeit des Ausnahmeprotokolleintrags leicht herabstufen, und es gibt keinen Grund, diese Ausnahme in der gesamten Kette durchzulaufen. Es ist schon erledigt.
Das Behandeln einer Ausnahme kann das Informieren von Benutzern über ein Problem, das Protokollieren des Ereignisses oder das einfache Ignorieren des Ereignisses umfassen.
HINWEIS: Wenn Sie eine Ausnahme absichtlich ignorieren, empfehle ich, in der leeren catch-Klausel einen Kommentar anzugeben, der klar angibt, warum. Dies lässt zukünftige Betreuer wissen, dass es sich nicht um einen Fehler oder eine verzögerte Programmierung handelte. Beispiel:
Beispiel 2: Füge zusätzlichen Kontext hinzu und wirf
Das Hinzufügen von zusätzlichem Kontext (wie Zeilennummer und Dateiname im Parsing-Code) kann dazu beitragen, das Debuggen von Eingabedateien zu verbessern - vorausgesetzt, das Problem ist vorhanden. Dies ist eine Art Sonderfall. Wenn Sie eine Ausnahme in eine "ApplicationException" umbrechen, um sie umzubenennen, hilft dies nicht beim Debuggen. Stellen Sie sicher, dass Sie zusätzliche Informationen hinzufügen.
Beispiel # 3: Mach nichts mit der Ausnahme
In diesem letzten Fall lassen Sie die Ausnahme einfach zu, ohne sie zu berühren. Der Ausnahmebehandler auf der äußersten Ebene kann die Protokollierung durchführen. Die
finally
Klausel wird verwendet, um sicherzustellen, dass alle von Ihrer Methode benötigten Ressourcen bereinigt werden. Hier kann jedoch nicht protokolliert werden, dass die Ausnahme ausgelöst wurde.quelle
Ich glaube nicht, dass lokale Fänge ein Anti-Pattern sind. Wenn ich mich richtig erinnere, wird es tatsächlich in Java erzwungen!
Was für mich bei der Implementierung der Fehlerbehandlung ausschlaggebend ist, ist die Gesamtstrategie. Möglicherweise möchten Sie einen Filter, der alle Ausnahmen an der Servicegrenze abfängt, und Sie möchten sie möglicherweise manuell abfangen - beides ist in Ordnung, solange eine Gesamtstrategie vorliegt, die den Codierungsstandards Ihres Teams entspricht.
Persönlich fange ich gerne Fehler in einer Funktion ab, wenn ich eine der folgenden Möglichkeiten habe:
Wenn es nicht einer dieser Fälle ist, füge ich kein lokales try / catch hinzu. Wenn dies der Fall ist, kann ich je nach Szenario die Ausnahme behandeln (z. B. eine TryX-Methode, die false zurückgibt) oder erneut auslösen, damit die Ausnahme von der globalen Strategie behandelt wird.
Beispielsweise:
Oder ein Rethrow-Beispiel:
Anschließend wird der Fehler oben im Stapel abgefangen und dem Benutzer eine benutzerfreundliche Meldung angezeigt.
Welchen Ansatz Sie auch wählen, es lohnt sich immer, Komponententests für diese Szenarien zu erstellen, damit Sie sicherstellen können, dass sich die Funktionalität nicht ändert, und den Projektfluss zu einem späteren Zeitpunkt stören.
Sie haben nicht erwähnt, in welcher Sprache Sie arbeiten, sind aber ein .NET-Entwickler und haben dies zu oft gesehen, um es nicht zu erwähnen.
SCHREIBE NICHT:
Verwenden:
Ersteres setzt die Stapelspur zurück und macht Ihren Top-Level-Fang völlig unbrauchbar!
TLDR
Lokales Fangen ist kein Anti-Pattern, es kann oft Teil eines Designs sein und dabei helfen, dem Fehler zusätzlichen Kontext hinzuzufügen.
quelle
Das hängt sehr von der Sprache ab. C ++ bietet beispielsweise keine Stack-Traces in Ausnahmefehlermeldungen. Daher kann es hilfreich sein, die Ausnahme durch häufiges Abfangen und erneutes Auslösen von Protokollen zu verfolgen. Im Gegensatz dazu bieten Java und ähnliche Sprachen sehr gute Stack-Traces, obwohl das Format dieser Stack-Traces möglicherweise nicht sehr konfigurierbar ist. Das Abfangen und Wiederherstellen von Ausnahmen in diesen Sprachen ist völlig sinnlos, es sei denn, Sie können wirklich einen wichtigen Kontext hinzufügen (z. B. das Verbinden einer einfachen SQL-Ausnahme mit dem Kontext einer Geschäftslogikoperation).
Jede Fehlerbehandlungsstrategie, die durch Reflektion implementiert wird, ist fast notwendigerweise weniger effizient als die in die Sprache integrierte Funktionalität. Darüber hinaus verursacht die allgegenwärtige Protokollierung unvermeidbaren Leistungsaufwand. Sie müssen also den Informationsfluss, den Sie erhalten, gegen andere Anforderungen für diese Software abwägen. Lösungen wie PostSharp, die auf Instrumenten auf Compilerebene basieren, sind im Allgemeinen viel besser als die Laufzeitreflexion.
Ich persönlich glaube, dass es nicht hilfreich ist, alles aufzuzeichnen, da es eine Menge irrelevanter Informationen enthält. Ich bin daher skeptisch gegenüber automatisierten Lösungen. Bei einem guten Protokollierungsrahmen kann es ausreichen, eine vereinbarte Codierungsrichtlinie zu haben, in der erläutert wird, welche Art von Informationen Sie protokollieren möchten und wie diese Informationen formatiert werden sollten. Sie können dann die Protokollierung dort hinzufügen, wo es darauf ankommt.
Die Anmeldung an der Geschäftslogik ist viel wichtiger als die Anmeldung an den Dienstprogrammfunktionen. Durch das Sammeln von Stack-Traces realer Absturzberichte (die nur auf der obersten Ebene eines Prozesses protokolliert werden müssen) können Sie Bereiche des Codes ermitteln, in denen die Protokollierung den größten Wert hätte.
quelle
Wenn ich
try/catch/log
in jeder Methode sehe , wirft es Bedenken auf, dass die Entwickler keine Ahnung hatten, was in ihrer Anwendung passieren könnte oder nicht, das Schlimmste vermuteten und alles wegen all der erwarteten Fehler präventiv überall protokollierten.Dies ist ein Symptom dafür, dass Unit- und Integrationstests unzureichend sind und Entwickler daran gewöhnt sind, viel Code im Debugger durchzuarbeiten und hoffen, dass sie durch viel Protokollierung in der Lage sind, fehlerhaften Code in einer Testumgebung bereitzustellen und die Probleme zu finden, indem sie nachsehen die Protokolle.
Code, der Ausnahmen auslöst, kann nützlicher sein als redundanter Code, der Ausnahmen abfängt und protokolliert. Wenn Sie eine Ausnahme mit einer aussagekräftigen Meldung auslösen, wenn eine Methode ein unerwartetes Argument empfängt (und an der Servicegrenze protokolliert), ist es weitaus hilfreicher, die als Nebenwirkung des ungültigen Arguments ausgelöste Ausnahme sofort zu protokollieren und zu erraten, was sie verursacht hat .
Nullen sind ein Beispiel. Wenn Sie als Argument oder Ergebnis eines Methodenaufrufs einen Wert erhalten, der nicht null sein sollte, lösen Sie die Ausnahme aus. Protokollieren Sie die resultierenden
NullReferenceException
geworfenen fünf Zeilen später nicht nur wegen des Nullwerts. In beiden Fällen erhalten Sie eine Ausnahme, aber eine sagt Ihnen etwas, während die andere Sie dazu bringt, nach etwas zu suchen.Wie von anderen gesagt, ist es am besten, Ausnahmen an der Dienstgrenze zu protokollieren oder wenn eine Ausnahme nicht erneut ausgelöst wird, weil sie ordnungsgemäß behandelt wird. Der wichtigste Unterschied ist zwischen etwas und nichts. Wenn Ihre Ausnahmen an einer Stelle protokolliert sind, die leicht zu erreichen ist, finden Sie die Informationen, die Sie benötigen, wenn Sie sie benötigen.
quelle
Wenn Sie Kontextinformationen aufzeichnen müssen, die nicht bereits in der Ausnahme enthalten sind, verpacken Sie sie in eine neue Ausnahme und geben die ursprüngliche Ausnahme als an
InnerException
. Auf diese Weise bleibt die ursprüngliche Stapelspur erhalten. So:Der zweite Parameter für den
Exception
Konstruktor enthält eine innere Ausnahme. Dann können Sie alle Ausnahmen an einem einzigen Ort protokollieren, und Sie erhalten weiterhin die vollständige Stapelverfolgung und die Kontextinformationen in demselben Protokolleintrag.Möglicherweise möchten Sie eine benutzerdefinierte Ausnahmeklasse verwenden, aber der Punkt ist der gleiche.
try / catch / log / rethrow ist ein Durcheinander, weil es zu verwirrenden Protokollen führt - was ist, wenn zwischen dem Protokollieren der Kontextinformationen und dem Protokollieren der tatsächlichen Ausnahme im Top-Level-Handler in einem anderen Thread eine andere Ausnahme auftritt? try / catch / throw ist jedoch in Ordnung, wenn die neue Ausnahme dem Original Informationen hinzufügt.
quelle
Die Ausnahme selbst sollte alle Informationen enthalten, die für eine ordnungsgemäße Protokollierung erforderlich sind, einschließlich Meldung, Fehlercode und was nicht. Daher sollte es nicht erforderlich sein, eine Ausnahme nur zum erneuten Auslösen oder Auslösen einer anderen Ausnahme abzufangen.
Häufig wird das Muster mehrerer Ausnahmen, die abgefangen und erneut ausgelöst wurden, als allgemeine Ausnahme angezeigt, z. B. das Abfangen einer DatabaseConnectionException, einer InvalidQueryException und einer InvalidSQLParameterException sowie das erneute Auslösen einer DatabaseException. Trotzdem würde ich argumentieren, dass all diese spezifischen Ausnahmen in erster Linie von DatabaseException herrühren sollten, so dass ein erneutes Werfen nicht erforderlich ist.
Sie werden feststellen, dass das Entfernen unnötiger Try-Catch-Klauseln (auch für reine Protokollierungszwecke) die Arbeit tatsächlich erleichtert und nicht erschwert. Nur die Stellen in Ihrem Programm, an denen die Ausnahme behandelt wird, sollten die Ausnahme protokollieren. Andernfalls sollte ein programmweiter Ausnahmebehandler für einen letzten Versuch, die Ausnahme zu protokollieren, fehlschlagen, bevor das Programm ordnungsgemäß beendet wird. Die Ausnahme sollte einen vollständigen Stack-Trace haben, der den genauen Punkt angibt, an dem die Ausnahme ausgelöst wurde. Daher ist es häufig nicht erforderlich, eine "Kontext" -Protokollierung bereitzustellen.
Das besagte AOP kann eine schnelle Lösung für Sie sein, obwohl es normalerweise eine leichte Verlangsamung insgesamt mit sich bringt. Ich würde Sie ermutigen, stattdessen die unnötigen try catch-Klauseln vollständig zu entfernen, wenn nichts von Wert hinzugefügt wird.
quelle
try { tester.test(); } catch (NullPointerException e) { logger.error("Variable tester was null!"); }
. Die Stapelverfolgung ist in den meisten Fällen ausreichend, aber da dies nicht der Fall ist, ist die Art des Fehlers in der Regel ausreichend.