Perché exception.printStackTrace () è considerata una cattiva pratica?


126

C'è un sacco di materiale fuori che suggerisce che la stampa la traccia dello stack di un'eccezione è cattiva pratica.

Ad esempio, dal check di RegexpSingleline in Checkstyle:

Questo controllo può essere usato [...] per trovare cattive pratiche comuni come chiamare ex.printStacktrace ()

Tuttavia, faccio fatica a trovare un posto che dia un valido motivo per cui, poiché sicuramente la traccia dello stack è molto utile per rintracciare ciò che ha causato l'eccezione. Cose di cui sono a conoscenza:

  1. Una traccia dello stack non dovrebbe mai essere visibile agli utenti finali (per esperienza dell'utente e per motivi di sicurezza)

  2. La generazione di una traccia dello stack è un processo relativamente costoso (anche se è improbabile che sia un problema nella maggior parte delle circostanze "eccezionali")

  3. Molti framework di registrazione stamperanno la traccia dello stack per te (la nostra no e no, non possiamo cambiarla facilmente)

  4. La stampa della traccia dello stack non costituisce una gestione degli errori. Dovrebbe essere combinato con altre informazioni di registrazione e gestione delle eccezioni.

Quali altri motivi ci sono per evitare di stampare una traccia dello stack nel tuo codice?


12
Come persona che deve risolvere regolarmente i problemi, non ometterei mai di stampare uno stack stack quando qualcosa è andato storto. Sì, non mostrarlo all'utente, ma sì, scaricalo in un registro errori.
Paul Grime,

4
Hai davvero bisogno di altri motivi? Penso che tu abbia risposto abbastanza bene alla tua domanda. Tuttavia, stacktrace deve essere stampato in eccezioni eccezionali. :)
Neil,

Risposte:


125

Throwable.printStackTrace()scrive la traccia dello stack su System.errPrintStream. Il System.errflusso e il flusso di output "errore" standard sottostante del processo JVM possono essere reindirizzati da

  • invocando System.setErr()quale cambia la destinazione indicata da System.err.
  • o reindirizzando il flusso di output degli errori del processo. Il flusso di output dell'errore può essere reindirizzato a un file / dispositivo
    • i cui contenuti possono essere ignorati dal personale,
    • il file / dispositivo potrebbe non essere in grado di ruotare il registro, deducendo che è necessario riavviare il processo per chiudere l'handle di file / dispositivo aperto, prima di archiviare il contenuto esistente del file / dispositivo.
    • o il file / dispositivo effettivamente elimina tutti i dati scritti su di esso, come nel caso di /dev/null.

Inferendo a quanto sopra, invocare Throwable.printStackTrace()costituisce solo un comportamento di gestione delle eccezioni valido (non buono / eccezionale)

  • se non sei System.errstato riassegnato per tutta la durata della vita dell'applicazione,
  • e se non è necessaria la rotazione del registro mentre l'applicazione è in esecuzione,
  • e se la pratica di registrazione accettata / progettata dell'applicazione è scrivere System.err(e nel flusso di output degli errori standard della JVM).

Nella maggior parte dei casi, le condizioni di cui sopra non sono soddisfatte. Uno potrebbe non essere a conoscenza di altro codice in esecuzione nella JVM e non è possibile prevedere la dimensione del file di registro o la durata del processo in esecuzione e una pratica di registrazione ben progettata ruoterebbe attorno alla scrittura di file di registro "analizzabili dalla macchina" (un funzione preferibile ma facoltativa in un logger) in una destinazione nota, per facilitare il supporto.

Infine, si dovrebbe ricordare che l'output di Throwable.printStackTrace()verrà sicuramente intercalato con altri contenuti scritti System.err(e possibilmente anche System.outse entrambi vengono reindirizzati allo stesso file / dispositivo). Questo è un fastidio (per le app a thread singolo) che si deve affrontare, poiché i dati relativi alle eccezioni non sono facilmente analizzabili in un tale evento. Peggio ancora, è molto probabile che un'applicazione multi-thread produca registri molto confusi in quanto Throwable.printStackTrace() non è thread-safe .

