web-dev-qa-db-fra.com

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

Il y a un lot of matériau out / qui suggère qu'imprimer la trace d'une pile d'exception est une mauvaise pratique. 

Par exemple. de la vérification RegexpSingleline dans Checkstyle:

Cette vérification peut être utilisée [...] pour trouver une mauvaise pratique courante telle que l'appel ex.printStacktrace ()

Cependant, j'ai du mal à trouver où que ce soit, ce qui donne une raison valable à cela puisque la trace de pile est très utile pour localiser la cause de l'exception. Des choses que je connais:

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

  2. Générer une trace de pile est un processus relativement coûteux (bien que ce ne soit probablement pas un problème dans la plupart des circonstances «exceptionnelles»)

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

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

Quelles autres raisons existe-t-il pour éviter d’imprimer une trace de pile dans votre code?

114
Chris Knight

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

  • invocation System.setErr() qui modifie la destination indiqué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 tourner le journal, ce qui implique 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 ignore en réalité toutes les données écrites, comme dans le cas de /dev/null.

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

  • si System.err n'est pas réaffecté pendant toute la durée de vie de l'application,
  • et si vous n'avez pas besoin de la rotation du journal pendant l'exécution de l'application,
  • et si elle est acceptée/conçue, l'application enregistre 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 remplies. On ne sait peut-être pas qu’un autre code est exécuté sur la machine virtuelle, et il est impossible de 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 la machine" fonctionnalité préférable mais facultative dans un enregistreur) dans une destination connue, pour faciliter la prise en charge.

Enfin, il convient de rappeler que la sortie de Throwable.printStackTrace() serait définitivement entrelacée avec un autre contenu écrit dans System.err (et même éventuellement System.out si les deux sont redirigés vers le même fichier/périphérique). Il s’agit là d’un désagrément (pour les applications à un seul thread), car les données relatives aux exceptions ne sont pas facilement analysables dans un tel cas. Pire encore, il est fort probable qu'une application multithread produira des journaux très déroutants car Throwable.printStackTrace()n'est pas thread-safe.

Il n'y a pas de mécanisme de synchronisation pour synchroniser l'écriture de la trace de pile sur System.err lorsque plusieurs threads invoquent Throwable.printStackTrace() en même temps. Pour résoudre ce problème, votre code doit être synchronisé sur le moniteur associé à System.err (et aussi à System.out, si le fichier/périphérique de destination est identique), ce qui représente un lourd tribut à payer pour l'intégrité du fichier journal. Pour prendre un exemple, les classes ConsoleHandler et StreamHandler sont 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é à l'instance StreamHandler. 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 dans ces flux de manière sérialisable.

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


En prolongeant l'argument dans l'un des paragraphes précédents, il est également un mauvais choix d'utiliser Throwable.printStackTrace conjointement avec un enregistreur qui écrit dans la console. Cela est en partie dû au fait que l'enregistreur se synchronise sur un moniteur différent, alors que votre application le ferait (éventuellement, si vous ne voulez pas d'enregistrements de journal entrelacés) sur un autre moniteur. L'argument est également valable lorsque vous utilisez deux enregistreurs différents qui écrivent dans la même destination, dans votre application.

109
Vineet Reynolds

Vous touchez plusieurs problèmes ici:

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

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

  • Ils sont très obscurs et illisibles, l’application sera très peu conviviale.
  • Afficher une trace de pile à l'utilisateur final peut présenter un risque potentiel pour la sécurité. Corrigez-moi si je me trompe, PHP affiche en fait les paramètres de la fonction dans la trace de la pile - brillant, mais très dangereux - si vous obteniez une exception lors de la connexion à la base de données, qu'allez-vous probablement dans le stacktrace?

2) La génération d’une trace de pile est un processus relativement coûteux (bien que ce ne soit probablement pas 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 le lancement d’une exception a un prix), l’impression n’est pas si coûteuse. En fait, vous pouvez remplacer Throwable#fillInStackTrace() dans votre exception personnalisée, ce qui permet de lancer une exception presque aussi économique qu'une simple instruction GOTO.

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

