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

126

Es gibt eine Menge von Material heraus dort , die , dass die Stack - Trace einer Ausnahme Druck legt nahe , ist eine schlechte Praxis.

ZB vom RegexpSingleline-Check in Checkstyle:

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

Ich habe jedoch Schwierigkeiten, irgendwo zu finden, was einen triftigen Grund dafür gibt, da die Stapelverfolgung sicherlich sehr nützlich ist, um herauszufinden, was die Ausnahme verursacht hat. Dinge, die mir bewusst sind:

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

  2. Das Generieren einer Stapelverfolgung ist ein relativ teurer Prozess (obwohl es unter den meisten "außergewöhnlichen" Umständen unwahrscheinlich ist, dass dies ein Problem darstellt).

  3. Viele Protokollierungsframeworks drucken den Stack-Trace für Sie (bei uns nicht und nein, wir können ihn nicht einfach ändern).

  4. Das Drucken der Stapelverfolgung stellt keine Fehlerbehandlung dar. Es sollte mit anderen Informationsprotokollierungen und Ausnahmebehandlungen kombiniert werden.

Welche anderen Gründe gibt es, um das Drucken eines Stack-Trace in Ihrem Code zu vermeiden?

Chris Knight
quelle
12
Als jemand, der regelmäßig Fehler beheben muss , würde ich niemals das Drucken eines Stacktraces unterlassen, wenn etwas schief gelaufen ist. Ja, zeigen Sie es dem Benutzer nicht an, aber ja, speichern Sie es in einem Fehlerprotokoll.
Paul Grime
4
Benötigen Sie wirklich andere Gründe? Ich denke, Sie haben Ihre eigene Frage ziemlich gut beantwortet. Stacktrace sollte jedoch in Ausnahmefällen gedruckt werden. :)
Neil

Antworten:

125

Throwable.printStackTrace()schreibt den Stack-Trace in System.errPrintStream. Der System.errStream und der zugrunde liegende Standardausgabestream "Fehler" des JVM-Prozesses können von umgeleitet werden

  • Aufrufen, System.setErr()wodurch das Ziel geändert wird, auf das verwiesen wird System.err.
  • oder durch Umleiten des Fehlerausgabestreams des Prozesses. Der Fehlerausgabestream wird möglicherweise in eine Datei / ein Gerät umgeleitet
    • deren Inhalt vom Personal ignoriert werden kann,
    • Die Datei / das Gerät kann möglicherweise nicht protokolliert werden, was darauf schließen lässt, dass ein Prozessneustart erforderlich ist, um das geöffnete Datei- / Gerätehandle zu schließen, 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 der Fall ist /dev/null.

Daraus folgt, dass das Aufrufen nur Throwable.printStackTrace()ein gültiges (nicht gutes / gutes) Verhalten bei der Ausnahmebehandlung darstellt

  • Wenn Sie System.errwährend der gesamten Lebensdauer der Anwendung nicht neu zugewiesen wurden,
  • und wenn Sie keine Protokollrotation benötigen, während die Anwendung ausgeführt wird,
  • und wenn akzeptiert / entworfen, besteht die Protokollierungspraxis der Anwendung darin, in System.err(und den Standardfehlerausgabestream der JVM) zu schreiben .

In den meisten Fällen sind die oben genannten Bedingungen nicht erfüllt. Man kennt möglicherweise keinen anderen Code, der in der JVM ausgeführt wird, und man kann die Größe der Protokolldatei oder die Laufzeit des Prozesses nicht vorhersagen, und eine gut konzipierte Protokollierungspraxis würde sich darauf konzentrieren, "maschinenparsierbare" Protokolldateien zu schreiben (a bevorzugte, aber optionale Funktion in einem Logger) an einem bekannten Ziel, um die Unterstützung zu unterstützen.

Schließlich sollte man bedenken, dass die Ausgabe von Throwable.printStackTrace()definitiv mit anderen Inhalten verschachtelt wird, auf die geschrieben wurde System.err(und möglicherweise sogar, System.outwenn beide auf dieselbe Datei / dasselbe Gerät umgeleitet werden). Dies ist ein Ärger (für Single-Threaded-Apps), mit dem man sich befassen muss, da die Daten um Ausnahmen in einem solchen Fall nicht leicht zu analysieren sind. Schlimmer noch, es ist sehr wahrscheinlich, dass eine Multithread-Anwendung sehr verwirrende Protokolle erstellt, da sie Throwable.printStackTrace() nicht threadsicher ist .

