Endlich eine Ausnahme reinwerfen

27

Statische Codeanalysatoren wie Fortify "beschweren" sich, wenn eine Ausnahme in einen finallyBlock geworfen wird Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. Normalerweise stimme ich dem zu. Aber vor kurzem bin ich auf diesen Code gestoßen:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

Wenn das writernicht richtig geschlossen werden kann, löst die writer.close()Methode eine Ausnahme aus. Eine Ausnahme sollte geworfen werden, da die Datei (höchstwahrscheinlich) nach dem Schreiben nicht gespeichert wurde.

Ich könnte eine zusätzliche Variable deklarieren, sie setzen, wenn beim Schließen ein Fehler auftritt, writerund nach dem finally-Block eine Ausnahme auslösen. Aber dieser Code funktioniert einwandfrei und ich bin nicht sicher, ob ich ihn ändern soll oder nicht.

Was sind die Nachteile des Auslösens einer Ausnahme innerhalb des finallyBlocks?

superM
quelle
4
Wenn dies Java ist und Sie Java 7 verwenden können, prüfen Sie, ob ARM-Blöcke Ihr Problem lösen können.
Landei
@ Landei, das löst es, aber leider verwenden wir nicht Java 7.
superM
Ich würde sagen, dass der Code, den Sie gezeigt haben, nicht "Verwenden einer throw-Anweisung in einem finally-Block" ist und als solcher ist der logische Fortschritt in Ordnung.
Mike
@Mike, ich habe die Standardzusammenfassung verwendet, die Fortify anzeigt, aber direkt oder indirekt gibt es eine Ausnahme, die endlich im Inneren geworfen wird.
SuperM
Leider wird der Try-with-Resources-Block auch von Fortify als Ausnahmefehler erkannt. Er ist zu schlau, verdammt. Ich bin mir immer noch nicht sicher, wie ich ihn überwinden soll Ressourcen schließlich, und jetzt wird jede solche Aussage von Fortify als Sicherheitsbedrohung gemeldet ..
mmona

Antworten:

18

Grundsätzlich dienen finallyKlauseln dazu, die ordnungsgemäße Freigabe einer Ressource sicherzustellen. Wenn jedoch eine Ausnahme innerhalb des finally-Blocks ausgelöst wird, erlischt diese Garantie. Schlimmer noch, wenn Ihr Hauptcodeblock eine Ausnahme auslöst, wird diese durch die im Block ausgelöste Ausnahme finallyausgeblendet. Es sieht so aus, als ob der Fehler durch den Aufruf von verursacht wurde close, nicht aus dem wahren Grund.

Einige Leute folgen einem unangenehmen Muster von verschachtelten Ausnahmebehandlungsroutinen und verschlucken alle Ausnahmen, die im finallyBlock geworfen werden .

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

In älteren Java-Versionen können Sie diesen Code "vereinfachen", indem Sie Ressourcen in Klassen einschließen, die diese "sichere" Bereinigung für Sie durchführen. Ein guter Freund von mir erstellt eine Liste anonymer Typen, die jeweils die Logik für die Bereinigung ihrer Ressourcen enthalten. Dann durchläuft sein Code einfach die Liste und ruft die dispose-Methode innerhalb des finallyBlocks auf.

Travis Parks
quelle
1
+1 Obwohl ich zu denen gehöre, die diesen fiesen Code verwenden. Aber zu meiner Rechtfertigung werde ich sagen, dass ich das nicht immer mache, nur wenn die Ausnahme nicht kritisch ist.
SuperM
2
Ich finde, dass ich es auch tue. Manchmal beeinträchtigt das Erstellen eines gesamten Ressourcenmanagers (anonym oder nicht) den Zweck des Codes.
Travis Parks
+1 von mir auch; Sie sagten im Grunde genau das, was ich wollte, aber besser. Bemerkenswert ist, dass einige der Stream-Implementierungen in Java keine Exceptions on close () auslösen können, die Schnittstelle dies jedoch deklariert, da dies bei einigen von ihnen der Fall ist. Unter bestimmten Umständen fügen Sie also möglicherweise einen Fangblock hinzu, der eigentlich nie benötigt wird.
Vaughandroid
1
Was ist so schlimm daran, einen try / catch-Block in einem finally-Block zu verwenden? Ich würde das lieber tun, als meinen gesamten Code aufzublähen, indem ich alle meine Verbindungen und Streams usw. umbrechen müsste, damit sie leise geschlossen werden können. oder was auch immer) verursacht eine Ausnahme - das ist etwas, über das Sie vielleicht gerne Bescheid wissen oder etwas unternehmen möchten ...
Ban-Geoengineering
10

