it-swarm.dev

Comment écrire un bon message d'exception

Je fais actuellement une révision du code et l'une des choses que je remarque est le nombre d'exceptions où le message d'exception semble simplement réitérer l'exception s'est produite. par exemple.

throw new Exception("BulletListControl: CreateChildControls failed.");

Je peux déterminer les trois éléments de ce message à partir du reste de l'exception. Je connais la classe et la méthode de la trace de la pile et je sais qu'elle a échoué (car j'ai une exception).

Cela m'a fait penser au message que je mettais dans les messages d'exception. Je crée d'abord une classe d'exception, si elle n'existe pas déjà, pour la raison générale (par exemple PropertyNotFoundException - le pourquoi), puis quand je la jette, le message indique ce qui s'est mal passé (par exemple "Impossible de trouver la propriété 'IDontExist' sur Node 1234" - le quoi). L'où est dans le StackTrace. Le - quand peut se retrouver dans le journal (le cas échéant). Le comment est pour le développeur de travailler (et de corriger)

Avez-vous d'autres conseils pour lever des exceptions? Plus précisément en ce qui concerne la création de nouveaux types et le message d'exception.

102
Colin Mackay

Je vais orienter ma réponse davantage sur ce qui vient après une exception: à quoi ça sert et comment les logiciels devraient-ils se comporter, que devraient faire vos utilisateurs avec l'exception? Une excellente technique que j'ai rencontrée au début de ma carrière était de toujours signaler les problèmes et les erreurs en 3 parties: contexte, problème et solution. L'utilisation de cette dicipline modifie énormément la gestion des erreurs et améliore considérablement l'utilisation du logiciel par les opérateurs.

Voici quelques exemples.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

Dans ce cas, l'opérateur sait exactement quoi faire et à quel fichier doit être affecté. Ils savent également que les modifications du pool de connexions n'ont pas pris et doivent être répétées.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

J'écris des systèmes côté serveur et mes opérateurs sont généralement un support technique de première ligne. J'écrirais les messages différemment pour les logiciels de bureau qui ont un public différent mais qui incluent les mêmes informations.

Plusieurs choses merveilleuses se produisent si l'on utilise cette technique. Le développeur de logiciels est souvent le mieux placé pour savoir comment résoudre les problèmes dans son propre code, donc l'encodage des solutions de cette manière lorsque vous écrivez le code est extrêmement avantageux pour les utilisateurs finaux qui sont désavantagés à trouver des solutions car ils manquent souvent d'informations sur ce que faisait exactement le logiciel. Quiconque a déjà lu un message d'erreur Oracle saura ce que je veux dire.

La deuxième chose merveilleuse qui me vient à l'esprit est lorsque vous vous retrouvez à essayer de décrire une solution dans votre exception et que vous écrivez "Cochez X et si A, puis B sinon C". C'est un signe très clair et évident que votre exception est vérifiée au mauvais endroit. Vous, le programmeur, avez la capacité de comparer les choses dans le code, donc "si" les instructions doivent être exécutées dans le code, pourquoi impliquer l'utilisateur dans quelque chose qui peut être automatisé? Il y a des chances que cela vienne du plus profond du code et que quelqu'un ait fait le paresseux et levé IOException à partir d'un certain nombre de méthodes et détecté des erreurs potentielles de toutes dans un bloc de code appelant qui ne peut pas décrire correctement ce qui s'est mal passé, ce que le le contexte spécifique est et comment y remédier. Cela vous encourage à écrire des erreurs de grain plus fines, à les attraper et à les gérer au bon endroit dans votre code afin que vous puissiez articuler correctement les étapes que l'opérateur devrait prendre.

Dans une entreprise, nous avions des opérateurs de premier ordre qui connaissaient très bien le logiciel et gardaient leur propre "livre de course", ce qui augmentait notre rapport d'erreurs et proposait des solutions. Pour reconnaître cela, le logiciel a commencé à inclure des liens wiki vers le livre d'exécution dans les exceptions afin qu'une explication de base soit disponible ainsi que des liens vers des discussions et des observations plus avancées par les opérateurs au fil du temps.

Si vous avez eu la possibilité d'essayer cette technique, il devient beaucoup plus évident de nommer vos exceptions dans le code lors de la création de la vôtre. NonRecoverableConfigurationReadFailedException devient un peu un raccourci pour ce que vous êtes sur le point de décrire plus en détail à l'opérateur. J'aime être verbeux et je pense que ce sera plus facile à interpréter pour le prochain développeur qui touche mon code.

72
Sir Wobin

Dans cette question plus récente j'ai fait remarquer que les exceptions ne devraient pas contenir de message du tout. À mon avis, le fait qu'ils le fassent est une énorme idée fausse. Ce que je propose, c'est que

Le "message" de l'exception est le nom de classe (entièrement qualifié) de l'exception.

Une exception doit contenir dans ses propres variables membres autant de détails que possible sur ce qui s'est exactement passé; par exemple, un IndexOutOfRangeException doit contenir la valeur d'index qui a été trouvée non valide, ainsi que les valeurs supérieures et inférieures qui étaient valides au moment où l'exception a été levée. De cette façon, en utilisant la réflexion, vous pouvez créer automatiquement un message qui se lit comme suit: IndexOutOfRangeException: index = -1; min=0; max=5 et ceci, avec la trace de la pile, devraient être toutes les informations objectives dont vous avez besoin pour résoudre le problème. Le mettre en forme dans un joli message comme "l'index -1 n'était pas compris entre 0 et 5" n'ajoute aucune valeur.

Dans votre exemple particulier, la classe NodePropertyNotFoundException contiendrait le nom de la propriété qui n'a pas été trouvée et une référence au nœud qui ne contenait pas la propriété. Ceci est important: il ne doit pas contenir le nom du nœud; il doit contenir une référence au nœud réel. Dans votre cas particulier, cela peut ne pas être nécessaire, mais c'est une question de principe et une façon de penser préférée: la principale préoccupation lors de la construction d'une exception est qu'elle doit être utilisable par du code qui pourrait l'attraper. L'utilisabilité par l'homme est une préoccupation importante mais secondaire.

Cela prend en charge la situation très frustrante dont vous avez pu être témoin à un moment donné de votre carrière, où vous avez peut-être intercepté une exception contenant des informations vitales sur ce qui s'est passé dans le texte du message, mais pas dans ses variables membres, donc vous a dû faire une analyse de chaîne du texte afin de comprendre ce qui s'est passé, en espérant que le texte du message reste le même dans les futures versions de la couche sous-jacente, et en priant pour que le texte du message ne soit pas dans une langue étrangère lorsque votre programme est exécuter dans d'autres pays.

Bien sûr, puisque le nom de classe de l'exception est le message de l'exception (et les variables membres de l'exception sont les détails spécifiques), cela signifie que vous avez besoin de beaucoup, de nombreuses exceptions différentes pour transmettre tous les différents messages, et c'est bon.

Maintenant, parfois, alors que nous écrivons du code, nous rencontrons une situation erronée pour laquelle nous voulons simplement coder rapidement une instruction throw et continuer à écrire notre code au lieu d'avoir à interrompre ce que nous faisons pour aller créer un nouveau classe d'exception afin que nous puissions le jeter là. Pour ces cas, j'ai une classe GenericException qui accepte en fait un message de chaîne en tant que paramètre de construction, mais le constructeur de cette classe d'exception est orné d'un grand gros énorme violet vif FIXME XXX TODO commentaire indiquant que chaque instanciation unique de cette classe doit être remplacée par une instanciation d'une classe d'exception plus spécialisée avant la sortie du système logiciel, de préférence avant que le code ne soit validé.

23
Mike Nakis

En règle générale, ne exception devrait aider les développeurs à identifier la cause en donnant des informations utiles (valeurs attendues, valeur réelle, causes/solution possibles, etc.).

De nouveaux types d'exceptions doivent être créés quand aucun des types intégrés n'a de sens. Un type spécifique permet à d'autres développeurs d'attraper une exception spécifique et de la gérer. Si le développeur sait comment gérer votre exception mais que le type est Exception, il ne pourra pas le gérer correctement.

13
mbillard

Dans .NET, ne faites jamais throw new Exception("...") (comme l'a montré l'auteur de la question). L'exception est le type d'exception racine et n'est pas censée être lancée directement. Au lieu de cela, lancez l'un des types d'exceptions .NET dérivées ou créez votre propre exception personnalisée qui dérive d'Exception (ou d'un autre type d'exception).

Pourquoi ne pas lancer Exception? Parce que lancer Exception ne fait rien pour décrire votre exception et force votre code appelant à écrire du code comme catch(Exception ex) { ... } ce qui n'est généralement pas une bonne chose! :-).

4
bytedev

Les éléments que vous souhaitez rechercher pour "ajouter" à l'exception sont les éléments de données qui ne sont pas inhérents à l'exception ou à la trace de pile. Que ceux-ci fassent partie du "message" ou doivent être joints lors de la connexion est une question intéressante.

Comme vous l'avez déjà noté, l'exception vous dit de manière proactive quoi, la trace de pile vous indique probablement où, mais le "pourquoi" peut être plus impliqué (devrait être, on pourrait l'espérer) que d'aller simplement regarder une ligne ou deux et dire " doh! Bien sûr ". Cela est encore plus vrai lors de la journalisation des erreurs dans le code de production - j'ai trop souvent été mordu par de mauvaises données qui ont trouvé leur chemin dans un système en direct qui n'existe pas dans nos systèmes de test. Quelque chose d'aussi simple que de savoir quel est l'ID de l'enregistrement dans la base de données qui cause (ou contribue) à l'erreur peut gagner beaucoup de temps.

Donc ... Soit répertorié ou, pour .NET, ajouté à la collecte de données des exceptions journalisées (c.f. @Plip!):

  • Paramètres (cela peut devenir un peu intéressant - vous ne pouvez pas ajouter à la collecte de données si elle ne se sérialise pas et parfois un seul paramètre peut être étonnamment complexe)
  • Les données supplémentaires retournées par ADO.NET ou Linq à SQL ou similaire (cela peut aussi devenir un peu intéressant!).
  • Quoi que ce soit d'autre pourrait ne pas être apparent.

Certaines choses, bien sûr, vous ne saurez pas ce dont vous avez besoin tant que vous ne les avez pas dans votre rapport/journal d'erreurs initial. Certaines choses que vous ne réalisez pas que vous pouvez obtenir jusqu'à ce que vous trouviez que vous en avez besoin.

2
Murph

Quelles sont les exceptions pour?

(1) Dire à l'utilisateur que quelque chose s'est mal passé?

Cela devrait être un dernier recours, car votre code devrait intercéder et leur montrer quelque chose de "plus agréable" qu'une exception.

Le message "erreur" doit indiquer clairement et succinctement quoi s'est mal passé et ce que, le cas échéant, l'utilisateur peut faire pour récupérer à partir de la condition d'erreur.

par exemple. "Veuillez ne pas appuyer à nouveau sur ce bouton"

(2) Dire à un développeur qu'il a mal tourné?

C'est le genre de chose que vous connectez à un fichier pour une analyse ultérieure.
Le Stack Trace indiquera au développeur le code s'est cassé; le message devrait, encore une fois, indiquer quoi s'est mal passé.

(3) Dire à un gestionnaire d'exceptions (c'est-à-dire un code) que quelque chose s'est mal passé?

Le Type de l'Exception décidera quel gestionnaire d'exceptions pourra le consulter et les propriétés définies sur l'objet Exception permettront au gestionnaire de les traiter.

Le message de l'exception est totalement hors de propos.

0
Phill W.