it-swarm.com.de

Ist es wirklich schlecht, allgemeine Ausnahmen zu erwischen?

Normalerweise stimme ich den meisten Warnungen zur Codeanalyse zu und versuche, sie einzuhalten. Ich habe es jedoch schwerer mit diesem:

CA1031: Keine allgemeinen Ausnahmetypen abfangen

Ich verstehe die Gründe für diese Regel. Aber in der Praxis, wenn ich unabhängig von der ausgelösten Ausnahme dieselbe Aktion ausführen möchte, warum sollte ich dann jede einzelne Aktion speziell behandeln? Wenn ich bestimmte Ausnahmen behandle, was ist, wenn sich der von mir aufgerufene Code ändert, um in Zukunft eine neue Ausnahme auszulösen? Jetzt muss ich meinen Code ändern, um diese neue Ausnahme zu behandeln. Wenn ich dagegen einfach Exception gefangen habe, muss sich mein Code nicht ändern.

Wenn Foo beispielsweise Bar aufruft und Foo die Verarbeitung unabhängig von der Art der von Bar ausgelösten Ausnahme beenden muss, gibt es dann einen Vorteil, wenn Sie genau angeben, welche Art von Ausnahme ich abfange?

Vielleicht ein besseres Beispiel:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Wenn Sie hier keine allgemeine Ausnahme abfangen, müssen Sie jede mögliche Art von Ausnahme abfangen. Patrick hat einen guten Punkt, dass OutOfMemoryException nicht so behandelt werden sollte. Was ist, wenn ich jede Ausnahme außer OutOfMemoryException ignorieren möchte?

59
Bob Horn

Diese Regeln sind allgemein eine gute Idee und sollten daher befolgt werden.

Aber Denken Sie daran, dies sind allgemeine Regeln. Sie decken nicht alle Situationen ab. Sie decken die häufigsten Situationen ab. Wenn Sie eine bestimmte Situation haben und das Argument vorbringen können, dass Ihre Technik besser ist (und Sie sollten in der Lage sein, einen Kommentar in den Code zu schreiben, um Ihr Argument dafür zu artikulieren), dann tun Sie dies (und lassen Sie es dann einer Peer-Review unterziehen).

Auf der Gegenseite des Arguments.

Ich sehe Ihr Beispiel oben nicht als eine gute Situation dafür. Wenn das Protokollierungssystem ausfällt (vermutlich eine andere Ausnahme protokolliert), möchte ich wahrscheinlich nicht, dass die Anwendung fortgesetzt wird. Beenden Sie die Ausnahme und drucken Sie sie in der Ausgabe aus, damit der Benutzer sehen kann, was passiert ist.

33
Martin York

Ja, allgemeine Ausnahmen zu erwischen ist eine schlechte Sache. Eine Ausnahme bedeutet normalerweise, dass das Programm nicht das tun kann, worum Sie es gebeten haben.

Es gibt einige Arten von Ausnahmen, die Sie könnten behandeln:

  • Schwerwiegende Ausnahmen: Nicht genügend Speicher, Stapelüberlauf usw. Eine übernatürliche Kraft hat Ihr Universum nur durcheinander gebracht und der Prozess stirbt bereits. Sie können die Situation nicht verbessern, also geben Sie einfach auf
  • Ausnahme ausgelöst, weil Fehler im von Ihnen verwendeten Code vorliegen: Versuchen Sie nicht, diese zu behandeln, sondern beheben Sie die Ursache des Problems. Verwenden Sie keine Ausnahmen für die Flusskontrolle
  • Ausnahmesituationen: Behandeln Sie Ausnahmen nur in diesen Fällen. Hier können wir Folgendes einschließen: Netzwerkkabel abgezogen, Internetverbindung funktioniert nicht mehr, fehlende Berechtigungen usw.

Oh, und in der Regel: Wenn Sie nicht wissen, was Sie mit einer Ausnahme tun sollen, wenn Sie sie abfangen, ist es besser, einfach schnell zu scheitern (übergeben Sie die Ausnahme an den Anrufer und lassen Sie sie damit umgehen).

31

Die oberste äußere Schleife sollte eine davon haben, um alles zu drucken, was sie kann, und dann einen schrecklichen, gewalttätigen und lauten Tod sterben (da dies nicht passieren sollte und jemand hören muss).