Was Travis Parks sagte, ist wahr, dass Ausnahmen im finallyBlock alle Rückgabewerte oder Ausnahmen von den try...catchBlöcken verbrauchen .

Wenn Sie jedoch Java 7 verwenden, können Sie das Problem mithilfe eines Try-with-Resources- Blocks lösen . Laut Dokumentation wird java.lang.AutoCloseableder Try-with-Resources-Block für Sie geschlossen , solange Ihre Ressource implementiert ist (die meisten Bibliotheksschreiber / -leser tun dies jetzt). Der zusätzliche Vorteil hierbei ist, dass jede Ausnahme, die beim Schließen auftritt, unterdrückt wird, sodass der ursprüngliche Rückgabewert oder die ursprüngliche Ausnahme überschritten wird.

Von

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

Zu

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Clintonmonk
quelle
1
Dies ist manchmal hilfreich. In dem Fall, dass ich tatsächlich eine Ausnahme auslösen muss, wenn die Datei nicht geschlossen werden kann, werden die Probleme durch diesen Ansatz ausgeblendet.
SuperM
1
@superM Tatsächlich verbirgt try-with-resources keine Ausnahme vor close(). "Jede Ausnahme, die beim Schließen auftritt, wird unterdrückt, wodurch der ursprüngliche Rückgabewert oder die ursprüngliche Ausnahme überschritten wird" - dies ist einfach nicht wahr . Eine Ausnahme von der close()Methode wird nur dann unterdrückt, wenn eine andere Ausnahme vom try / catch-Block ausgelöst wird. Wenn der tryBlock also keine Ausnahme auslöst, wird die Ausnahme von der close()Methode ausgelöst (und der Rückgabewert wird nicht zurückgegeben). Es kann sogar aus dem aktuellen catchBlock abgefangen werden .
Ruslan Stelmachenko
0

Ich denke, das müssen Sie von Fall zu Fall klären. In einigen Fällen ist das, was der Analysator sagt, insofern richtig, als der Code, den Sie haben, nicht so gut ist und ein Umdenken erfordert. Es kann aber auch andere Fälle geben, in denen das Werfen oder sogar das erneute Werfen das Beste ist. Es ist nicht etwas, das Sie beauftragen können.

Drekka
quelle
0

Dies wird eher eine konzeptionelle Antwort darauf sein, warum diese Warnungen auch bei Ansätzen zum Ausprobieren von Ressourcen auftreten können. Es ist leider auch nicht die einfache Art von Lösung, die Sie sich erhoffen.

Fehlerbehebung kann nicht fehlschlagen

finally modelliert einen Kontrollfluss nach einer Transaktion, der unabhängig davon ausgeführt wird, ob eine Transaktion erfolgreich ist oder fehlschlägt.

Im Fehlerfall wird finallydie Logik erfasst, die während der Wiederherstellung eines Fehlers ausgeführt wird, bevor dieser vollständig wiederhergestellt wurde (bevor wir unser catchZiel erreichen).

Stellen Sie sich das konzeptuelle Problem präsentiert es einen Fehler in der Begegnung Mitte von einem Fehler zurückzugewinnen.

Stellen Sie sich einen Datenbankserver vor, auf dem versucht wird, eine Transaktion festzuschreiben, die zur Hälfte fehlschlägt (der Server hat in der Mitte nicht genügend Arbeitsspeicher). Jetzt möchte der Server die Transaktion auf einen Punkt zurücksetzen, an dem nichts passiert ist. Stellen Sie sich jedoch vor, es stößt beim Zurücksetzen auf einen weiteren Fehler. Jetzt haben wir eine halb festgeschriebene Transaktion in der Datenbank - die Atomarität und die Unteilbarkeit der Transaktion sind jetzt aufgehoben, und die Integrität der Datenbank wird jetzt gefährdet.

Dieses konzeptionelle Problem tritt in jeder Sprache auf, die sich mit Fehlern befasst, unabhängig davon, ob es sich um C mit manueller Fehlercode-Weitergabe oder C ++ mit Ausnahmen und Destruktoren oder Java mit Ausnahmen und handelt finally.

finally kann nicht in Sprachen fehlschlagen, die es auf die gleiche Weise bereitstellen, wie Destruktoren in C ++ beim Auftreten von Ausnahmen nicht fehlschlagen können.

Die einzige Möglichkeit, dieses konzeptionelle und schwierige Problem zu vermeiden, besteht darin, sicherzustellen, dass beim Zurücksetzen von Transaktionen und Freigeben von Ressourcen in der Mitte möglicherweise keine rekursive Ausnahme / kein rekursiver Fehler auftritt.