Es gibt keinen Synchronisationsmechanismus, mit dem das Schreiben des Stack-Trace synchronisiert werden kann, System.errwenn mehrere Threads gleichzeitig aufgerufen Throwable.printStackTrace()werden. Um dies zu beheben, muss Ihr Code auf dem zugehörigen Monitor synchronisiert werden System.err(und auch System.out, wenn die Zieldatei / das Gerät identisch ist), und das ist ein ziemlich hoher Preis für die Vernunft von Protokolldateien. Um ein Beispiel zu nehmen, die ConsoleHandlerund StreamHandlersind Klassen , die für das Anhängen Protokollaufzeichnungen in Konsole, in der Logging - Einrichtung zur Verfügung gestellt von java.util.logging; Der eigentliche Vorgang zum Veröffentlichen von Protokolldatensätzen wird synchronisiert. Jeder Thread, der versucht, einen Protokolldatensatz zu veröffentlichen, muss auch die Sperre für den mit dem verknüpften Monitor erhaltenStreamHandlerBeispiel. Wenn Sie die gleiche Garantie für nicht verschachtelte Protokolldatensätze mit System.out/ haben möchten, müssen System.errSie dies auch sicherstellen - die Nachrichten werden auf serialisierbare Weise in diesen Streams veröffentlicht.

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


Wenn Sie das Argument in einem der vorherigen Absätze erweitern, ist es auch eine schlechte Wahl, es Throwable.printStackTracein Verbindung mit einem Logger zu verwenden, der auf die Konsole schreibt. Dies liegt zum Teil daran, dass der Logger auf einem anderen Monitor synchronisiert wird, während Ihre Anwendung (möglicherweise, wenn Sie keine verschachtelten Protokolldatensätze wünschen) auf einem anderen Monitor synchronisiert wird. Das Argument gilt auch, wenn Sie in Ihrer Anwendung zwei verschiedene Logger verwenden, die auf dasselbe Ziel schreiben.

Vineet Reynolds
quelle
Tolle Antwort, danke. Obwohl ich der Meinung bin, dass es die meiste Zeit Lärm macht oder nicht notwendig ist, gibt es Zeiten, in denen es absolut kritisch ist.
Chris Knight
1
@ Chris : Ja, es gibt Zeiten , wenn Sie nicht , sondern benutzen System.out.printlnund Throwable.printStackTraceund natürlich Entwickler Abwägen erfordern. Ich war etwas besorgt, dass der Teil über die Thread-Sicherheit übersehen wurde. Wenn Sie sich die meisten Logger-Implementierungen ansehen, werden Sie feststellen, dass sie den Teil synchronisieren, in den Protokolldatensätze geschrieben werden (sogar in die Konsole), obwohl sie keine Monitore auf System.erroder erfassen System.out.
Vineet Reynolds
2
Wäre es falsch zu bemerken, dass keiner dieser Gründe für die printStackTrace-Überschreibungen gilt, die ein PrintStream- oder PrintWriter-Objekt als Parameter verwenden?
ESRogs
1
@Geek, letzteres ist der Prozessdateideskriptor mit Wert 2. Ersteres ist nur eine Abstraktion.
Vineet Reynolds
1
Im JDK-Quellcode von printStackTrace () wird synchronized zum Sperren von System.err PrintStream verwendet, daher sollte es sich um eine thread-sichere Methode handeln.
EyouGo
33

Sie berühren hier mehrere Themen:

1) Ein Stack-Trace sollte für Endbenutzer niemals sichtbar sein (aus Gründen der Benutzererfahrung 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 dunkel und unlesbar, die Anwendung sieht sehr benutzerunfreundlich aus.
  • Das Anzeigen einer Stapelverfolgung für den Endbenutzer kann ein potenzielles Sicherheitsrisiko darstellen. Korrigieren Sie mich, wenn ich falsch liege. PHP druckt tatsächlich Funktionsparameter im Stack-Trace - brillant, aber sehr gefährlich. Wenn Sie beim Herstellen einer Verbindung zur Datenbank eine Ausnahme erhalten, was werden Sie wahrscheinlich im Stacktrace tun?

2) Das Generieren einer Stapelverfolgung ist ein relativ teurer Prozess (obwohl es unter den meisten Ausnahmebedingungen unwahrscheinlich ist, dass dies ein Problem darstellt).

