¿Por qué la excepción.printStackTrace () se considera una mala práctica?

126

Hay una gran cantidad de material de salir hay que sugiere que la impresión de la traza de la pila de una excepción es una mala práctica.

Por ejemplo, de la verificación RegexpSingleline en Checkstyle:

Esta verificación se puede utilizar [...] para encontrar malas prácticas comunes, como llamar a ex.printStacktrace ()

Sin embargo, estoy luchando por encontrar cualquier lugar que proporcione una razón válida por la cual, seguramente, el seguimiento de la pila es muy útil para rastrear qué causó la excepción. Cosas de las que soy consciente:

  1. Un seguimiento de la pila nunca debe ser visible para los usuarios finales (para la experiencia del usuario y con fines de seguridad)

  2. Generar un seguimiento de la pila es un proceso relativamente costoso (aunque es poco probable que sea un problema en la mayoría de las circunstancias 'excepcionales')

  3. Muchos marcos de registro imprimirán el seguimiento de la pila por usted (el nuestro no y no, no podemos cambiarlo fácilmente)

  4. Imprimir el seguimiento de la pila no constituye un manejo de errores. Debe combinarse con otro registro de información y manejo de excepciones.

¿Qué otras razones hay para evitar imprimir una traza de pila en su código?

Chris Knight
fuente
12
Como alguien que tiene que solucionar problemas regularmente, nunca omitiría imprimir un seguimiento de pila cuando algo sale mal. Sí, no se lo muestre al usuario, pero sí, vuélvalo a un registro de errores.
Paul Grime
44
¿Realmente necesitas otras razones? Creo que has respondido tu propia pregunta bastante bien. Sin embargo, stacktrace debe imprimirse en excepciones excepcionales. :)
Neil

Respuestas:

125

Throwable.printStackTrace()escribe el seguimiento de la pila en System.errPrintStream. La System.errsecuencia y la secuencia de salida de "error" estándar subyacente del proceso JVM pueden ser redirigidas por

  • invocando System.setErr()que cambia el destino señalado por System.err.
  • o redirigiendo la secuencia de salida de error del proceso. El flujo de salida de error puede ser redirigido a un archivo / dispositivo
    • cuyos contenidos pueden ser ignorados por el personal,
    • el archivo / dispositivo puede no ser capaz de rotar el registro, infiriendo que se requiere reiniciar el proceso para cerrar el identificador de archivo / dispositivo abierto, antes de archivar el contenido existente del archivo / dispositivo.
    • o el archivo / dispositivo realmente descarta todos los datos escritos en él, como es el caso de /dev/null.

Inferir de lo anterior, invocar Throwable.printStackTrace()constituye un comportamiento de manejo de excepciones válido (no bueno / excelente), solo

  • si no ha System.errsido reasignado durante toda la vida útil de la aplicación,
  • y si no necesita rotación de registro mientras se ejecuta la aplicación,
  • y si la práctica de registro aceptada / diseñada de la aplicación es escribir en System.err(y el flujo de salida de error estándar de la JVM).

En la mayoría de los casos, las condiciones anteriores no se cumplen. Es posible que no se tenga conocimiento de otro código que se ejecuta en la JVM, y no se puede predecir el tamaño del archivo de registro o la duración del tiempo de ejecución del proceso, y una práctica de registro bien diseñada girará en torno a la escritura de archivos de registro "procesables por máquina" (un característica preferible pero opcional en un registrador) en un destino conocido, para ayudar en el soporte.

Finalmente, uno debería recordar que la salida de Throwable.printStackTrace()definitivamente se intercalaría con otro contenido escrito System.err(y posiblemente incluso System.outsi ambos se redirigen al mismo archivo / dispositivo). Esta es una molestia (para aplicaciones de subproceso único) con la que uno debe lidiar, ya que los datos en torno a las excepciones no se pueden analizar fácilmente en tal caso. Peor aún, es muy probable que una aplicación multiproceso produzca registros muy confusos ya Throwable.printStackTrace() que no es segura para subprocesos .

