Argumente gegen die Fehlerunterdrückung

35

Ich habe in einem unserer Projekte einen Code wie diesen gefunden:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Soweit ich weiß, ist das Unterdrücken derartiger Fehler eine schlechte Praxis, da hierdurch nützliche Informationen aus der Ausnahme des ursprünglichen Servers gelöscht werden und der Code fortgesetzt wird, wenn er tatsächlich beendet werden sollte.

Wann ist es angebracht, alle derartigen Fehler vollständig zu unterdrücken?

user626528
quelle
13
Eric Lippert hat einen guten Blog-Beitrag mit dem Titel Vexing Exceptions geschrieben, in dem er Exceptions in vier verschiedene Kategorien einteilt und den richtigen Ansatz für jede dieser Kategorien vorschlägt. Aus einer C # -Perspektive geschrieben, aber für alle Sprachen anwendbar, glaube ich.
Damien_The_Unbeliever
3
Ich glaube, dass Code das "Fail-Fast" -Prinzip verletzt. Es besteht eine hohe Wahrscheinlichkeit, dass der eigentliche Fehler darin versteckt ist.
Euphoric
4
Sie fangen zu viel. Sie würden auch Bugs wie NRE, Array-Index außerhalb der Grenzen, Konfigurationsfehler, ... fangen. Das verhindert, dass Sie diese Bugs finden, und sie verursachen fortwährend Schaden.
USR

Antworten:

52

Stellen Sie sich Code mit Tausenden von Dateien mit einer Reihe von Bibliotheken vor. Stellen Sie sich vor, alle sind so codiert.

Stellen Sie sich beispielsweise vor, ein Update Ihres Servers führt dazu, dass eine Konfigurationsdatei verschwindet. Wenn Sie versuchen, diese Klasse zu verwenden, ist ein Stack-Trace eine Nullzeiger-Ausnahme. Wie würden Sie das beheben? Es kann Stunden dauern, bis Sie möglicherweise nur den unformatierten Stack-Trace der nicht gefundenen Datei [Dateipfad] protokollieren und diese vorübergehend beheben können.

Oder noch schlimmer: ein Fehler in einer der Bibliotheken, die Sie nach einem Update verwenden, das Ihren Code später zum Absturz bringt. Wie können Sie dies zurück in die Bibliothek verfolgen?

Auch ohne robuste Fehlerbehandlung einfach machen

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

oder

LOGGER.error("[method name]/[arguments] should not be there")

kann Ihnen Stunden Zeit sparen.

Es gibt jedoch einige Fälle, in denen Sie die Ausnahme wirklich ignorieren und null wie folgt zurückgeben möchten (oder nichts tun). Vor allem, wenn Sie schlecht gestalteten Legacy-Code integrieren und diese Ausnahme als normaler Fall erwartet wird.

Wenn Sie dies tun, sollten Sie sich nur fragen, ob Sie die Ausnahmebedingung oder die "ordnungsgemäße Behandlung" für Ihre Anforderungen wirklich ignorieren. Wenn die Rückgabe von null in diesem Fall Ihre Ausnahme "richtig behandelt", dann tun Sie es. Und fügen Sie einen Kommentar hinzu, warum dies das Richtige ist.

Best Practices sind in den meisten Fällen zu befolgen, vielleicht 80%, vielleicht 99%, aber Sie werden immer einen Randfall finden, in dem sie nicht zutreffen. In diesem Fall hinterlassen Sie einen Kommentar, warum Sie die Praxis nicht für die anderen (oder sogar für sich selbst) befolgen, die Ihren Code Monate später lesen.

