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:
Ein Stack-Trace sollte für Endbenutzer niemals sichtbar sein (aus Gründen der Benutzerfreundlichkeit und aus Sicherheitsgründen).
Das Generieren einer Stack-Ablaufverfolgung ist ein relativ teurer Prozess (in den meisten "außergewöhnlichen" Umständen ist dies jedoch wahrscheinlich kein Problem).
Viele Protokollierungs-Frameworks drucken die Stapelablaufverfolgung für Sie aus (unsere und nicht, wir können sie nicht leicht ändern)
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?
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
System.setErr()
was das Ziel ändert, auf das System.err
zeigt./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
System.err
nicht während der gesamten Lebensdauer der Anwendung neu zugewiesen haben,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.
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:
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.
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);
.
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.
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 .
printStackTrace()
druckt auf eine Konsole. In Produktionsumgebungen schaut sich das nie jemand an. Suraj ist richtig, sollte diese Informationen an einen Logger weitergeben.
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
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.
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).