Non esiste alcun meccanismo di sincronizzazione per sincronizzare la scrittura della traccia dello stack System.errquando vengono richiamati più thread Throwable.printStackTrace()contemporaneamente. La risoluzione di questo richiede in realtà la sincronizzazione del codice sul monitor associato System.err(e anche System.out, se il file / dispositivo di destinazione è lo stesso), e questo è un prezzo piuttosto elevato da pagare per la sanità mentale del file di registro. Per fare un esempio, le classi ConsoleHandlere StreamHandlersono responsabili dell'aggiunta dei record di registro alla console, nella funzione di registrazione fornita da java.util.logging; l'operazione effettiva di pubblicazione dei record di registro è sincronizzata: ogni thread che tenta di pubblicare un record di registro deve anche acquisire il blocco sul monitor associato alStreamHandleresempio. Se si desidera avere la stessa garanzia di disporre di record di registro non interlacciati utilizzando System.out/ System.err, è necessario assicurarsi lo stesso: i messaggi vengono pubblicati su questi flussi in modo serializzabile.

Considerando quanto sopra e gli scenari molto limitati in cui Throwable.printStackTrace()è effettivamente utile, risulta spesso che invocarlo è una cattiva pratica.


Estendendo l'argomento in uno dei paragrafi precedenti, è anche una cattiva scelta da usare Throwable.printStackTraceinsieme a un logger che scrive sulla console. Ciò è in parte dovuto al motivo per cui il logger si sincronizzerebbe su un monitor diverso, mentre l'applicazione (eventualmente, se non si desidera che i record di registro interfogliati) si sincronizzino su un monitor diverso. L'argomento vale anche quando si utilizzano due logger diversi che scrivono nella stessa destinazione, nell'applicazione.


Ottima risposta, grazie. Tuttavia, anche se concordo sul fatto che il più delle volte è rumoroso o non necessario, ci sono momenti in cui è assolutamente critico.
Chris Knight,

1
@Chris Sì, ci sono momenti in cui non puoi non usare System.out.printlne Throwable.printStackTracee, naturalmente, è necessario il giudizio degli sviluppatori. Ero un po 'preoccupato che mancasse la parte sulla sicurezza dei thread. Se osservi la maggior parte delle implementazioni del logger, noterai che sincronizzano la parte in cui sono scritti i record di registro (anche sulla console), sebbene non acquisiscano monitor su System.erro System.out.
Vineet Reynolds,

2
Sarebbe sbagliato notare che nessuno di questi motivi si applica alle sostituzioni di printStackTrace che accettano un oggetto PrintStream o PrintWriter come parametro?
ESRogs

1
@Geek, quest'ultimo è il descrittore del file di processo con valore 2. Il primo è solo un'astrazione.
Vineet Reynolds,

1
Nel codice sorgente JDK di printStackTrace (), utilizza sincronizzato per bloccare System.err PrintStream, quindi dovrebbe essere un metodo thread-safe.
EyouGo,

33

Stai toccando più problemi qui:

1) Una traccia dello stack non dovrebbe mai essere visibile agli utenti finali (per esperienza dell'utente e per motivi di sicurezza)

Sì, dovrebbe essere accessibile per diagnosticare i problemi degli utenti finali, ma l'utente finale non dovrebbe vederli per due motivi:

  • Sono molto oscuri e illeggibili, l'applicazione sembrerà molto ostile per l'utente.
  • Mostrare una traccia dello stack all'utente finale potrebbe comportare un potenziale rischio per la sicurezza. Correggimi se sbaglio, PHP in realtà stampa i parametri delle funzioni nello stack trace - geniale, ma molto pericoloso - se dovessi ottenere un'eccezione mentre ti connetti al database, che cosa hai probabilmente nello stacktrace?

2) Generare una traccia dello stack è un processo relativamente costoso (anche se è improbabile che sia un problema nella maggior parte delle circostanze "eccezionali")