Das einzig sichere Design ist also ein Design, bei dem writer.close()es unmöglich ist, einen Fehler zu machen. Normalerweise gibt es im Entwurf Möglichkeiten, um Szenarien zu vermeiden, in denen solche Dinge mitten in der Wiederherstellung fehlschlagen können, was dies unmöglich macht.

Dies ist leider der einzige Weg - die Fehlerbehebung kann nicht fehlschlagen. Der einfachste Weg, dies sicherzustellen, besteht darin, diese Arten von "Ressourcenfreigabe" - und "Nebenwirkungen" -Funktionen nicht mehr funktionsfähig zu machen. Es ist nicht einfach - eine ordnungsgemäße Fehlerbehebung ist schwierig und leider auch schwer zu testen. Um dies zu erreichen, müssen Sie jedoch sicherstellen, dass bei Funktionen, die "zerstören", "schließen", "herunterfahren", "zurücksetzen" usw., möglicherweise keine externen Fehler auftreten, da solche Funktionen häufig auftreten müssen aufgerufen werden, während ein bestehender Fehler behoben wird.

Beispiel: Protokollierung

Angenommen, Sie möchten Daten in einem finallyBlock protokollieren . Dies ist oft ein großes Problem, es sei denn, die Protokollierung kann nicht fehlschlagen . Die Protokollierung kann mit ziemlicher Sicherheit fehlschlagen, da möglicherweise mehr Daten an die Datei angehängt werden sollen, und dies kann leicht zu vielen Fehlern führen.

Die Lösung hier ist, es so zu machen, dass jede in finallyBlöcken verwendete Protokollierungsfunktion nicht zum Aufrufer geworfen werden kann (es könnte fehlschlagen, aber es wird nicht geworfen). Wie könnten wir das machen? Wenn Ihre Sprache das Werfen im Kontext von "Endlich" zulässt, vorausgesetzt, es gibt einen verschachtelten Try / Catch-Block, ist dies eine Möglichkeit, dem Aufrufer das Werfen zu vermeiden, indem Ausnahmen verschluckt und in Fehlercodes umgewandelt werden Prozess oder Thread, der separat und außerhalb eines vorhandenen Fehlerbehebungsstapels fehlschlagen kann, werden beendet. Solange Sie mit diesem Prozess kommunizieren können, ohne dass die Möglichkeit besteht, dass ein Fehler auftritt, ist dies auch ausnahmesicher, da das Sicherheitsproblem in diesem Szenario nur besteht, wenn wir rekursiv aus demselben Thread heraus werfen.

In diesem Fall können wir mit Protokollierungsfehlern davonkommen, vorausgesetzt, dass sie nicht ausgelöst werden, da das Fehlschlagen der Protokollierung und das Nichtstun nicht das Ende der Welt ist (es gehen keine Ressourcen verloren oder es werden keine Nebeneffekte zurückgesetzt, z. B.).

Wie auch immer, ich bin sicher, Sie können sich bereits vorstellen, wie unglaublich schwierig es ist, eine Software wirklich ausnahmesicher zu machen. Möglicherweise ist es nicht erforderlich, dies in vollem Umfang zu prüfen, es sei denn, es handelt sich um die unternehmenskritischste Software. Beachten Sie jedoch, wie Sie wirklich Ausnahmesicherheit erreichen können, da selbst sehr universelle Bibliotheksautoren häufig hier fummeln und die gesamte Ausnahmesicherheit Ihrer Anwendung durch die Verwendung der Bibliothek zunichte machen.

SomeFileWriter

Wenn Sie SomeFileWriterdie Option "inside" verwenden können close, ist dies im Allgemeinen nicht mit der Ausnahmebehandlung kompatibel, es sei denn, Sie versuchen niemals, sie in einem Kontext zu schließen, in dem eine vorhandene Ausnahme behoben wird. Wenn der Code dafür außerhalb Ihrer Kontrolle liegt, handelt es sich möglicherweise um SOL. Es lohnt sich jedoch, die Autoren über dieses offensichtliche Problem mit der Ausnahmesicherheit zu informieren. Wenn es in Ihrer Kontrolle liegt, ist es meine Hauptempfehlung, sicherzustellen, dass das Schließen mit allen erforderlichen Mitteln nicht fehlschlagen kann.

Stellen Sie sich vor, ein Betriebssystem könnte eine Datei tatsächlich nicht schließen. Jetzt wird jedes Programm, das versucht, eine Datei beim Herunterfahren zu schließen, nicht mehr heruntergefahren . Was sollen wir jetzt tun? Halten Sie einfach die Anwendung offen und ignorieren Sie das Problem (möglicherweise in Ordnung, wenn es nicht so kritisch ist). Das sicherste Design: Machen Sie es so, dass es unmöglich ist, eine Datei nicht zu schließen.


quelle