No existe un mecanismo de sincronización para sincronizar la escritura del seguimiento de la pila System.errcuando se invocan varios subprocesos Throwable.printStackTrace()al mismo tiempo. Resolver esto realmente requiere que su código se sincronice en el monitor asociado con System.err(y también System.out, si el archivo / dispositivo de destino es el mismo), y ese es un precio bastante alto a pagar por la sanidad del archivo de registro. Para tomar un ejemplo, las clases ConsoleHandlery StreamHandlerson responsables de agregar registros de registro a la consola, en la instalación de registro proporcionada por java.util.logging; la operación real de publicación de registros de registro está sincronizada: cada subproceso que intente publicar un registro de registro también debe adquirir el bloqueo en el monitor asociado con elStreamHandlerejemplo. Si desea tener la misma garantía de tener registros de registros no intercalados utilizando System.out/ System.err, debe asegurarse de lo mismo: los mensajes se publican en estas transmisiones de manera serializable.

Teniendo en cuenta todo lo anterior, y los escenarios muy restringidos en los que Throwable.printStackTrace()es realmente útil, a menudo resulta que invocarlo es una mala práctica.


Extendiendo el argumento en uno de los párrafos anteriores, también es una mala elección usar Throwable.printStackTracejunto con un registrador que escribe en la consola. Esto se debe en parte a que el registrador se sincronizaría en un monitor diferente, mientras que su aplicación (posiblemente, si no desea registros de registros intercalados) se sincronizaría en un monitor diferente. El argumento también es válido cuando utiliza dos registradores diferentes que escriben en el mismo destino, en su aplicación.

Vineet Reynolds
fuente
Gran respuesta, gracias. Sin embargo, aunque estoy de acuerdo en que la mayoría de las veces es ruido o no es necesario, hay momentos en los que es absolutamente crítico.
Chris Knight
1
@ Chris Sí, hay momentos en que no se puede utilizar, pero System.out.printlne Throwable.printStackTracey, por supuesto, se requiere un juicio desarrollador. Estaba un poco preocupado de que se perdiera la parte sobre la seguridad del hilo. Si observa la mayoría de las implementaciones de registradores, notará que sincronizan la parte donde se escriben los registros de registro (incluso en la consola), aunque no adquieren monitores en System.erro System.out.
Vineet Reynolds
2
¿Me equivocaría al notar que ninguno de estos motivos se aplica a las anulaciones printStackTrace que toman un objeto PrintStream o PrintWriter como parámetro?
ESRogs
1
@ Geek, este último es el descriptor del archivo de proceso con valor 2. El primero es solo una abstracción.
Vineet Reynolds
1
En el código fuente JDK de printStackTrace (), se usa sincronizado para bloquear el System.err PrintStream, por lo que debería ser un método seguro para subprocesos.
EyouGo
33

Estás tocando múltiples problemas aquí:

1) Un seguimiento de la pila nunca debe ser visible para los usuarios finales (para la experiencia del usuario y para fines de seguridad)

Sí, debería ser accesible para diagnosticar problemas de los usuarios finales, pero el usuario final no debería verlos por dos razones:

  • Son muy oscuros e ilegibles, la aplicación se verá muy amigable para el usuario.
  • Mostrar un seguimiento de la pila al usuario final podría presentar un riesgo potencial de seguridad. Corríjame si me equivoco, PHP realmente imprime los parámetros de la función en el seguimiento de la pila, brillante, pero muy peligroso. Si obtiene una excepción al conectarse a la base de datos, ¿qué es probable que tenga en el seguimiento de la pila?

2) Generar un seguimiento de pila es un proceso relativamente costoso (aunque es poco probable que sea un problema en la mayoría de las circunstancias 'excepcionales')

La generación de un seguimiento de pila ocurre cuando se crea / lanza la excepción (es por eso que lanzar una excepción tiene un precio), la impresión no es tan costosa. De hecho, puede anular Throwable#fillInStackTrace()su excepción personalizada haciendo que lanzar una excepción sea casi tan barato como una simple declaración GOTO.

