wake-up-neo.com

Warum wird exception.printStackTrace () als schlechte Praxis angesehen?

Es gibt ein lot of material out dort , das darauf hindeutet, dass das Drucken der Stapelverfolgung einer Ausnahme eine schlechte Praxis ist. 

Z.B. aus dem RegexpSingleline check in Checkstyle:

Diese Prüfung kann [...] verwendet werden, um häufig auftretende schlechte Praktiken wie das Aufrufen von ex.printStacktrace () zu finden.

Ich bemühe mich jedoch, irgendwo einen Ort zu finden, der einen gültigen Grund dafür gibt, warum die Stack-Ablaufverfolgung sicherlich sehr nützlich ist, um die Ursache der Ausnahme zu ermitteln. Dinge, die mir bekannt sind:

  1. Ein Stack-Trace sollte für Endbenutzer niemals sichtbar sein (aus Gründen der Benutzerfreundlichkeit und aus Sicherheitsgründen).

  2. Das Generieren einer Stack-Ablaufverfolgung ist ein relativ teurer Prozess (in den meisten "außergewöhnlichen" Umständen ist dies jedoch wahrscheinlich kein Problem).

  3. Viele Protokollierungs-Frameworks drucken die Stapelablaufverfolgung für Sie aus (unsere und nicht, wir können sie nicht leicht ändern)

  4. Das Drucken der Stapelablaufverfolgung stellt keine Fehlerbehandlung dar. Es sollte mit anderer Informationsprotokollierung und Ausnahmebehandlung kombiniert werden.

Welche anderen Gründe gibt es, um das Drucken einer Stapelverfolgung in Ihrem Code zu vermeiden?

114
Chris Knight

Throwable.printStackTrace() schreibt den Stack-Trace in System.err PrintStream. Der System.err-Stream und der zugrunde liegende Standard-Fehlerstrom "error" des JVM-Prozesses können mit umgeleitet werden

  • aufruf von System.setErr() was das Ziel ändert, auf das System.err zeigt.
  • oder durch Umleiten des Fehlerausgabestroms des Prozesses. Der Fehlerausgabestrom kann zu einer Datei/einem Gerät umgeleitet werden
    • deren Inhalt vom Personal ignoriert werden kann,
    • die Datei/das Gerät ist möglicherweise nicht in der Lage, die Protokollrotation zu protokollieren. Daraus folgt, dass zum Schließen der geöffneten Datei/des Gerätehandles ein Neustart des Prozesses erforderlich ist, bevor der vorhandene Inhalt der Datei/des Geräts archiviert wird.
    • oder die Datei/das Gerät verwirft tatsächlich alle darauf geschriebenen Daten, wie dies bei /dev/null der Fall ist.

Daraus lässt sich schließen, dass das Aufrufen von Throwable.printStackTrace() nur ein gültiges (nicht gutes/großes) Ausnahmebehandlungsverhalten darstellt

  • wenn Sie System.err nicht während der gesamten Lebensdauer der Anwendung neu zugewiesen haben,
  • und wenn Sie während der Anwendung keine Protokollrotation benötigen,
  • und wenn die Protokollierungspraxis der Anwendung akzeptiert/entworfen ist, in System.err (und den Standardfehlerausgabestrom der JVM) zu schreiben.

In den meisten Fällen sind die oben genannten Bedingungen nicht erfüllt. Es kann sein, dass Sie keinen anderen Code kennen, der in der JVM ausgeführt wird, und Sie können die Größe der Protokolldatei oder die Laufzeit des Prozesses nicht vorhersagen. Eine gut entwickelte Protokollierungspraxis würde sich auf das Schreiben von "maschinenlesbaren" Protokolldateien (a bevorzugt, aber optional in einem Logger) an einem bekannten Zielort, um die Unterstützung zu erleichtern.