Andernfalls sollten Sie im Allgemeinen sehr vorsichtig sein, da Sie höchstwahrscheinlich nicht alles vorausgesehen haben, was an diesem Ort passieren könnte, und es daher höchstwahrscheinlich nicht richtig behandeln werden. Seien Sie so genau wie möglich, damit Sie nur diejenigen fangen, die Sie wissen passieren werden, und lassen Sie diejenigen, die vorher nicht gesehen wurden, bis zu dem oben erwähnten lauten Tod sprudeln.

15
user1249

Es ist nicht so, dass es schlecht ist, es ist nur so, dass bestimmte Fänge besser sind. Wenn Sie spezifisch sind, bedeutet dies, dass Sie konkreter verstehen, was Ihre Anwendung tut, und mehr Kontrolle darüber haben. Im Allgemeinen, wenn Sie auf eine Situation stoßen, in der Sie gerade eine bekommen Ausnahme, loggen Sie es ein und fahren Sie fort, es gibt wahrscheinlich einige schlechte Dinge, die sowieso vor sich gehen. Wenn Sie speziell die Ausnahmen abfangen, von denen Sie wissen, dass sie von einem Codeblock oder einer Methode ausgelöst werden können, besteht eine höhere Wahrscheinlichkeit, dass Sie sie tatsächlich wiederherstellen können anstatt nur zu protokollieren und auf das Beste zu hoffen.

11
Ryan Hayes

Ich habe in letzter Zeit darüber nachgedacht, und meine vorläufige Schlussfolgerung ist, dass die bloße Frage auftaucht, weil die . NET-Ausnahmehierarchie stark durcheinander ist.

Nehmen Sie zum Beispiel das niedrige ArgumentNullException welches könnte ein vernünftiger Kandidat für eine Ausnahme ist, die Sie nicht = will fangen, weil es neigt eher auf einen Fehler im Code als auf einen legitimen Laufzeitfehler hinweist. Ah ja. Dies gilt auch für NullReferenceException , außer dass NullReferenceException direkt von SystemException abgeleitet ist. Es gibt also keinen Bucket, in den Sie alle "logischen Fehler" einfügen können, um sie abzufangen (oder nicht fangen).

Dann da ist der IMNSHO major Pfusch von SEHException ableiten (via ExternalException) SystemException und machen es so zu einem "normalen" SystemException Wenn Sie ein SEHException erhalten, möchten Sie einen Speicherauszug schreiben und so schnell wie möglich beenden - und ab .NET 4 werden mindestens einige SEH-Ausnahmen berücksichtigt - Corrupted State Exceptions das wird nicht abgefangen. Eine gute Sache und eine Tatsache, die die CA1031 - Regel noch nutzloser macht, denn jetzt wird Ihre faule catch (Exception) diese schlimmsten Typen sowieso nicht fangen.

Dann scheint es, dass das andere Framework-Zeug ziemlich inkonsistent entweder Exception direkt oder über SystemException ableitet und jeden Versuch unternimmt, catch-Klauseln nach so etwas wie Schweregrad zu gruppieren strittig.

Es gibt ein Stück von Mr. Lippert von C# Ruhm, genannt Vexing Exception , in dem er eine nützliche Kategorisierung von Ausnahmen vorlegt: Sie könnten argumentieren, Sie wollen nur "exogene" fangen, außer ... C# Die Sprache und das Design der .NET Framework-Ausnahmen machen es unmöglich, "nur exogene" auf prägnante Weise zu fangen. (und z. B. kann ein OutOfMemoryException sehr gut ein völlig normaler behebbarer Fehler für eine API sein, die Puffer zuweisen muss, die irgendwie groß sind)

Fazit für mich ist, dass die Art und Weise, wie C# Catch-Blöcke funktionieren und wie die Framework-Ausnahmehierarchie entworfen wird, die Regel CA1031 Überaus völlig nutzlos ist . Es gibt vor, um beim Grundproblem "Ausnahmen nicht schlucken" zu helfen, aber das Schlucken von Ausnahmen hat nichts mit dem zu tun, was Sie fangen, sondern mit dem, was Sie dann tun:

Es gibt mindestens 4 Möglichkeiten , um einen gefangenen Exception legitim zu behandeln, und CA1031 Scheint nur oberflächlich mit einer von ihnen umzugehen ( nämlich der Fall des erneuten Werfens).


Als Randnotiz gibt es eine C # 6-Funktion namens Ausnahmefilter , die CA1031 Wieder etwas gültiger macht, da Sie dann die gewünschten Ausnahmen richtig, richtig und richtig filtern können zu fangen, und es gibt weniger Grund, ein ungefiltertes catch (Exception) zu schreiben.