Walfrat
quelle
7
+1, um zu erklären, warum Sie glauben, dies in einem Kommentar tun zu müssen. Ohne eine Erklärung würde das Verschlucken von Ausnahmen immer Alarmglocken in meinem Kopf auslösen.
jpmc26
Mein Problem dabei ist, dass der Kommentar in diesem Code steckt. Als Konsument der Funktion werde ich diesen Kommentar möglicherweise nie sehen.
CorsiKa
@ jpmc26 Wenn die Ausnahme behandelt wird (z. B. durch Rückgabe eines speziellen Werts), ist dies überhaupt keine Ausnahme, die eine Ausnahme verschluckt.
Deduplizierer
2
@corsiKa ein Verbraucher muss die Implementierungsdetails nicht kennen, wenn die Funktion ihre Arbeit ordnungsgemäß erledigt. Vielleicht sind sie etwas davon in Apache-Bibliotheken oder im Frühling und du weißt es nicht.
Walfrat
@ Deduplicator ja, aber einen Fang als Teil Ihres Algorithmus zu verwenden, sollte auch keine gute Übung sein :)
Walfrat
14

Es gibt Fälle, in denen dieses Muster nützlich ist - es wird jedoch normalerweise verwendet, wenn die generierte Ausnahme niemals hätte auftreten dürfen (dh wenn Ausnahmen für normales Verhalten verwendet werden).

Angenommen, Sie haben eine Klasse, die eine Datei öffnet und in dem zurückgegebenen Objekt speichert. Wenn die Datei nicht vorhanden ist, können Sie davon ausgehen, dass dies kein Fehlerfall ist. Geben Sie null zurück und lassen Sie den Benutzer stattdessen eine neue Datei erstellen. Die Methode zum Öffnen von Dateien kann hier eine Ausnahme auslösen, um darauf hinzuweisen, dass keine Datei vorhanden ist. Daher kann ein unbemerktes Abfangen eine gültige Situation sein.

Es ist jedoch selten, dass Sie dies tun möchten, und wenn der Code mit einem solchen Muster übersät ist, möchten Sie sich jetzt damit befassen. Zumindest würde ich erwarten, dass solch ein stiller Fang eine Protokollzeile schreibt, die besagt, dass dies geschehen ist (Sie haben eine Ablaufverfolgung, richtig) und einen Kommentar, um dieses Verhalten zu erklären.

gbjbaanb
quelle
2
"Wird verwendet, wenn die generierte Ausnahme niemals hätte sein dürfen". Der Ausnahmepunkt ist zu signalisieren, dass etwas schrecklich schief gelaufen ist.
Euphoric
3
@Euphoric Aber manchmal kennt der Schreiber einer Bibliothek die Absichten des Endbenutzers dieser Bibliothek nicht. Das "schreckliche Unrecht" einer Person kann der leicht wiederherstellbare Zustand einer anderen Person sein.
Simon B
2
@Euphorisch, es hängt davon ab, was Ihre Sprache als "schrecklich falsch" ansieht: Python-Leute mögen Ausnahmen für Dinge, die in (zB) C ++ der Flusskontrolle sehr nahe kommen. Die Rückgabe von null kann in bestimmten Situationen unhaltbar sein, z. B. beim Lesen aus einem Cache, in dem Elemente plötzlich und unvorhersehbar entfernt werden können.
Matt Krause
10
@Euphoric, "Datei nicht gefunden ist keine normale Ausnahme. Sie sollten zuerst prüfen, ob eine Datei vorhanden ist, wenn die Möglichkeit besteht, dass sie möglicherweise nicht vorhanden ist." Nein! Nein! Nein! Nein! Das ist klassisches falsches Denken um Atomoperationen. Die Datei wird möglicherweise gelöscht, wenn Sie überprüfen, ob sie vorhanden ist, und auf sie zugreifen. Der einzig richtige Weg, um mit solchen Situationen umzugehen, besteht darin, darauf zuzugreifen und die Ausnahmen zu behandeln, wenn sie auftreten null.
David Arno
3
@Euphoric: Schreiben Sie also den Code, um zu verhindern, dass Sie mindestens zweimal auf die Datei zugreifen können? Dies verstößt gegen DRY und auch nicht ausgeführter Code ist fehlerhafter Code.
Deduplizierer
7

Dies ist zu 100% kontextabhängig. Wenn der Aufrufer einer solchen Funktion die Anforderung hat, Fehler irgendwo anzuzeigen oder zu protokollieren, ist dies offensichtlich Unsinn. Wenn der Aufrufer auch alle Fehler- oder Ausnahmemeldungen ignoriert, ist es wenig sinnvoll, die Ausnahme oder ihre Meldung zurückzugeben. Wenn der Code nur beendet werden soll, ohne dass ein Grund angezeigt wird, dann ein Anruf

   if(QueryServer(args)==null)
      TerminateProgram();