Schließlich sollte man sich daran erinnern, dass die Ausgabe von Throwable.printStackTrace() definitiv mit anderen Inhalten, die in System.err geschrieben wurden, verschachtelt werden könnte (und möglicherweise sogar System.out, wenn beide in dieselbe Datei/auf dasselbe Gerät umgeleitet werden). Dies ist ein Ärgernis (für Single-Threaded-Apps), mit dem man umgehen muss, da die Daten um Ausnahmen in einem solchen Ereignis nicht leicht analysiert werden können. Schlimmer noch, es ist sehr wahrscheinlich, dass eine Multithread-Anwendung sehr verwirrende Protokolle erzeugt, da Throwable.printStackTrace()nicht threadsicher ist.

Es gibt keinen Synchronisationsmechanismus, um das Schreiben des Stack-Trace mit System.err zu synchronisieren, wenn mehrere Threads gleichzeitig Throwable.printStackTrace() aufrufen. Um dieses Problem zu lösen, muss Ihr Code auf dem mit System.err (und auch System.out, wenn die Zieldatei/-vorrichtung identisch ist) verbundenen Monitor synchronisiert werden. Dies ist ein ziemlich hoher Preis für die Protokolldateien. So sind beispielsweise die Klassen ConsoleHandler und StreamHandler für das Anhängen von Protokolldatensätzen an die Konsole zuständig, und zwar in der von Java.util.logging bereitgestellten Protokollierungsfunktion. Der eigentliche Vorgang zum Veröffentlichen von Protokolldatensätzen wird synchronisiert. Jeder Thread, der versucht, einen Protokolldatensatz zu veröffentlichen, muss auch die Sperre des Monitors erwerben, der der StreamHandler-Instanz zugeordnet ist. Wenn Sie die gleiche Garantie für nicht verschachtelte Protokollsätze mit System.out/System.err haben möchten, müssen Sie dasselbe sicherstellen - die Nachrichten werden seriell in diesen Streams veröffentlicht.

In Anbetracht all der oben genannten und der sehr eingeschränkten Szenarien, in denen Throwable.printStackTrace() tatsächlich nützlich ist, stellt sich oft heraus, dass das Aufrufen eine schlechte Praxis ist.


Um das Argument in einem der vorhergehenden Absätze zu erweitern, ist es auch eine schlechte Wahl, Throwable.printStackTrace in Verbindung mit einem Logger zu verwenden, der in die Konsole schreibt. Dies ist zum Teil darauf zurückzuführen, dass der Logger auf einem anderen Monitor synchronisiert wurde, während Ihre Anwendung (möglicherweise, wenn Sie keine verschachtelten Protokollsätze verwenden möchten) auf einem anderen Monitor synchronisiert. Das Argument gilt auch, wenn Sie in Ihrer Anwendung zwei verschiedene Logger verwenden, die an dasselbe Ziel schreiben.

109
Vineet Reynolds

Sie haben hier mehrere Probleme angesprochen:

1) Ein Stack-Trace sollte für Endbenutzer niemals sichtbar sein (aus Gründen der Benutzerfreundlichkeit und der Sicherheit).

Ja, es sollte zugänglich sein, um Probleme von Endbenutzern zu diagnostizieren, aber Endbenutzer sollten sie aus zwei Gründen nicht sehen:

  • Sie sind sehr undurchsichtig und unleserlich, die Anwendung wird sehr benutzerfreundlich aussehen.
  • Das Anzeigen eines Stack-Trace für den Endbenutzer kann ein potenzielles Sicherheitsrisiko darstellen. Korrigieren Sie mich, wenn ich falsch liege. PHP gibt tatsächlich Funktionsparameter im Stack-Trace aus - brillant, aber sehr gefährlich.

2) Das Generieren eines Stack-Trace ist ein relativ teurer Prozess (obwohl es in den meisten Ausnahmesituationen unwahrscheinlich ist).

Das Generieren einer Stapelablaufverfolgung geschieht, wenn die Ausnahme erstellt/ausgelöst wird (daher ist das Auslösen einer Ausnahme mit einem Preis verbunden). Das Drucken ist nicht so teuer. Tatsächlich können Sie Throwable#fillInStackTrace() in Ihrer benutzerdefinierten Ausnahme überschreiben, was das Auslösen einer Ausnahme fast so billig macht wie eine einfache GOTO-Anweisung.