5
Martin Ba

Die beiden Möglichkeiten schließen sich nicht gegenseitig aus.

Im Idealfall würden Sie alle möglichen Arten von Ausnahmen abfangen, die Ihre Methode generieren könnte, sie auf Ausnahmebasis behandeln und am Ende eine allgemeine catch -Klausel hinzufügen, um zukünftige oder unbekannte Ausnahmen abzufangen. Auf diese Weise erhalten Sie das Beste aus beiden Welten.

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

Denken Sie daran, dass Sie die spezifischeren Ausnahmen an die erste Stelle setzen müssen, um sie zu erfassen.

Bearbeiten: Aus den in den Kommentaren angegebenen Gründen wurde beim letzten Fang ein erneuter Wurf hinzugefügt.

5
Rotem

Die Behandlung von Pokemon-Ausnahmen (ich muss sie alle fangen!) Ist sicherlich nicht immer schlecht. Wenn Sie eine Methode einem Client, insbesondere einem Endbenutzer, zur Verfügung stellen, ist es oft besser, alles und jeden abzufangen, als dass Ihre Anwendung abstürzt und brennt.

Im Allgemeinen sollten sie jedoch vermieden werden, wo Sie können. Sofern Sie nicht bestimmte Maßnahmen ergreifen können, die auf dem Typ der Ausnahme basieren, ist es besser, sie nicht zu behandeln und die Ausnahme in die Luft sprudeln zu lassen, anstatt die Ausnahme zu verschlucken oder falsch zu behandeln.

Schauen Sie sich this SO Antwort für mehr Lektüre an.

4
Tom Squires

Das Abfangen einer allgemeinen Ausnahme ist schlecht, da Ihr Programm dadurch in einem undefinierten Zustand bleibt. Sie wissen nicht, wo etwas schief gelaufen ist, also wissen Sie nicht, was Ihr Programm tatsächlich getan hat oder nicht getan hat.

Wo ich erlauben würde, alles zu fangen, ist beim Schließen eines Programms. Solange du es in Ordnung aufräumen kannst. Nichts ist so ärgerlich wie ein Programm, das Sie schließen und das nur einen Fehlerdialog auslöst, der nichts anderes tut, als dort zu sitzen, nicht wegzugehen und das Herunterfahren Ihres Computers zu verhindern.

In einer verteilten Umgebung kann Ihre Protokollmethode nach hinten losgehen: Wenn Sie eine allgemeine Ausnahme abfangen, kann Ihr Programm die Protokolldatei weiterhin sperren, sodass andere Benutzer keine Protokolle erstellen können.

1
Pieter B

Ich möchte dies eher aus einer logischen als aus einer technischen Perspektive ansprechen.

Jetzt muss ich meinen Code ändern, um diese neue Ausnahme zu behandeln.

Nun, jemand würde damit umgehen müssen. Das ist die Idee. Autoren von Bibliothekscode sollten das Hinzufügen neuer Ausnahmetypen mit Bedacht durchführen. Da dies jedoch dazu führen kann, dass Clients beschädigt werden, sollten Sie dies nicht sehr häufig feststellen.

Ihre Frage lautet im Grunde: "Was ist, wenn es mir egal ist, was schief gelaufen ist? Muss ich wirklich die Mühe machen, herauszufinden, was es war?"

Hier ist der Schönheitsteil: Nein, tust du nicht.

"Also, kann ich einfach wegschauen und alles, was böse ist, automatisch unter den Teppich kehren und damit fertig werden?"

Nein, so funktioniert es auch nicht.

Der Punkt ist, dass die Sammlung möglicher Ausnahmen immer größer ist als die Sammlung, die Sie erwarten und an der Sie im Kontext Ihres lokalen kleinen Problems interessiert sind. Sie behandeln diejenigen, die Sie erwarten, und wenn etwas Unerwartetes passiert, überlassen Sie es den übergeordneten Handlern, anstatt es zu schlucken. Wenn Sie sich nicht für eine Ausnahme interessieren, die Sie nicht erwartet haben, setzen Sie darauf, dass jemand im Call-Stack ist, und es wäre Sabotage, eine Ausnahme zu beenden, bevor sie ihren Handler erreicht.

"Aber ... aber ... dann könnte eine dieser anderen Ausnahmen, die mir egal sind, dazu führen, dass meine Aufgabe fehlschlägt!"