würde wohl ausreichen. Sie müssen sich überlegen, ob es dadurch für den Benutzer schwierig wird, die Fehlerursache zu finden. Ist dies der Fall, ist dies die falsche Form der Fehlerbehandlung. Wenn der aufrufende Code jedoch so aussieht

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

Dann sollten Sie während der Codeüberprüfung einen Streit mit dem Entwickler haben. Wenn jemand eine solche Form der fehlerfreien Behandlung implementiert, nur weil er zu faul ist, um die richtigen Anforderungen herauszufinden, sollte dieser Code nicht in Produktion gehen, und der Autor des Codes sollte über die möglichen Folgen einer solchen Faulheit unterrichtet werden .

Doc Brown
quelle
5

In den Jahren, in denen ich Systeme programmiert und entwickelt habe, gab es nur zwei Situationen, in denen ich das fragliche Muster als nützlich empfand (in beiden Fällen betrachte ich die Unterdrückung, die auch die Protokollierung der ausgelösten Ausnahme enthielt, nicht nullals eine gute Praxis ).

Die zwei Situationen sind die folgenden:

1. Wenn die Ausnahme nicht als Ausnahmezustand angesehen wurde

In diesem Fall führen Sie eine Operation für einige Daten aus, die möglicherweise ausgelöst werden, von denen Sie wissen, dass sie ausgelöst werden. Sie möchten jedoch weiterhin, dass Ihre Anwendung ausgeführt wird, da Sie die verarbeiteten Daten nicht benötigen. Wenn Sie sie erhalten, ist es gut, wenn Sie es nicht tun, ist es auch gut.

Einige optionale Attribute einer Klasse können in den Sinn kommen.

2. Wenn Sie eine neue (bessere, schnellere?) Implementierung einer Bibliothek über eine Schnittstelle bereitstellen, die bereits in einer Anwendung verwendet wird

Stellen Sie sich vor, Sie haben eine Anwendung, die eine Art alte Bibliothek verwendet, die keine Ausnahmen ausgelöst hat, sondern nullbei einem Fehler zurückgegeben wurde. Sie haben also einen Adapter für diese Bibliothek erstellt, indem Sie die ursprüngliche API der Bibliothek kopiert haben, und verwenden diese neue (immer noch nicht auslösende) Schnittstelle in Ihrer Anwendung, um die nullPrüfungen selbst durchzuführen.

Es kommt eine neue Version der Bibliothek oder eine völlig andere Bibliothek mit derselben Funktionalität, die anstelle von nulls Ausnahmen auslöst und die Sie verwenden möchten.

Sie möchten die Ausnahmen nicht an Ihre Hauptanwendung weitergeben, daher unterdrücken und protokollieren Sie sie in dem Adapter, den Sie erstellen, um diese neue Abhängigkeit zu verpacken.


Der erste Fall ist kein Problem, sondern das gewünschte Verhalten des Codes. Wenn jedoch in der zweiten Situation der nullRückgabewert des Bibliotheksadapters überall wirklich einen Fehler bedeutet, ist es nullmöglicherweise eine gute Idee , die API so umzugestalten, dass eine Ausnahme ausgelöst und abgefangen wird, anstatt zu prüfen (und dies ist in der Regel im Code der Fall).

Ich persönlich verwende Ausnahmesuppression nur für den ersten Fall. Ich habe es nur für den zweiten Fall verwendet, als wir nicht das Budget hatten, um den Rest der Anwendung mit den Ausnahmen anstelle von nulls zum Laufen zu bringen .

Andy
quelle
-1

Während es logisch erscheint zu sagen, dass Programme nur Ausnahmen abfangen sollten, mit denen sie umgehen können, und möglicherweise nicht wissen können, wie mit Ausnahmen umzugehen ist, die vom Programmierer nicht erwartet wurden, ignoriert eine solche Behauptung die Tatsache, dass viele Vorgänge in a fehlschlagen können eine nahezu unbegrenzte Anzahl von Möglichkeiten, die keine Nebenwirkungen haben, und dass in vielen Fällen die ordnungsgemäße Behandlung für die überwiegende Mehrheit solcher Fehler identisch sein wird; Die genauen Details des Fehlers sind irrelevant, und folglich sollte es egal sein, ob der Programmierer sie vorweggenommen hat.