3) Muchos marcos de registro imprimirán el seguimiento de la pila por usted (el nuestro no y no, no podemos cambiarlo fácilmente)

Muy buen punto. El problema principal aquí es: si el marco registra la excepción por usted, no haga nada (¡pero asegúrese de que lo haga!) Si desea registrar la excepción usted mismo, use el marco de registro como Logback o Log4J , para no ponerlos en la consola sin procesar porque es muy difícil controlarlo.

Con el marco de registro, puede redirigir fácilmente los rastros de la pila a un archivo, consola o incluso enviarlos a una dirección de correo electrónico específica. Con hardcoded printStackTrace()tienes que vivir con el sysout.

4) Imprimir el seguimiento de la pila no constituye un manejo de errores. Debe combinarse con otro registro de información y manejo de excepciones.

Nuevamente: inicie sesión SQLExceptioncorrectamente (con el seguimiento completo de la pila, utilizando el marco de registro) y muestre un mensaje agradable: " Lo sentimos, actualmente no podemos procesar su solicitud ". ¿Realmente crees que el usuario está interesado en los motivos? ¿Has visto la pantalla de error StackOverflow? Es muy gracioso, pero no revela ningún detalle. Sin embargo, asegura al usuario que el problema será investigado.

Pero él lo llamará de inmediato y usted debe poder diagnosticar el problema. Por lo tanto, necesita ambos: registro de excepciones adecuado y mensajes fáciles de usar.


Para concluir: siempre registre excepciones (preferiblemente utilizando el marco de registro ), pero no las exponga al usuario final. Piense detenidamente y sobre los mensajes de error en su GUI, muestre los rastros de la pila solo en modo de desarrollo.

Tomasz Nurkiewicz
fuente
Tu respuesta es la que obtuve.
Blasanka
"la mayoría de las circunstancias" excepcionales ". bonito.
awwsmm
19

Lo primero que printStackTrace () no es costoso como dice , porque el seguimiento de la pila se llena cuando se crea la excepción.

La idea es pasar cualquier cosa que vaya a los registros a través de un marco de registro, de modo que el registro se pueda controlar. Por lo tanto, en lugar de usar printStackTrace, simplemente use algo comoLogger.log(msg, exception);

Suraj Chandran
fuente
11

Imprimir el seguimiento de la pila de la excepción en sí mismo no constituye una mala práctica, pero solo imprimir el seguimiento de la ubicación cuando ocurre una excepción es probablemente el problema aquí; a menudo, simplemente imprimir un seguimiento de la pila no es suficiente.

Además, hay una tendencia a sospechar que no se está realizando un manejo de excepciones adecuado si todo lo que se realiza en un catchbloque es a e.printStackTrace. Un manejo incorrecto podría significar que, en el mejor de los casos, se ignora un problema y, en el peor, un programa que continúa ejecutándose en un estado indefinido o inesperado.

Ejemplo

Consideremos el siguiente ejemplo:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Aquí, queremos hacer un procesamiento de inicialización antes de continuar con algún procesamiento que requiera que la inicialización haya tenido lugar.

En el código anterior, la excepción debería haberse detectado y manejado adecuadamente para evitar que el programa continúe con el continueProcessingAssumingThatTheStateIsCorrectmétodo que podríamos suponer que causaría problemas.

En muchos casos, e.printStackTrace()es una indicación de que se está tragando alguna excepción y se permite que el procesamiento continúe como si no ocurriera ningún problema.

¿Por qué esto se ha convertido en un problema?

Probablemente, una de las principales razones por las que el manejo de excepciones deficiente se ha vuelto más frecuente se debe a cómo IDE como Eclipse generará automáticamente el código que realizará un e.printStackTracepara el manejo de excepciones:

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

(Lo anterior es un try-catchautogenerado real por Eclipse para manejar un InterruptedExceptionlanzamiento Thread.sleep).

