Pourquoi exception.printStackTrace () est-il considéré comme une mauvaise pratique?

126

Il y a beaucoup de documents sur il ce qui suggère que l' impression de la trace de la pile d'une exception est une mauvaise pratique.

Par exemple, à partir de l'enregistrement RegexpSingleline dans Checkstyle:

Cette vérification peut être utilisée [...] pour trouver des mauvaises pratiques courantes telles que l'appel de ex.printStacktrace ()

Cependant, j'ai du mal à trouver n'importe où qui donne une raison valable, car la trace de la pile est sûrement très utile pour localiser la cause de l'exception. Choses dont je suis conscient:

  1. Une trace de pile ne doit jamais être visible pour les utilisateurs finaux (pour l'expérience utilisateur et à des fins de sécurité)

  2. La génération d'une trace de pile est un processus relativement coûteux (bien que peu susceptible de poser un problème dans la plupart des circonstances `` exceptionnelles '')

  3. De nombreux frameworks de journalisation imprimeront la trace de la pile pour vous (le nôtre ne le fait pas et non, nous ne pouvons pas le changer facilement)

  4. L'impression de la trace de pile ne constitue pas une gestion des erreurs. Il doit être combiné avec d'autres informations d'enregistrement et de gestion des exceptions.

Quelles sont les autres raisons pour éviter d'imprimer une trace de pile dans votre code?

Chris Knight
la source
12
En tant que personne qui doit dépanner régulièrement, je n'oublierais jamais d' imprimer une trace de pile en cas de problème. Oui, ne le montrez pas à l'utilisateur, mais oui, sauvegardez-le dans un journal des erreurs.
Paul Grime
4
Avez-vous vraiment besoin d'autres raisons? Je pense que vous avez assez bien répondu à votre propre question. Cependant, stacktrace doit être imprimé dans des exceptions exceptionnelles. :)
Neil
Error Prone va maintenant vous avertir de l'utilisation.printStackTrace() dans votre code :)
dimo414

Réponses:

125

Throwable.printStackTrace()écrit la trace de la pile dans System.errPrintStream. Le System.errflux et le flux de sortie standard "erreur" sous-jacent du processus JVM peuvent être redirigés par

  • appelant System.setErr()qui modifie la destination pointée par System.err.
  • ou en redirigeant le flux de sortie d'erreur du processus. Le flux de sortie d'erreur peut être redirigé vers un fichier / périphérique
    • dont le contenu peut être ignoré par le personnel,
    • le fichier / périphérique peut ne pas être capable de rotation du journal, en déduisant qu'un redémarrage du processus est nécessaire pour fermer le descripteur de fichier / périphérique ouvert, avant d'archiver le contenu existant du fichier / périphérique.
    • ou le fichier / périphérique supprime toutes les données qui y sont écrites, comme c'est le cas de /dev/null.

En déduisant de ce qui précède, l'invocation Throwable.printStackTrace()constitue un comportement de gestion des exceptions valide (pas bon / excellent), uniquement

  • si vous n'avez pas System.errété réaffecté pendant toute la durée de vie de l'application,
  • et si vous n'avez pas besoin de rotation du journal pendant l'exécution de l'application,
  • et si la pratique de journalisation acceptée / conçue de l'application consiste à écrire dans System.err(et dans le flux de sortie d'erreur standard de la JVM).

Dans la plupart des cas, les conditions ci-dessus ne sont pas satisfaites. Il se peut que l'on ne sache pas que d'autres codes s'exécutent dans la JVM, et que l'on ne peut pas prédire la taille du fichier journal ou la durée d'exécution du processus, et une pratique de journalisation bien conçue tournerait autour de l'écriture de fichiers journaux "analysables par machine" (un caractéristique préférable mais facultative dans un enregistreur) dans une destination connue, pour aider au soutien.

Enfin, il ne faut pas oublier que la sortie de Throwable.printStackTrace()serait certainement entrelacée avec un autre contenu écrit System.err(et peut-être même System.outsi les deux sont redirigés vers le même fichier / périphérique). C'est un désagrément (pour les applications à un seul thread) qu'il faut gérer, car les données autour des exceptions ne sont pas facilement analysables dans un tel événement. Pire encore, il est fort probable qu'une application multithread produise des journaux très déroutants car Throwable.printStackTrace() non thread-safe .

