¿Por qué exception.printStackTrace () considera malas prácticas?

Hay un montón de material que sugiere que imprimir el rastro de la stack de una excepción es una mala práctica.

Por ejemplo, desde el cheque de RegexpSingleline en Checkstyle:

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

Sin embargo, estoy luchando por encontrarlo en cualquier lugar, lo que da una razón válida porque desde luego el seguimiento de la stack es muy útil para rastrear qué causó la excepción. Cosas de las que soy consciente:

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

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

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

  4. Imprimir el seguimiento de la stack no constituye un manejo de errores. Se debe combinar con otro registro de información y manejo de excepciones.

¿Qué otras razones existen para evitar imprimir un rastro de stack en su código?

Throwable.printStackTrace() escribe el seguimiento de stack en System.err PrintStream. La secuencia System.err y la secuencia de salida “error” estándar subyacente del proceso JVM pueden ser redirigidas por

  • invocando System.setErr() que cambia el destino al que apunta System.err .
  • o redirigiendo la secuencia de salida de error del proceso. La secuencia de salida de error se puede redirigir a un archivo / dispositivo
    • cuyo contenido puede ser ignorado por el personal,
    • Es posible que el archivo / dispositivo no sea capaz de girar el registro, infiriendo que se requiere un reinicio del proceso para cerrar el archivo abierto / identificador del dispositivo, antes de archivar el contenido existente del archivo / dispositivo.
    • o el archivo / dispositivo en realidad descarta todos los datos escritos en él, como es el caso de /dev/null .

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

  • si no tiene reasignado System.err durante toda la vida útil de la aplicación,
  • y si no requiere rotación de registro mientras la aplicación se está ejecutando,
  • y si la práctica de registro aceptado / diseñado 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 están satisfechas. Es posible que uno no conozca 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 proceso en tiempo de ejecución, y una práctica de registro bien diseñada giraría en torno a la escritura de archivos de registro “analizables por máquina” ( característica preferible pero opcional en un registrador) en un destino conocido, para ayudar en el soporte.

Finalmente, uno debe recordar que la salida de Throwable.printStackTrace() definitivamente se intercalará con otro contenido escrito en System.err (y posiblemente incluso System.out si ambos son redirigidos al mismo archivo / dispositivo). Esto 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 ese caso. Peor aún, es muy probable que una aplicación de subprocesos múltiples produzca registros muy confusos ya que Throwable.printStackTrace() no es seguro para subprocesos .

No hay un mecanismo de sincronización para sincronizar la escritura del seguimiento de la stack con System.err cuando varios hilos invocan a Throwable.printStackTrace() al mismo tiempo. Resolver esto en realidad 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 para pagar la cordura del archivo de registro. Para tomar un ejemplo, las clases ConsoleHandler y StreamHandler son responsables de StreamHandler registros a la consola, en la función de registro proporcionada por java.util.logging ; la operación real de publicar registros de registro está sincronizada: cada hilo que intente publicar un registro de registro también debe adquirir el locking en el monitor asociado con la instancia de StreamHandler . Si desea tener la misma garantía de tener registros de registro no intercalados usando System.out / System.err , debe asegurarse 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.printStackTrace junto con un registrador que escribe en la consola. Esto se debe en parte a la razón por la que el registrador se sincronizaría en un monitor diferente, mientras que su aplicación (posiblemente, si no desea registros de registro 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.

Estás tocando múltiples problemas aquí:

1) Un seguimiento de la stack nunca debe ser visible para los usuarios finales (para la experiencia del usuario y con 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 parecerá poco amigable para el usuario.
  • Mostrar un seguimiento de la stack al usuario final puede introducir un posible riesgo de seguridad. Corrígeme si estoy equivocado, PHP realmente imprime los parámetros de la función en el seguimiento de la stack – shiny, pero muy peligroso – si obtienes una excepción mientras te conectas a la base de datos, ¿qué es lo más probable en stacktrace?

2) Generar un seguimiento de stack 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 stack ocurre cuando se crea / lanza la excepción (es por eso que lanzar una excepción viene con un precio), la impresión no es tan cara. De hecho, puede anular Throwable#fillInStackTrace() en su excepción personalizada haciendo que lanzar una excepción sea tan barato como una simple statement GOTO.

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

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

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

4) Imprimir el seguimiento de la stack no constituye un manejo de errores. Se debe combinar con otro registro de información y manejo de excepciones.

De nuevo: registre SQLException correctamente (con el seguimiento completo de la stack, utilizando el marco de registro) y muestre el mensaje ” Lo sentimos, actualmente no podemos procesar su solicitud “. ¿De verdad crees que el usuario está interesado en las razones? ¿Has visto la pantalla de error de StackOverflow? Es muy divertido, pero no revela ningún detalle. Sin embargo, asegura al usuario que el problema será investigado.

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


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

Lo primero que printStackTrace () no es costoso, ya que el seguimiento de la stack se completa cuando se crea la excepción.

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

Imprimir el trazado de la stack de la excepción en sí mismo no constituye una mala práctica, pero la impresión de la traza de acertar cuando se produce una excepción es probablemente el problema aquí: muchas veces, simplemente imprimir un trazado de la stack 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 está realizando en un bloque catch es un e.printStackTrace . El manejo inadecuado podría significar que, en el mejor de los casos, se ignora un problema y, en el peor de los casos, un progtwig 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 algún proceso 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 capturado y gestionado correctamente para evitar que el progtwig continueProcessingAssumingThatTheStateIsCorrect método continueProcessingAssumingThatTheStateIsCorrect que podríamos suponer que causaría problemas.

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

¿Por qué esto se convirtió 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 la forma en que los IDEs, como Eclipse, generarán automáticamente código que realizará un e.printStackTrace para el manejo de excepciones:

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

(Lo anterior es un try-catch real generado automáticamente por Eclipse para manejar una InterruptedException lanzada por Thread.sleep ).

Para la mayoría de las aplicaciones, simplemente imprimir la traza de la stack a un error estándar probablemente no sea suficiente. El manejo de excepciones incorrecto podría, en muchos casos, conducir a una aplicación que se ejecuta en un estado inesperado y podría conducir a un comportamiento inesperado e indefinido.

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 de la llamada printStackTrace : la excepción no se maneja correctamente ni se permite su escape.

Por otro lado, como regla, siempre registro el seguimiento de la stack 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 .

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

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

por ejemplo catalina.out cuando se usa tomcat como contenedor

No es una mala práctica porque algo está “mal” en PrintStackTrace (), sino porque es “código de olor”. La mayoría de las veces, la llamada PrintStackTrace () está allí porque alguien no manejó adecuadamente la excepción. Una vez que lidie con la excepción de una manera adecuada, generalmente ya no le importa el StackTrace.

Además, mostrar stacktrace en stderr generalmente solo es útil cuando se depura, no en producción porque muy a menudo stderr no lleva a ninguna parte. Iniciar sesión tiene más sentido. Pero solo al reemplazar PrintStackTrace () por registrar la excepción aún se deja una aplicación que falló pero sigue ejecutándose como si nada hubiera pasado.

Como algunos chicos ya mencionaron aquí, el problema está en la excepción de tragar en caso de que simplemente llame a e.printStackTrace() en el bloque catch . 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 RuntimeException burbuja a la persona que llama para evitar lockings silenciosos (por ejemplo, debido a una configuración incorrecta del registrador).