Para la mayoría de las aplicaciones, simplemente imprimir el seguimiento de la pila al error estándar probablemente no sea suficiente. El manejo inadecuado de excepciones podría en muchos casos llevar a que una aplicación se ejecute en un estado inesperado y podría conducir a un comportamiento inesperado e indefinido.

Coobird
fuente
1
Esta. Muy a menudo, simplemente no detectar la excepción en absoluto, al menos a nivel de método, es mejor que capturar, imprimir la traza de la pila y continuar como si no ocurriera ningún problema.
EIS
10

Creo que su lista de razones es bastante completa.

Un ejemplo particularmente malo que he encontrado más de una vez es el siguiente:

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

El problema con el código anterior es que el manejo consiste completamente en la printStackTracellamada: la excepción realmente no se maneja correctamente ni se le permite escapar.

Por otro lado, como regla, siempre registro el seguimiento de la pila cada vez que hay una excepción inesperada en mi código. Con los años, esta política me ha ahorrado mucho tiempo de depuración.

Finalmente, en una nota más ligera, la Excepción Perfecta de Dios .

NPE
fuente
"no se permite escapar a la excepción", ¿quiso decir que la excepción se propaga por la pila? ¿En qué se diferencia el registro de printStackTrace ()?
MasterJoe
5

printStackTrace()imprime en una consola. En entornos de producción, nadie está mirando eso. Suraj es correcto, debe pasar esta información a un registrador.

Oh Chin Boon
fuente
Punto válido, aunque observamos cuidadosamente la salida de nuestra consola de producción.
Chris Knight
¿Puedes explicar a qué te refieres con mirar cuidadosamente? Como y quien A que frecuencia
Oh Chin Boon el
Al observar cuidadosamente, debería haber dicho cuándo lo requiere. Es un archivo de registro continuo que es uno de varios puertos de llamada si algo sale mal con nuestra aplicación.
Chris Knight
3

En aplicaciones de servidor, el stacktrace explota su archivo stdout / stderr. Puede hacerse cada vez más grande y está lleno de datos inútiles porque generalmente no tiene contexto ni marca de tiempo, etc.

p. ej. catalina.out cuando se usa tomcat como contenedor

Hajo Thelen
fuente
Buen punto. El uso abusivo de printStackTrace () podría explotar nuestros archivos de registro, o por lo menos llenarlo con resmas de información inútil
Chris Knight
@ChrisKnight: ¿podría sugerir un artículo que explique cómo los archivos de registro podrían explotar con información inútil? Gracias.
MasterJoe
3

No es una mala práctica porque hay algo "incorrecto" en PrintStackTrace (), sino porque es "olor a código". La mayoría de las veces la llamada PrintStackTrace () está ahí porque alguien no pudo manejar adecuadamente la excepción. Una vez que maneja la excepción de manera adecuada, generalmente ya no le importa StackTrace.

Además, mostrar el stacktrace en stderr generalmente solo es útil cuando se depura, no en producción porque muy a menudo stderr no va a ninguna parte. Iniciar sesión tiene más sentido. Pero solo reemplazando PrintStackTrace () con el registro, la excepción aún te deja con una aplicación que falló pero sigue funcionando como si nada hubiera pasado.

AVee
fuente
0

Como algunos tipos ya mencionaron aquí, el problema es con la excepción de tragarse en caso de que simplemente llame e.printStackTrace()al catchbloque. No detendrá la ejecución del hilo y continuará después del bloque try como en condiciones normales.

En lugar de eso, debe intentar recuperarse de la excepción (en caso de que sea recuperable), o lanzar RuntimeException, o hacer burbujear la excepción a la persona que llama para evitar bloqueos silenciosos (por ejemplo, debido a una configuración incorrecta del registrador).

chicles
fuente
0

Para evitar el problema de flujo de salida enredado referido por @Vineet Reynolds

puedes imprimirlo en el stdout: e.printStackTrace(System.out);

Traeyee
fuente