La generazione di una traccia dello stack avviene quando viene creata / generata l'eccezione (ecco perché generare un'eccezione ha un prezzo), la stampa non è così costosa. In effetti, puoi ignorare Throwable#fillInStackTrace()l'eccezione personalizzata in modo efficace, generando un'eccezione quasi economica come una semplice istruzione GOTO.

3) Molti framework di registrazione stamperanno la traccia dello stack per te (la nostra no e no, non possiamo cambiarla facilmente)

Ottimo punto Il problema principale qui è: se il framework registra l'eccezione per te, non fare nulla (ma assicurati che lo faccia!) Se vuoi registrare tu stesso l'eccezione, usa il framework di registrazione come Logback o Log4J , per non metterli sulla console raw perché è molto difficile controllarlo.

Con il framework di registrazione è possibile reindirizzare facilmente le tracce dello stack su file, console o persino inviarle a un indirizzo e-mail specificato. Con hardcoded printStackTrace()devi convivere con il sysout.

4) La stampa della traccia dello stack non costituisce una gestione degli errori. Dovrebbe essere combinato con altre informazioni di registrazione e gestione delle eccezioni.

Ancora una volta: accedi SQLExceptioncorrettamente (con la traccia dello stack completo, usando il framework di registrazione) e mostra bene: messaggio " Spiacenti, al momento non siamo in grado di elaborare la tua richiesta ". Pensi davvero che l'utente sia interessato ai motivi? Hai visto la schermata di errore StackOverflow? È molto divertente, ma non rivela alcun dettaglio. Tuttavia garantisce all'utente che il problema verrà esaminato.

Ma ti chiamerà immediatamente e devi essere in grado di diagnosticare il problema. Quindi hai bisogno di entrambi: registrazione delle eccezioni corretta e messaggi intuitivi.


Per concludere: registra sempre le eccezioni (preferibilmente utilizzando il framework di registrazione ), ma non esponile all'utente finale. Pensa attentamente ai messaggi di errore nella tua GUI, mostra le tracce dello stack solo in modalità di sviluppo.


la tua risposta è quella che ho ricevuto.
Blasanka,

"la maggior parte delle circostanze eccezionali". simpatico.
awwsmm

19

Come prima cosa, printStackTrace () non è costoso, poiché la traccia dello stack viene riempita quando viene creata l'eccezione stessa.

L'idea è di passare tutto ciò che va ai registri attraverso un framework logger, in modo che la registrazione possa essere controllata. Quindi invece di usare printStackTrace, basta usare qualcosa di simileLogger.log(msg, exception);


11

Stampare la traccia dello stack dell'eccezione in sé non costituisce una cattiva pratica, ma la stampa della traccia dello stace solo quando si verifica un'eccezione è probabilmente il problema qui - spesso a volte, solo stampare una traccia dello stack non è sufficiente.

Inoltre, c'è la tendenza a sospettare che la corretta gestione delle eccezioni non venga eseguita se tutto ciò che viene eseguito in un catchblocco è a e.printStackTrace. Una gestione impropria potrebbe significare nella migliore delle ipotesi che un problema viene ignorato e, nella peggiore delle ipotesi, un programma che continua l'esecuzione in uno stato indefinito o imprevisto.

Esempio

Consideriamo il seguente esempio:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Qui, vogliamo eseguire alcune elaborazioni di inizializzazione prima di continuare con alcune elaborazioni che richiedono che l'inizializzazione sia avvenuta.

Nel codice sopra, l'eccezione avrebbe dovuto essere rilevata e gestita correttamente per impedire al programma di procedere al continueProcessingAssumingThatTheStateIsCorrectmetodo che potremmo presumere causerebbe problemi.

In molti casi, e.printStackTrace()indica che viene ingerita un'eccezione e che l'elaborazione può procedere come se non si verificasse alcun problema.

Perché questo è diventato un problema?

Probabilmente uno dei maggiori motivi per cui la scarsa gestione delle eccezioni è diventata più diffusa è dovuto al modo in cui IDE come Eclipse genereranno automaticamente il codice che eseguirà un e.printStackTraceper la gestione delle eccezioni:

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

(Quanto sopra è un vero try-catchgenerato automaticamente da Eclipse per gestire un InterruptedExceptionlancio da Thread.sleep.)