Wenn der Zweck einer Funktion beispielsweise darin besteht, eine Dokumentdatei in ein geeignetes Objekt einzulesen und entweder ein neues Dokumentfenster zu erstellen, um dieses Objekt anzuzeigen, oder dem Benutzer zu melden, dass die Datei nicht gelesen werden konnte, versuchen Sie, eine zu laden Eine ungültige Dokumentdatei sollte die Anwendung nicht zum Absturz bringen. Stattdessen sollte eine Meldung angezeigt werden, die auf das Problem hinweist. Der Rest der Anwendung sollte jedoch normal weiterlaufen, es sei denn, der Versuch, das Dokument zu laden, hat aus irgendeinem Grund den Status eines anderen Elements im System beschädigt .

Grundsätzlich hängt die ordnungsgemäße Behandlung einer Ausnahme häufig weniger vom Ausnahmetyp ab als vom Ort, an dem sie ausgelöst wurde. Wenn eine Ressource durch eine Lese- / Schreibsperre geschützt ist und eine Ausnahme innerhalb einer Methode ausgelöst wird, die die Lesesperre erhalten hat, sollte das ordnungsgemäße Verhalten im Allgemeinen darin bestehen, die Sperre aufzuheben, da die Methode der Ressource nichts anhaben kann . Wenn eine Ausnahme ausgelöst wird, während die Sperre zum Schreiben angefordert wird, sollte die Sperre häufig ungültig gemacht werden, da sich die geschützte Ressource möglicherweise in einem ungültigen Zustand befindet. Wenn das Sperrprimitiv keinen "ungültigen" Zustand aufweist, sollte ein Flag hinzugefügt werden, um eine solche Ungültigmachung zu verfolgen. Das Aufheben der Sperre, ohne sie zu ungültig zu machen, ist fehlerhaft, da andere Codes das geschützte Objekt möglicherweise in einem ungültigen Zustand sehen. Das Schloss baumeln zu lassen ist jedoch nicht Auch eine richtige Lösung. Die richtige Lösung besteht darin, die Sperre aufzuheben, damit anstehende oder zukünftige Erfassungsversuche sofort fehlschlagen.

Wenn sich herausstellt, dass eine ungültige Ressource vor dem Versuch, sie zu verwenden, verworfen wird, besteht kein Grund, die Anwendung herunterzufahren. Wenn eine ungültig gemachte Ressource für den weiteren Betrieb einer Anwendung von entscheidender Bedeutung ist, muss die Anwendung heruntergefahren werden. Wenn die Ressource jedoch ungültig gemacht wird, ist dies wahrscheinlich. Der Code, der die ursprüngliche Ausnahme erhalten hat, kann häufig nicht wissen, welche Situation zutrifft. Wenn er jedoch die Ressource ungültig macht, kann er sicherstellen, dass in beiden Fällen die richtige Aktion ausgeführt wird.