Il n'existe pas de mécanisme de synchronisation pour synchroniser l'écriture de la trace de pile System.errlorsque plusieurs threads sont appelés Throwable.printStackTrace()en même temps. Résoudre ce problème nécessite en fait que votre code se synchronise sur le moniteur associé à System.err(et aussi System.out, si le fichier / périphérique de destination est le même), et c'est un prix assez élevé à payer pour le bon fonctionnement du fichier journal. Pour prendre un exemple, les classes ConsoleHandleret StreamHandlersont responsables de l'ajout des enregistrements de journal à la console, dans la fonction de journalisation fournie par java.util.logging; l'opération réelle de publication des enregistrements de journal est synchronisée - chaque thread qui tente de publier un enregistrement de journal doit également acquérir le verrou sur le moniteur associé auStreamHandlerexemple. Si vous souhaitez avoir la même garantie d'avoir des enregistrements de journal non entrelacés à l'aide de System.out/ System.err, vous devez vous assurer de la même chose - les messages sont publiés sur ces flux de manière sérialisable.

Compte tenu de tout ce qui précède et des scénarios très restreints dans lesquels Throwable.printStackTrace()est réellement utile, il s'avère souvent que l'invoquer est une mauvaise pratique.


En étendant l'argument dans l'un des paragraphes précédents, c'est également un mauvais choix à utiliser Throwable.printStackTraceen conjonction avec un enregistreur qui écrit sur la console. Ceci est en partie dû à la raison pour laquelle l'enregistreur se synchroniserait sur un moniteur différent, tandis que votre application se synchroniserait (éventuellement, si vous ne voulez pas d'enregistrements de journal entrelacés) sur un moniteur différent. L'argument est également valable lorsque vous utilisez deux enregistreurs différents qui écrivent vers la même destination, dans votre application.

Vineet Reynolds
la source
Excellente réponse, merci. Cependant, bien que je convienne que la plupart du temps, c'est du bruit ou pas nécessaire, il y a des moments où c'est absolument critique.
Chris Knight
1
@Chris Oui, il y a des moments où vous ne pouvez pas ne pas utiliser System.out.printlnet Throwable.printStackTraceet bien sûr, le jugement du développeur est nécessaire. J'étais un peu inquiet que la partie sur la sécurité des threads n'ait pas été omise. Si vous regardez la plupart des implémentations de logger, vous remarquerez qu'elles synchronisent la partie où les enregistrements de journal sont écrits (même sur la console), bien qu'elles n'acquièrent pas de moniteurs sur System.errou System.out.
Vineet Reynolds
2
Aurais-je tort de noter qu'aucune de ces raisons ne s'applique aux substitutions printStackTrace qui prennent un objet PrintStream ou PrintWriter comme paramètre?
ESRogs
1
@Geek, ce dernier est le descripteur de fichier de processus avec la valeur 2. Le premier n'est qu'une abstraction.
Vineet Reynolds
1
Dans le code source JDK de printStackTrace (), il utilise synchronized pour verrouiller le System.err PrintStream, il doit donc s'agir d'une méthode thread-safe.
EyouGo
33

Vous abordez plusieurs problèmes ici:

1) Une trace de pile ne doit jamais être visible pour les utilisateurs finaux (pour l'expérience utilisateur et à des fins de sécurité)

Oui, il doit être accessible pour diagnostiquer les problèmes des utilisateurs finaux, mais l'utilisateur final ne doit pas les voir pour deux raisons:

  • Ils sont très obscurs et illisibles, l'application aura l'air très peu conviviale.
  • L'affichage d'une trace de pile à l'utilisateur final peut présenter un risque de sécurité potentiel. Corrigez-moi si je me trompe, PHP imprime en fait les paramètres de fonction dans la trace de pile - brillant, mais très dangereux - si vous obteniez une exception lors de la connexion à la base de données, qu'êtes-vous susceptible de faire dans le stacktrace?

2) La génération d'une trace de pile est un processus relativement coûteux (bien que peu susceptible de poser un problème dans la plupart des circonstances `` exceptionnelles '')

La génération d'une trace de pile se produit lorsque l'exception est créée / levée (c'est pourquoi la levée d'une exception a un prix), l'impression n'est pas si chère. En fait, vous pouvez remplacer Throwable#fillInStackTrace()votre exception personnalisée en faisant de la levée une exception presque aussi bon marché qu'une simple instruction GOTO.