3) Viele Protokollierungs-Frameworks drucken den Stack-Trace für Sie aus (unser nicht und nein, wir können ihn nicht einfach ändern).

Sehr guter Punkt. Das Hauptproblem hierbei ist: Wenn das Framework die Ausnahme für Sie protokolliert, tun Sie nichts (stellen Sie jedoch sicher, dass dies der Fall ist!). Wenn Sie die Ausnahme selbst protokollieren möchten, verwenden Sie das Protokollierungsframework wie Logback oder Log4J , um sie nicht auf der Raw-Konsole abzulegen, da es sehr schwer ist, sie zu kontrollieren.

Mit dem Protokollierungsframework können Sie Stapelverfolgungen einfach an eine Datei oder Konsole umleiten oder an eine angegebene E-Mail-Adresse senden. Mit fest codiertem printStackTrace() müssen Sie mit dem sysout leben.

4) Das Drucken der Stapelverfolgung stellt keine Fehlerbehandlung dar. Es sollte mit anderen Informationen zur Protokollierung und Ausnahmebehandlung kombiniert werden.

Nochmals: loggen Sie SQLException korrekt (mit dem vollständigen Stack-Trace, unter Verwendung des Protokollierungs-Frameworks) und zeigen Sie Nice an: " Entschuldigung, Ihre Anfrage kann derzeit nicht bearbeitet werden." . Glauben Sie wirklich, dass der Benutzer an den Gründen interessiert ist? Haben Sie den StackOverflow-Fehlerbildschirm gesehen? Es ist sehr humorvoll, enthüllt aber keine Details. Es stellt jedoch sicher, dass der Benutzer das Problem untersucht.

Aber er ruft Sie sofort an und Sie müssen in der Lage sein, das Problem zu diagnostizieren. Sie benötigen also beides: eine ordnungsgemäße Ausnahmeprotokollierung und benutzerfreundliche Meldungen.


Zum Abschluss: protokollieren Sie Ausnahmen immer (vorzugsweise mit - Protokollierungsframework ), aber machen Sie sie nicht verfügbar an den Endbenutzer. Überlegen Sie genau und überlegen Sie sich Fehlermeldungen in Ihrer GUI. Zeigen Sie Stack-Traces nur im Entwicklungsmodus an.

32

printStackTrace () ist nicht teuer, da Sie die Stack-Ablaufverfolgung füllen, wenn die Ausnahme selbst erstellt wird.

Die Idee ist, alles, was an die Protokolle geht, durch ein Logger-Framework zu übergeben, damit die Protokollierung gesteuert werden kann. Verwenden Sie daher anstelle von printStackTrace einfach etwas wie Logger.log(msg, exception);.

17
Suraj Chandran

Das Ausdrucken der Stapelverfolgung der Ausnahme an sich stellt keine schlechte Praxis dar, aber nur das Drucken der Staceverfolgung, wenn eine Ausnahme auftritt, ist hier wahrscheinlich das Problem - oftmals reicht es nicht aus, eine Stapelverfolgung zu drucken.

Außerdem besteht die Tendenz zu vermuten, dass keine ordnungsgemäße Ausnahmebehandlung durchgeführt wird, wenn in einem catch-Block nur ein e.printStackTrace ausgeführt wird. Unsachgemäße Behandlung kann im besten Fall bedeuten, dass ein Problem ignoriert wird, und im schlimmsten Fall ein Programm, das in einem undefinierten oder unerwarteten Zustand ausgeführt wird.

Beispiel

Betrachten wir folgendes Beispiel:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

Hier möchten wir eine Initialisierungsverarbeitung durchführen, bevor wir mit einer Verarbeitung fortfahren, bei der die Initialisierung stattgefunden hat.

Im obigen Code sollte die Ausnahme abgefangen und ordnungsgemäß behandelt worden sein, um zu verhindern, dass das Programm mit der continueProcessingAssumingThatTheStateIsCorrect-Methode fortfährt, von der wir annehmen könnten, dass sie Probleme verursachen würde.