Superkatze
quelle
2
Dies kann die Frage nicht beantworten, da die Behandlung einer Ausnahme ohne Kenntnis der Ausnahmedetails erfolgt!
Taemyr
@Taemyr: Wenn der Zweck einer Funktion darin besteht, "wenn möglich X zu tun oder andernfalls anzuzeigen, dass dies nicht der Fall war", und X nicht möglich war, ist es oft unpraktisch, alle Arten von Ausnahmen aufzulisten, die weder die Funktion noch die Funktion betreffen Der Anrufer wird sich auch nicht um "Es war nicht möglich, X zu tun" kümmern. Die Behandlung von Pokemon-Ausnahmen ist in solchen Fällen oft die einzig praktikable Möglichkeit, Dinge zum Laufen zu bringen, und muss nicht annähernd so schlimm sein, wie manche Leute es ausmachen, wenn Code gegen die Ungültigmachung von Dingen verstößt, die durch unerwartete Ausnahmen beschädigt werden.
Supercat
Genau. Jede Methode sollte einen Zweck haben. Wenn sie ihren Zweck erfüllen kann, unabhängig davon, ob eine von ihr aufgerufene Methode eine Ausnahme auslöst oder nicht, dann ist es richtig, die Ausnahme zu schlucken, was auch immer sie ist, und ihre Arbeit zu erledigen. Bei der Fehlerbehandlung geht es grundsätzlich darum, die Aufgabe nach einem Fehler zu erledigen. Darauf kommt es an, nicht darauf, welcher Fehler aufgetreten ist.
Jmoreno
@jmoreno: Was die Dinge schwierig macht, ist, dass es in vielen Situationen eine große Anzahl von Ausnahmen gibt, von denen viele ein Programmierer nicht besonders erwartet hätte, die auf kein Problem hinweisen würden, und einige Ausnahmen, von denen ein Programmierer dies nicht tun würde Ich nehme nicht vorweg, was auf ein Problem hindeutet, und keine definierte systematische Methode, um sie zu unterscheiden. Der einzige Weg, mit dem ich vernünftig umgehen kann, besteht darin, dass Ausnahmen Dinge ungültig machen, die beschädigt wurden. Wenn sie wichtig sind, werden sie bemerkt und wenn sie nicht wichtig sind, werden sie ignoriert.
Supercat
-2

Das Verschlucken eines solchen Fehlers ist für niemanden besonders nützlich. Ja, der ursprüngliche Anrufer kann über die Ausnahme , aber jemand anderes nicht kümmern könnte .

Was machen sie dann? Sie fügen Code zur Behandlung der Ausnahme hinzu, um festzustellen, welche Art von Ausnahme sie zurückerhalten. Groß. Wenn die Ausnahmebehandlung jedoch aktiviert bleibt, erhält der ursprüngliche Aufrufer keine Null mehr und an anderer Stelle bricht etwas zusammen.

Selbst wenn Sie wissen, dass ein Upstream-Code einen Fehler auslösen und somit null zurückgeben kann, wäre es für Sie äußerst träge, zumindest nicht zu versuchen, die Ausnahme im aufrufenden Code IMHO auszulösen.

Robbie Dee
quelle
"ernsthaft träge" - meintest du vielleicht "ernsthaft unfähig"?
Esoteric Screen Name
@EsotericScreenName Wählen Sie eine ... ;-)
Robbie Dee
-3

Ich habe Beispiele gesehen, in denen Bibliotheken von Drittanbietern möglicherweise nützliche Methoden hatten, mit der Ausnahme, dass sie in einigen Fällen Ausnahmen auslösen, die für mich funktionieren sollten. Was kann ich tun?

  • Implementiere genau das, was ich selbst brauche. Kann bei einer Deadline schwierig sein.
  • Ändern Sie den Code des Drittanbieters. Aber dann muss ich bei jedem Upgrade nach Zusammenführungskonflikten suchen.
  • Schreiben Sie eine Wrapper-Methode, um die Ausnahme in einen normalen Nullzeiger, einen booleschen Wert false oder was auch immer umzuwandeln.

Zum Beispiel die Bibliotheksmethode

public foo findFoo() {...} 

Gibt das erste foo zurück, aber wenn es keins gibt, wird eine Ausnahme ausgelöst. Aber was ich brauche, ist eine Methode, um das erste foo oder eine Null zurückzugeben. Also schreibe ich diesen:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Nicht ordentlich. Aber manchmal die pragmatische Lösung.

om
quelle
1
Was meinst du mit "Ausnahmen werfen, wo sie für dich arbeiten sollten"?
CorsiKa
@corsiKa, das Beispiel, an das ich denke, sind Methoden, die eine Liste oder eine ähnliche Datenstruktur durchsuchen. Schlagen sie fehl, wenn sie nichts finden, oder geben sie einen Nullwert zurück? Ein weiteres Beispiel sind waitUntil-Methoden, die bei Zeitüberschreitung fehlschlagen und keinen booleschen Wert false zurückgeben.
om