3) De nombreux frameworks de journalisation imprimeront la trace de la pile pour vous (le nôtre ne le fait pas et non, nous ne pouvons pas le changer facilement)

Très bon point. Le principal problème ici est le suivant: si le framework enregistre l'exception pour vous, ne faites rien (mais assurez-vous que c'est le cas!) Si vous souhaitez enregistrer l'exception vous-même, utilisez un framework de journalisation comme Logback ou Log4J , pour ne pas les mettre sur la console brute car il est très difficile de le contrôler.

Avec le framework de journalisation, vous pouvez facilement rediriger les traces de pile vers un fichier, une console ou même les envoyer à une adresse e-mail spécifiée. Avec le code en dur, printStackTrace()vous devez vivre avec le sysout.

4) L'impression de la trace de pile ne constitue pas une gestion des erreurs. Il doit être combiné avec d'autres informations d'enregistrement et de gestion des exceptions.

Encore une fois: connectez-vous SQLExceptioncorrectement (avec la trace complète de la pile, en utilisant le cadre de journalisation) et affichez gentil: " Désolé, nous ne sommes actuellement pas en mesure de traiter votre demande ". Pensez-vous vraiment que l'utilisateur est intéressé par les raisons? Avez-vous vu l'écran d'erreur StackOverflow? C'est très humoristique, mais ne révèle aucun détail. Cependant, il garantit à l'utilisateur que le problème sera étudié.

Mais il va vous appeler immédiatement et vous devez être en mesure de diagnostiquer le problème. Vous avez donc besoin des deux: une journalisation des exceptions appropriée et des messages conviviaux.


Pour conclure les choses: consignez toujours les exceptions (de préférence en utilisant le cadre de journalisation ), mais ne les exposez pas à l'utilisateur final. Réfléchissez bien et aux messages d'erreur dans votre GUI, affichez les traces de pile uniquement en mode développement.

Tomasz Nurkiewicz
la source
ta réponse est celle que j'ai obtenue.
Blasanka
«la plupart des circonstances« exceptionnelles »». agréable.
awwsmm
19

Premièrement, printStackTrace () n'est pas cher comme vous le dites, car la trace de la pile est remplie lorsque l'exception est créée elle-même.

L'idée est de transmettre tout ce qui va aux journaux via un framework de journalisation, de sorte que la journalisation puisse être contrôlée. Par conséquent, au lieu d'utiliser printStackTrace, utilisez simplement quelque chose commeLogger.log(msg, exception);

Suraj Chandran
la source
11

Imprimer la trace de pile de l'exception en soi ne constitue pas une mauvaise pratique, mais imprimer uniquement la trace de stace lorsqu'une exception se produit est probablement le problème ici - souvent, il ne suffit pas d'imprimer une trace de pile.

De plus, il y a une tendance à soupçonner qu'une gestion correcte des exceptions n'est pas effectuée si tout ce qui est effectué dans un catchbloc est un fichier e.printStackTrace. Une mauvaise gestion peut signifier au mieux qu'un problème est ignoré, et au pire un programme qui continue de s'exécuter dans un état indéfini ou inattendu.

Exemple

Prenons l'exemple suivant:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Ici, nous voulons effectuer un traitement d'initialisation avant de passer à un traitement qui nécessite que l'initialisation ait eu lieu.

Dans le code ci-dessus, l'exception aurait dû être interceptée et correctement gérée pour empêcher le programme de passer à la continueProcessingAssumingThatTheStateIsCorrectméthode que nous pourrions supposer causer des problèmes.

Dans de nombreux cas, e.printStackTrace()cela indique qu'une exception est en train d'être avalée et que le traitement est autorisé à se dérouler comme si aucun problème ne s'était produit.

Pourquoi est-ce devenu un problème?

L'une des principales raisons pour lesquelles une mauvaise gestion des exceptions est devenue plus répandue est probablement la façon dont les IDE tels qu'Eclipse généreront automatiquement du code qui effectuera une e.printStackTracegestion des exceptions:

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

(Ce qui précède est un réel try-catchauto-généré par Eclipse pour gérer un InterruptedExceptionlancé par Thread.sleep.)