In vielen Fällen ist e.printStackTrace() ein Hinweis darauf, dass eine Ausnahme verschluckt wird und die Verarbeitung fortgesetzt werden kann, als ob kein Problem aufgetreten wäre.

Warum ist das ein Problem geworden?

Wahrscheinlich ist einer der Hauptgründe dafür, dass eine schlechte Ausnahmebehandlung häufiger auftritt, weil IDEs wie Eclipse automatisch Code generieren, der einen e.printStackTrace für die Ausnahmebehandlung durchführt:

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(Das obige ist ein try-catch, der von Eclipse automatisch generiert wurde, um eine InterruptedException zu behandeln, die von Thread.sleep geworfen wird.)

Für die meisten Anwendungen reicht es wahrscheinlich nicht aus, die Stack-Ablaufverfolgung auf Standardfehler zu drucken. Eine fehlerhafte Ausnahmebehandlung kann in vielen Fällen dazu führen, dass eine Anwendung in einem unerwarteten Status ausgeführt wird, der zu unerwartetem und undefiniertem Verhalten führen kann.

10
coobird

Ich denke, Ihre Liste der Gründe ist ziemlich umfassend.

Ein besonders schlechtes Beispiel, dem ich mehr als einmal begegnet bin, lautet wie folgt:

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

Das Problem mit dem obigen Code besteht darin, dass die Behandlung aus vollständig des printStackTrace-Aufrufs besteht: Die Ausnahme wird nicht richtig behandelt, und sie kann nicht entgehen.

Auf der anderen Seite protokolliere ich in der Regel immer die Stack-Ablaufverfolgung, wenn in meinem Code eine unerwartete Ausnahme auftritt. Diese Politik hat mir im Laufe der Jahre eine Menge Zeit beim Debuggen gespart.

Zum Schluss noch etwas leichter: Gottes perfekte Ausnahme .

9
NPE

printStackTrace() druckt auf eine Konsole. In Produktionsumgebungen schaut sich das nie jemand an. Suraj ist richtig, sollte diese Informationen an einen Logger weitergeben. 

4
Oh Chin Boon

In Serveranwendungen sprengt der stacktrace Ihre stdout/stderr-Datei. Es kann immer größer werden und wird mit unbrauchbaren Daten gefüllt, da Sie normalerweise keinen Kontext und keinen Zeitstempel usw. haben.

z.B. catalina.out bei Verwendung von Tomcat als Container

3
Hajo Thelen

Es ist keine schlechte Praxis, weil bei PrintStackTrace () etwas "falsch" ist, sondern weil es "Code-Geruch" ist. Meistens ist der PrintStackTrace () - Aufruf vorhanden, weil die Ausnahmebedingung nicht ordnungsgemäß verarbeitet wurde. Wenn Sie sich mit der Ausnahmebedingung richtig befassen, kümmert Sie sich in der Regel nicht mehr um das StackTrace. 

Darüber hinaus ist das Anzeigen des Stacktraces auf stderr im Allgemeinen nur beim Debuggen hilfreich, nicht in der Produktion, da stderr häufig nicht weitergeht. Protokollieren ist sinnvoller. Wenn Sie jedoch nur PrintStackTrace () durch Protokollieren der Ausnahme ersetzen, bleibt Ihnen eine Anwendung erhalten, die fehlgeschlagen ist, aber weiterhin ausgeführt wird, als wäre nichts passiert. 

3
AVee

Wie bereits erwähnt, ist das Problem das Schlucken der Ausnahme, falls Sie einfach e.printStackTrace() im catch-Block aufrufen. Die Thread-Ausführung wird nicht angehalten und nach dem try-Block wie im normalen Zustand fortgesetzt. 

Stattdessen müssen Sie entweder versuchen, die Ausnahme zu beheben (falls sie wiederherstellbar ist), oder RuntimeException auszulösen oder die Ausnahme an den Aufrufer weiterzuleiten, um stille Abstürze zu vermeiden (z. B. aufgrund einer falschen Konfiguration der Logger).

0
gumkins