Per la maggior parte delle applicazioni, la semplice stampa della traccia dello stack sull'errore standard non sarà probabilmente sufficiente. La gestione impropria delle eccezioni potrebbe in molti casi portare a un'applicazione in esecuzione in uno stato inaspettato e potrebbe comportare comportamenti imprevisti e indefiniti.


1
Questo. Abbastanza spesso semplicemente non cogliere affatto l'eccezione, almeno a livello di metodo, è meglio che catturare, fare la stampa dello stack stack e continuare come se non si fosse verificato alcun problema.
Eis,

10

Penso che il tuo elenco di motivi sia piuttosto completo.

Un esempio particolarmente negativo che ho riscontrato più di una volta è il seguente:

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

Il problema con il codice sopra è che la gestione consiste interamente nella printStackTracechiamata: l'eccezione non è realmente gestita correttamente né è consentita la fuga.

D'altra parte, di norma registro sempre la traccia dello stack ogni volta che c'è un'eccezione imprevista nel mio codice. Nel corso degli anni questa politica mi ha permesso di risparmiare molto tempo per il debug.

Infine, su una nota più leggera, la perfetta eccezione di Dio .


"L'eccezione non può scappare" - intendevi che l'eccezione è propagata nello stack? in che modo la registrazione è diversa da printStackTrace ()?
MasterJoe

5

printStackTrace()stampa su una console. Nelle impostazioni di produzione, nessuno lo sta mai guardando. Suraj ha ragione, dovrebbe passare queste informazioni a un logger.


Punto valido, anche se osserviamo attentamente l'output della nostra console di produzione.
Chris Knight,

Puoi spiegare cosa intendi osservando attentamente? Come e chi? Con quale frequenza?
Oh Chin Boon,

Osservando attentamente, avrei dovuto dire quando richiesto. È un file di registro continuo che è una delle diverse porte di chiamata se qualcosa va storto con la nostra app.
Chris Knight,

3

Nelle applicazioni server StackTrace esplode il file stdout / stderr. Può diventare sempre più grande ed è pieno di dati inutili perché di solito non hai contesto e nessun timestamp e così via.

ad es. catalina.out quando si utilizza tomcat come contenitore


Buon punto. L'uso abusivo di printStackTrace () potrebbe far esplodere i nostri file di registrazione, o quantomeno riempirli di risme di informazioni inutili
Chris Knight,

@ChrisKnight - potresti per favore suggerire un articolo che spiega come i file di log potrebbero saltare in aria con informazioni inutili? Grazie.
MasterJoe

3

Non è una cattiva pratica perché qualcosa è "sbagliato" in PrintStackTrace (), ma perché è "odore di codice". Il più delle volte la chiamata PrintStackTrace () è presente perché qualcuno non è riuscito a gestire correttamente l'eccezione. Una volta gestita l'eccezione in modo corretto, generalmente non ti interessa più StackTrace.

Inoltre, visualizzare lo stacktrace su stderr è generalmente utile solo durante il debug, non in produzione perché molto spesso stderr non va da nessuna parte. La registrazione ha più senso. Ma la semplice sostituzione di PrintStackTrace () con la registrazione dell'eccezione ti lascia comunque con un'applicazione che non è riuscita ma continua a funzionare come se niente fosse.


0

Come alcuni ragazzi hanno già menzionato qui il problema è con l'eccezione deglutizione nel caso in cui tu chiami semplicemente e.printStackTrace()nel catchblocco. Non interromperà l'esecuzione del thread e continuerà dopo il blocco try come in condizioni normali.

Invece, è necessario provare a ripristinare dall'eccezione (nel caso in cui sia recuperabile), oppure a lanciare RuntimeExceptiono a creare una bolla dell'eccezione per il chiamante al fine di evitare arresti anomali silenziosi (ad esempio, a causa di una configurazione errata del logger).


0

Per evitare il problema del flusso di output aggrovigliato riferito da @Vineet Reynolds

puoi stamparlo sullo stdout: e.printStackTrace(System.out);

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.