Pour la plupart des applications, il ne suffira probablement pas d'imprimer la trace de la pile en erreur standard. Une gestion incorrecte des exceptions peut, dans de nombreux cas, conduire une application à s'exécuter dans un état inattendu et entraîner un comportement inattendu et indéfini.

coobird
la source
1
Ce. Il est souvent préférable de ne pas attraper l'exception du tout, du moins au niveau de la méthode, que de capturer, d'imprimer la trace de la pile et de continuer comme si aucun problème ne s'était produit.
eis
10

Je pense que votre liste de raisons est assez complète.

Un exemple particulièrement mauvais que j'ai rencontré plus d'une fois est le suivant:

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

Le problème avec le code ci-dessus est que la gestion consiste entièrement en l' printStackTraceappel: l'exception n'est pas vraiment gérée correctement et n'est pas autorisée à s'échapper.

D'un autre côté, en règle générale, je consigne toujours la trace de la pile chaque fois qu'il y a une exception inattendue dans mon code. Au fil des ans, cette politique m'a fait gagner beaucoup de temps lors du débogage.

Enfin, sur une note plus légère, la Perfect Exception de Dieu .

NPE
la source
"exception ne peut pas s'échapper" - vouliez-vous dire que l'exception est propagée dans la pile? en quoi la journalisation est-elle différente de printStackTrace ()?
MasterJoe
5

printStackTrace()imprime sur une console. Dans les paramètres de production, personne ne regarde jamais cela. Suraj est correct, devrait transmettre cette information à un enregistreur.

Oh Chin Boon
la source
Point valide, bien que nous surveillions attentivement la sortie de notre console de production.
Chris Knight
Pouvez-vous expliquer ce que vous entendez par regarder attentivement? Comment et qui? A quelle fréquence?
Oh Chin Boon
En regardant attentivement, j'aurais dû dire quand cela est nécessaire. C'est un fichier journal roulant qui est l'un des nombreux ports d'escale en cas de problème avec notre application.
Chris Knight
3

Dans les applications serveur, le stacktrace fait exploser votre fichier stdout / stderr. Il peut devenir de plus en plus volumineux et rempli de données inutiles car généralement vous n'avez pas de contexte ni d'horodatage, etc.

par exemple catalina.out lors de l'utilisation de tomcat comme conteneur

Hajo Thelen
la source
Bon point. Une utilisation abusive de printStackTrace () pourrait faire exploser nos fichiers de journalisation, ou à tout le moins les remplir de rames d'informations inutiles
Chris Knight
@ChrisKnight - pourriez-vous s'il vous plaît suggérer un article qui explique comment les fichiers de journalisation pourraient exploser avec des informations inutiles? Merci.
MasterJoe
3

Ce n'est pas une mauvaise pratique car quelque chose ne va pas à propos de PrintStackTrace (), mais parce que c'est une "odeur de code". La plupart du temps, l'appel PrintStackTrace () est là parce que quelqu'un n'a pas réussi à gérer correctement l'exception. Une fois que vous avez traité l'exception d'une manière appropriée, vous ne vous souciez généralement plus de StackTrace.

De plus, l'affichage du stacktrace sur stderr n'est généralement utile que lors du débogage, pas en production car très souvent stderr ne mène nulle part. La journalisation a plus de sens. Mais le simple remplacement de PrintStackTrace () par la journalisation de l'exception vous laisse toujours avec une application qui a échoué mais continue de fonctionner comme si de rien n'était.

AVee
la source
0

Comme certains gars l'ont déjà mentionné ici, le problème est à l'exception de la déglutition au cas où vous appelez simplement e.printStackTrace()le catchbloc. Cela n'arrêtera pas l'exécution du thread et continuera après le bloc try comme dans des conditions normales.

Au lieu de cela, vous devez soit essayer de récupérer de l'exception (au cas où elle est récupérable), soit de lancer RuntimeException, soit de faire bulle l'exception à l'appelant afin d'éviter des plantages silencieux (par exemple, en raison d'une mauvaise configuration de l'enregistreur).

gommes
la source
0

Pour éviter le problème de flux de sortie enchevêtré référencé par @Vineet Reynolds

vous pouvez l'imprimer sur la sortie standard: e.printStackTrace(System.out);

Traeyee
la source