Très bon point. Le problème principal ici est le suivant: si la structure 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 une infrastructure de journalisation telle que Logback ou Log4J , de ne pas les mettre sur la console brute car il est très difficile de la contrôler.

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

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

Encore une fois: enregistrez correctement SQLException (avec la trace de pile complète, en utilisant le cadre de journalisation) et affichez Nice: "Désolé, nous ne pouvons actuellement pas 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 pas aucun détails. Cependant, cela garantit à l'utilisateur que le problème sera examiné.

Mais il va vous appeler immédiatement et vous devez pouvoir diagnostiquer le problème. Vous avez donc besoin des deux: une journalisation correcte des exceptions et des messages conviviaux.


Pour résumer: always enregistrez les exceptions (de préférence en utilisant logging framework ), mais ne les exposez pas à l'utilisateur final. Réfléchissez bien aux messages d'erreur dans votre interface graphique et affichez les traces de pile uniquement en mode développement.

31
Tomasz Nurkiewicz

La première chose printStackTrace () n’est pas chère, comme vous le dites, car le suivi de la pile est rempli lorsque l’exception est créée elle-même.

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

17
Suraj Chandran

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

En outre, on a tendance à penser que le traitement correct des exceptions n’est pas effectué si tout ce qui est exécuté dans un bloc catch est un e.printStackTrace. Une manipulation incorrecte peut signifier au mieux qu'un problème est ignoré, et au pire un programme qui continue à s'exécuter dans un état non défini ou inattendu.

Exemple

Considérons l'exemple suivant:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

Ici, nous souhaitons effectuer un traitement d'initialisation avant de poursuivre un traitement nécessitant l'initialisation.

Dans le code ci-dessus, l'exception aurait dû être interceptée et gérée correctement pour empêcher le programme de passer à la méthode continueProcessingAssumingThatTheStateIsCorrect, ce qui pourrait entraîner des problèmes.

Dans de nombreux cas, e.printStackTrace() 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 la gestion des exceptions médiocre est devenue plus répandue est liée à la manière dont des environnements de développement intégrés tels qu'Eclipse génèrent automatiquement du code qui exécutera un e.printStackTrace pour la gestion des exceptions:

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

(Ce qui précède est un try-catch réel généré automatiquement par Eclipse pour gérer une InterruptedException levée par Thread.sleep.)

Pour la plupart des applications, imprimer la trace de la pile en erreur standard ne sera probablement pas suffisant. Une gestion incorrecte des exceptions peut dans de nombreux cas conduire une application à un état inattendu et conduire à un comportement inattendu et indéfini.

10
coobird

Je pense que votre liste de raisons est assez exhaustive.

Voici un exemple particulièrement mauvais que j'ai rencontré plus d'une fois:

    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 en entièrement de l'appel printStackTrace: l'exception n'est pas vraiment gérée correctement ni autorisée à s'échapper.

D'un autre côté, en règle générale, j'enregistre 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 permis de gagner beaucoup de temps pour le débogage.

Enfin, sur une note plus légère, L’exception parfaite de Dieu .

9
NPE

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. 

4
Oh Chin Boon

Dans les applications serveur, le stacktrace explose votre fichier stdout/stderr. Il peut devenir de plus en plus grand et est rempli de données inutiles, car vous n’avez généralement ni contexte, ni horodatage, etc.

par exemple. catalina.out en utilisant Tomcat comme conteneur

3
Hajo Thelen

Ce n'est pas une mauvaise pratique parce que quelque chose ne va pas avec 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 de manière appropriée, vous ne vous souciez généralement plus de StackTrace. 

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

3
AVee

Comme certains gars ont déjà mentionné ici, le problème est avec l'exception d'ingestion au cas où vous appelez simplement e.printStackTrace() dans le bloc catch. 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 essayer de récupérer l’exception (au cas où elle soit récupérable), ou de lancer RuntimeException, ou de masquer l’exception à l’appelant afin d’éviter les plantages silencieux (par exemple, en raison d’une configuration incorrecte du journal).

0
gumkins