Das Generieren einer Stapelverfolgung erfolgt, 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()Ihre benutzerdefinierte Ausnahme effektiv überschreiben, sodass das Auslösen einer Ausnahme fast so billig ist wie eine einfache GOTO-Anweisung.

3) Viele Protokollierungsframeworks drucken den Stack-Trace für Sie (bei uns 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 (aber stellen Sie 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 weil es sehr schwer ist, es zu kontrollieren.

Mit dem Protokollierungsframework können Sie Stack-Traces einfach in Dateien umleiten, konsolidieren oder sogar an eine bestimmte E-Mail-Adresse senden. Mit hardcoded muss printStackTrace()man mit dem leben sysout.

4) Das Drucken der Stapelverfolgung stellt keine Fehlerbehandlung dar. Es sollte mit anderen Informationsprotokollierungen und Ausnahmebehandlungen kombiniert werden.

Nochmals: Melden Sie sich SQLExceptionkorrekt an (mit dem vollständigen Stack-Trace unter Verwendung des Protokollierungsframeworks) und zeigen Sie Folgendes an: " Entschuldigung, wir können Ihre Anfrage derzeit nicht verarbeiten ". Denken 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 wird Sie sofort anrufen und Sie müssen in der Lage sein, das Problem zu diagnostizieren. Sie benötigen also beides: eine ordnungsgemäße Ausnahmeprotokollierung und benutzerfreundliche Nachrichten.


Zum Abschluss: Protokollieren Sie Ausnahmen immer (vorzugsweise mithilfe des Protokollierungsframeworks ), setzen Sie sie jedoch nicht dem Endbenutzer zur Verfügung. Denken Sie sorgfältig über Fehlermeldungen in Ihrer GUI nach und zeigen Sie Stack-Traces nur im Entwicklungsmodus an.

Tomasz Nurkiewicz
quelle
Ihre Antwort ist die, die ich bekommen habe.
Blasanka
"die meisten" außergewöhnlichen "Umstände". nett.
awwsmm
19

Als erstes ist printStackTrace () nicht teuer, wie Sie angeben , da der Stack-Trace gefüllt wird, wenn die Ausnahme selbst erstellt wird.

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

Suraj Chandran
quelle
11

Das Drucken des Stack-Trace der Ausnahme an sich ist keine schlechte Praxis, sondern nur Drucken des Stace-Trace Auftreten einer Ausnahme ist hier wahrscheinlich das Problem. Oft reicht es nicht aus, nur einen Stack-Trace zu drucken.

Es besteht auch die Tendenz zu vermuten, dass keine ordnungsgemäße Ausnahmebehandlung durchgeführt wird, wenn in einem catchBlock nur a ausgeführt wird e.printStackTrace. Eine unsachgemäße Behandlung kann bedeuten, dass im besten Fall ein Problem ignoriert wird und im schlimmsten Fall ein Programm, das weiterhin in einem undefinierten oder unerwarteten Zustand ausgeführt wird.

Beispiel

Betrachten wir das folgende Beispiel:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

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

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

In vielen Fällen e.printStackTrace()ist dies ein Hinweis darauf, dass eine Ausnahme verschluckt wird und die Verarbeitung so 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 immer häufiger auftritt, die Tatsache, dass IDEs wie Eclipse automatisch Code generieren, der eine e.printStackTraceAusnahmebehandlung ausführt :

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

(Das Obige ist eine try-catchvon Eclipse automatisch generierte Version, die von einem InterruptedExceptiongeworfen wirdThread.sleep .)

Für die meisten Anwendungen reicht es wahrscheinlich nicht aus, nur den Stack-Trace auf Standardfehler zu drucken. Eine unsachgemäße Ausnahmebehandlung kann in vielen Fällen dazu führen, dass eine Anwendung in einem unerwarteten Zustand ausgeführt wird und zu unerwartetem und undefiniertem Verhalten führt.

Coobird
quelle
1
Dies. Sehr oft ist es besser , die Ausnahme überhaupt nicht abzufangen , zumindest auf Methodenebene, als abzufangen, den Stack-Trace zu drucken und fortzufahren, als ob kein Problem aufgetreten wäre.
Eis
10

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 ist, dass die Behandlung vollständig aus dem printStackTraceAufruf besteht: Die Ausnahme wird nicht richtig behandelt und darf nicht entkommen.

Andererseits protokolliere ich in der Regel immer den Stack-Trace, wenn mein Code eine unerwartete Ausnahme enthält. Im Laufe der Jahre hat mir diese Richtlinie viel Zeit beim Debuggen gespart.

Zum Schluss noch Gottes leichtere Ausnahme .

NPE
quelle
"Ausnahme darf nicht entkommen" - meinten Sie, dass die Ausnahme auf dem Stapel weitergegeben wird? Wie unterscheidet sich die Protokollierung von printStackTrace ()?
MasterJoe
5

printStackTrace()druckt auf eine Konsole. In Produktionsumgebungen sieht das noch niemand. Suraj ist korrekt, sollte diese Informationen an einen Logger weitergeben.

Oh Chin Boon
quelle
Gültiger Punkt, obwohl wir die Ausgabe unserer Produktionskonsole sorgfältig beobachten.
Chris Knight
Können Sie erklären, was Sie unter sorgfältiger Beobachtung verstehen? Wie und wer? Mit welcher Frequenz?
Oh Chin Boon
Bei sorgfältiger Beobachtung hätte ich sagen sollen, wann es erforderlich ist. Es ist eine fortlaufende Protokolldatei, die eine von mehreren Anlaufstellen ist, wenn mit unserer App etwas schief geht.
Chris Knight
3

In Serveranwendungen sprengt der Stacktrace Ihre stdout / stderr-Datei. Es kann immer größer werden und ist mit nutzlosen Daten gefüllt, da Sie normalerweise keinen Kontext und keinen Zeitstempel haben und so weiter.

zB Catalina.out bei Verwendung von Tomcat als Container

Hajo Thelen
quelle
Guter Punkt. Die missbräuchliche Verwendung von printStackTrace () kann unsere Protokolldateien sprengen oder zumindest mit unzähligen nutzlosen Informationen füllen
Chris Knight,
@ChrisKnight - Könnten Sie bitte einen Artikel vorschlagen, in dem erklärt wird, wie Protokolldateien mit nutzlosen Informationen in die Luft gesprengt werden können? Vielen Dank.
MasterJoe
3

Es ist keine schlechte Praxis, weil PrintStackTrace () nicht stimmt, sondern weil es nach Code riecht. Meistens ist der PrintStackTrace () -Aufruf vorhanden, weil jemand die Ausnahme nicht ordnungsgemäß behandelt hat. Sobald Sie die Ausnahme richtig behandelt haben, interessiert Sie die StackTrace im Allgemeinen nicht mehr.

Darüber hinaus ist das Anzeigen des Stacktraces auf stderr im Allgemeinen nur beim Debuggen nützlich, nicht in der Produktion, da stderr sehr oft nirgendwo hinführt. Protokollierung macht mehr Sinn. Wenn Sie jedoch nur PrintStackTrace () durch die Protokollierung der Ausnahme ersetzen, bleibt eine Anwendung erhalten, die fehlgeschlagen ist, aber weiterhin ausgeführt wird, als wäre nichts passiert.

AVee
quelle
0

Wie einige Leute hier bereits erwähnt haben, liegt das Problem mit der Ausnahme des Schluckens, falls Sie nur e.printStackTrace()den catchBlock anrufen . Die Thread-Ausführung wird nicht gestoppt und nach dem try-Block wie im normalen Zustand fortgesetzt.

Stattdessen müssen Sie entweder versuchen, die Ausnahme zu beheben (falls sie wiederhergestellt werden kann) oder RuntimeExceptiondie Ausnahme an den Anrufer zu senden oder zu sprudeln, um stille Abstürze zu vermeiden (z. B. aufgrund einer falschen Logger-Konfiguration).

Gummis
quelle
0

Um das von @Vineet Reynolds erwähnte Problem des verwickelten Ausgabestreams zu vermeiden

Sie können es auf dem Standard drucken: e.printStackTrace(System.out);

Traeyee
quelle