Ja. Aber sie werden immer wichtiger sein als die lokal gehandhabten. Wie ein Feueralarm oder der Chef, der Ihnen sagt, Sie sollen aufhören, was Sie tun, und eine dringendere Aufgabe übernehmen, die aufgetaucht ist.

0
Martin Maat

Ich verstehe die Gründe für diese Regel. Aber in der Praxis, wenn ich unabhängig von der ausgelösten Ausnahme dieselbe Aktion ausführen möchte, warum sollte ich dann jede einzelne Aktion speziell behandeln?

Wie andere gesagt haben, ist es wirklich schwierig (wenn nicht unmöglich), sich eine Aktion vorzustellen, die Sie unabhängig von der ausgelösten Ausnahme ausführen möchten. Nur ein Beispiel sind Situationen, in denen der Status des Programms beschädigt wurde und jede weitere Verarbeitung zu Problemen führen kann (dies ist die Begründung für Environment.FailFast ).

Wenn ich bestimmte Ausnahmen behandle, was ist, wenn sich der von mir aufgerufene Code ändert, um in Zukunft eine neue Ausnahme auszulösen? Jetzt muss ich meinen Code ändern, um diese neue Ausnahme zu behandeln. Wenn ich dagegen nur Exception abgefangen habe, muss sich mein Code nicht ändern.

Für Hobby-Code ist es in Ordnung, Exception zu fangen, aber für professionellen Code sollte die Einführung eines neuen Ausnahmetyps mit dem gleichen Respekt behandelt werden wie eine Änderung der Methodensignatur, d. H. Als brechende Änderung betrachtet werden. Wenn Sie diesen Standpunkt abonnieren, ist es sofort offensichtlich, dass die Änderung (oder Überprüfung) des Client-Codes die einzig richtige Vorgehensweise ist.

Wenn Foo beispielsweise Bar aufruft und Foo die Verarbeitung unabhängig von der Art der von Bar ausgelösten Ausnahme beenden muss, gibt es dann einen Vorteil, wenn Sie genau angeben, welche Art von Ausnahme ich abfange?

Sicher, denn Sie werden nicht nur Ausnahmen abfangen, die von Bar ausgelöst werden. Es wird auch Ausnahmen geben, die die Clients von Bar oder sogar die Laufzeit während der Zeit auslösen können, in der sich Bar auf dem Aufrufstapel befindet. Ein gut geschriebenes Bar sollte bei Bedarf seinen eigenen Ausnahmetyp definieren, damit Aufrufer die von sich selbst ausgegebenen Ausnahmen spezifisch abfangen können.

Was ist, wenn ich jede Ausnahme außer OutOfMemoryException ignorieren möchte?

IMHO ist dies der falsche Weg, um über die Ausnahmebehandlung nachzudenken. Sie sollten auf Whitelists (Fangausnahmetypen A und B) und nicht auf Blacklists (alle Ausnahmen außer X abfangen) arbeiten.

0
Jon

Vielleicht. Zu jeder Regel gibt es Ausnahmen, und keine Regel sollte unkritisch befolgt werden. Ihr Beispiel könnte möglicherweise einer der Fälle sein, in denen es sinnvoll ist, alle Ausnahmen zu schlucken. Wenn Sie beispielsweise einem kritischen Produktionssystem eine Ablaufverfolgung hinzufügen möchten und unbedingt sicherstellen möchten, dass Ihre Änderungen die Hauptaufgabe der App nicht stören.

Allerdings sollten Sie sorgfältig über mögliche Fehlerursachen nachdenken, bevor Sie sich entscheiden, sie alle stillschweigend zu ignorieren. Was ist zum Beispiel, wenn der Grund für die Ausnahme ist:

  • ein Programmierfehler in der Protokollierungsmethode, der dazu führt, dass immer eine bestimmte Ausnahme ausgelöst wird
  • ein ungültiger Pfad zur Protokolldatei in der Konfiguration
  • die Festplatte ist voll

Möchten Sie nicht sofort benachrichtigt werden, dass dieses Problem vorliegt, damit Sie es beheben können? Wenn Sie die Ausnahme verschlucken, wissen Sie nie, dass etwas schief gelaufen ist.

Einige Probleme (z. B. wenn die Festplatte voll ist) können auch dazu führen, dass andere Teile der Anwendung fehlschlagen. Dieser Fehler wird jedoch jetzt nicht protokolliert, sodass Sie es nie erfahren!

0
JacquesB