Qui devrait lire Exception.Message le cas échéant?

27

Lors de la conception d'exceptions, dois-je écrire des messages qu'un utilisateur ou un développeur doit comprendre? Qui devrait réellement être le lecteur des messages d'exception?

Je trouve que les messages d'exception ne sont pas du tout utiles et j'ai toujours du mal à les écrire. Par convention, le type de l'exception devrait déjà nous dire pourquoi quelque chose n'a pas fonctionné et les propriétés personnalisées peuvent ajouter encore plus d'informations comme les noms de fichiers, les index, les clés, etc. alors pourquoi le répéter dans le message lui-même? Un message généré automatiquement pourrait également faire et tout ce qu'il devait contenir est le nom de l'exception avec une liste de propriétés supplémentaires. Ce serait exactement aussi utile qu'un texte manuscrit.

Ne serait-il pas préférable de ne pas écrire de messages du tout, mais d'avoir des rendus d'exception spéciaux qui s'occupent de créer des messages significatifs peut-être dans différentes langues plutôt que de les coder en dur dans le code?


On m'a demandé si l'une de ces questions répondait à ma question:

Je les ai lus tous les deux et je n'étais pas satisfait de leurs réponses. Ils parlent des utilisateurs en général et se concentrent sur le contenu du message lui-même plutôt que sur le destinataire et il s'avère qu'il peut y en avoir au moins deux: l'utilisateur final et le développeur. Je ne sais jamais à qui je dois parler lorsque j'écris des messages d'exception.

Je pense même que le célèbre message n'a aucune valeur réelle car il ne fait que répéter le nom du type d'exception dans des mots différents, alors pourquoi prendre la peine de les écrire? Je peux parfaitement les générer automatiquement.

Pour moi, le message d'exception manque de différenciation de ses lecteurs. Une exception parfaite devrait fournir au moins deux versions du message: une pour l'utilisateur final et une pour le développeur. L'appeler simplement un message est trop générique. Un message de développeur doit ensuite être écrit en anglais, mais le message de l'utilisateur final peut devoir être traduit dans d'autres langues. Il n'est pas possible de réaliser tout cela avec un seul message, donc une exception devrait fournir un identifiant au message de l'utilisateur final qui, comme je viens de le dire, pourrait être disponible dans différentes langues.

Lorsque je lis toutes les autres questions liées, j'ai l'impression qu'un message d'exception est en effet destiné à être lu par un utilisateur final et non un développeur ... un seul message, c'est comme avoir un gâteau et le manger aussi.

t3chb0t
la source
4
Il y a deux autres questions posées ici sur les programmeurs qui me viennent à l'esprit. Je ne sais pas s'ils sont en double ou non, mais je pense que vous devriez les lire et peut-être qu'ils répondront à certaines ou même à toutes vos préoccupations: comment écrire un bon message d'exception et pourquoi de nombreux messages d'exception ne contiennent pas de détails utiles ?
Thomas Owens
2
@ThomasOwens Je les ai lus tous les deux mais ils ne s'adressent pas au destinataire spécifique d'un message d'exception. Ils parlent des utilisateurs en général mais qui est-il? Est-ce l'utilisateur du logiciel qui a maintenant une idée de la programmation ou les développeurs qui devraient analyser ce qui a mal tourné? Je ne sais jamais à qui dois-je m'adresser lorsque j'écris des messages.
t3chb0t
2
Je pense qu'une bonne réponse ici peut faire référence à l'une ou aux deux de ces questions, mais elles sont loin d'être des doublons.
Courses de légèreté avec Monica
1
Pourquoi diable était-ce fermé en double? Même s'il s'agissait d'un doublon de cette autre question (ce qui n'est pas le cas), celle-ci a clairement une meilleure réponse, ce devrait être l'autre qui est marquée comme doublon.
Ben Aaronson
2
@MichaelT Je ne vois toujours pas beaucoup de chevauchements à moins que vous ne supposiez que les messages d'exception sont destinés aux utilisateurs
Ben Aaronson

Réponses:

46

Ces messages sont destinés aux autres développeurs

Ces messages devraient être lus par les développeurs pour les aider à déboguer l'application. Cela peut prendre deux formes:

  • Débogage actif. Vous exécutez en fait un débogueur tout en écrivant du code et en essayant de comprendre ce qui se passe. Dans ce contexte, une exception utile vous guidera en facilitant la compréhension de ce qui ne va pas ou en suggérant éventuellement une solution de contournement (bien que cela soit facultatif).

  • Débogage passif. Le code s'exécute en production et échoue. L'exception est enregistrée, mais vous obtenez uniquement le message et la trace de pile. Dans ce contexte, un message d'exception utile vous aidera à localiser rapidement le bogue.

    Étant donné que ces messages sont souvent enregistrés, cela signifie également que vous ne devez pas y inclure d'informations sensibles (telles que des clés privées ou des mots de passe, même si cela peut être utile pour déboguer l'application).

Par exemple, le IOSecurityExceptiontype d'une exception levée lors de l'écriture d'un fichier n'est pas très explicite sur le problème: est-ce parce que nous n'avons pas les autorisations d'accès à un fichier? Ou peut-être pouvons-nous le lire, mais pas l'écrire? Ou peut-être que le fichier n'existe pas et que nous n'avons pas les autorisations pour y créer des fichiers? Ou peut-être qu'il est verrouillé (espérons que le type de l'exception sera différent dans ce cas, mais dans la pratique, les types peuvent parfois être cryptiques). Ou peut-être que la sécurité d'accès au code nous empêche d'effectuer des opérations d'E / S?

Au lieu:

IOSecurityException: le fichier a été trouvé mais l'autorisation de lire son contenu a été refusée.

est beaucoup plus explicite. Ici, nous savons immédiatement que les autorisations sur le répertoire sont définies correctement (sinon, nous ne pourrons pas savoir que le fichier existe), mais les autorisations au niveau du fichier sont problématiques.

Cela signifie également que si vous ne pouvez pas fournir d'informations supplémentaires qui ne sont pas déjà dans le type de l'exception, vous pouvez laisser le message vide. DivisionByZeroExceptionest un bon exemple où le message est redondant. D'un autre côté, le fait que la plupart des langues vous permettent de lever une exception sans spécifier son message se fait pour une raison différente: soit parce que le message par défaut est déjà disponible, soit parce qu'il sera généré plus tard si nécessaire (et la génération de ce message est inclus dans le type d'exception, ce qui est parfaitement logique, "OOPly" parlant).

Notez que pour des raisons techniques (souvent de performances), certains messages finissent par être beaucoup plus cryptés qu'ils ne devraient l'être. .NET NullReferenceException:

La référence d'objet n'est pas définie à une instance d'un objet.

est un excellent exemple d'un message qui n'est pas utile. Un message utile serait:

La référence d'objet productn'est pas définie sur une instance d'un objet lorsqu'elle est appelée product.Price.

Ces messages ne sont pas destinés aux utilisateurs finaux!

Les utilisateurs finaux ne devraient pas voir les messages d'exception. Jamais. Bien que certains développeurs finissent par montrer ces messages aux utilisateurs, cela entraîne une mauvaise expérience utilisateur et de la frustration. Messages tels que:

La référence d'objet n'est pas définie à une instance d'un objet.

ne signifie absolument rien pour un utilisateur final et doit être évité à tout prix.

Le pire des cas est d'avoir un try / catch global qui lève l'exception à l'utilisateur et quitte l'application. Une application soucieuse de ses utilisateurs:

  • Gère les exceptions en premier lieu. La plupart d'entre eux peuvent être manipulés sans déranger l'utilisateur. Le réseau est en panne? Pourquoi ne pas attendre quelques secondes et réessayer?

  • Empêche un utilisateur de diriger l'application vers un cas exceptionnel. Si vous demandez à l'utilisateur d'entrer deux nombres et de diviser le premier par le second, pourquoi laisseriez-vous l'utilisateur entrer zéro dans le deuxième cas afin de le blâmer quelques secondes plus tard? Qu'en est-il de surligner la zone de texte en rouge (avec une info-bulle utile indiquant que le nombre doit être différent de zéro) et de désactiver le bouton de validation jusqu'à ce que le champ reste rouge?

  • Invite un utilisateur à effectuer une action dans un formulaire qui n'est pas une erreur. Il n'y a pas suffisamment d'autorisations pour accéder à un fichier? Pourquoi ne pas demander à l'utilisateur d'accorder des autorisations administratives ou de choisir un autre fichier?

  • Si rien d'autre ne fonctionne, affiche une erreur utile et conviviale qui est spécifiquement écrite pour réduire la frustration de l'utilisateur, aider l'utilisateur à comprendre ce qui s'est mal passé et éventuellement résoudre le problème, et également l'aider à prévenir l'erreur à l'avenir (le cas échéant).

    Dans votre question, vous avez suggéré d'avoir deux messages dans une exception: un message technique pour les développeurs et un pour les utilisateurs finaux. Bien qu'il s'agisse d'une suggestion valable dans certains cas mineurs, la plupart des exceptions sont produites à un niveau où il est impossible de produire un message significatif pour les utilisateurs. Prenez DivisionByZeroExceptionet imaginez que nous ne pouvions pas empêcher l'exception de se produire et que nous ne pouvons pas la gérer nous-mêmes. Lorsque la division se produit, le framework (puisque c'est le framework, et non le code métier, qui lève l'exception) sait-il quel sera un message utile pour un utilisateur? Absolument pas:

    La division par zéro s'est produite. [D'ACCORD]

    Au lieu de cela, on peut le laisser lever l'exception, puis l'attraper à un niveau supérieur où nous connaissions le contexte commercial et pourrions agir en conséquence afin d'aider réellement l'utilisateur final, montrant ainsi quelque chose comme:

    Le champ D13 ne peut pas avoir la valeur identique à celle du champ E6, car la soustraction de ces valeurs est utilisée comme diviseur. [D'ACCORD]

    ou peut-être:

    Les valeurs signalées par le service ATP ne correspondent pas aux données locales. Cela peut être dû au fait que les données locales ne sont pas synchronisées. Souhaitez-vous synchroniser les informations d'expédition et réessayer? [Oui Non]

Ces messages ne sont pas destinés à l'analyse

Les messages d'exception ne devraient pas non plus être analysés ni utilisés par programme. Si vous pensez que l'appelant peut avoir besoin d'informations supplémentaires, incluez-les dans l'exception côte à côte avec le message. Ceci est important, car le message peut être modifié sans préavis. Le type fait partie de l'interface, mais le message ne l'est pas: ne vous y fiez jamais pour la gestion des exceptions.

Imaginez le message d'exception:

La connexion au serveur de mise en cache a expiré après avoir attendu 500 ms. Augmentez le délai d'expiration ou vérifiez la surveillance des performances pour identifier une baisse des performances du serveur. Le temps d'attente moyen pour la mise en cache du serveur était de 6 ms. pour le dernier mois, 4 ms. pour la dernière semaine et 377 ms. pour la dernière heure.

Vous souhaitez extraire les valeurs «500», «6», «4» et «377». Réfléchissez un peu à l'approche que vous utiliserez pour effectuer l'analyse, puis poursuivez la lecture.

Vous avez l'idée? Génial.

Maintenant, le développeur d'origine a découvert une faute de frappe:

Connecting to the caching sever timed out after waiting [...]

devrait être:

                            ↓
Connecting to the caching server timed out after waiting [...]

De plus, le développeur considère que le mois / semaine / une heure ne sont pas particulièrement pertinents, il fait donc également un changement supplémentaire:

Le temps d'attente moyen pour la mise en cache du serveur était de 6 ms. pour le dernier mois, 5 ms. pour les dernières 24 heures et 377 ms. pour la dernière heure.

Que se passe-t-il avec votre analyse?

Au lieu d'analyser, vous pouvez utiliser des propriétés d'exception (qui peuvent contenir tout ce que vous voulez, dès que les données peuvent être sérialisées):

{
    message: "Connecting to the caching [...]",
    properties: {
        "timeout": 500,
        "statistics": [
            { "timespan": 1, "unit": "month", "average-timeout": 6 },
            { "timespan": 7, "unit": "day", "average-timeout": 4 },
            { "timespan": 1, "unit": "hour", "average-timeout": 377 },
        ]
    }
}

Est-il facile d'utiliser ces données maintenant?

Parfois (comme dans .NET), le message peut même être traduit dans la langue de l'utilisateur (à mon humble avis, traduire ces messages est absolument faux, car tout développeur devrait être capable de lire en anglais). L'analyse de tels messages est presque impossible.

Arseni Mourzenko
la source
2
Bon point sur l'utilisateur final. J'ajouterais que l'utilisateur final ne devrait pas voir les messages d'exception également pour des raisons de sécurité. La connaissance de la nature exacte d'une erreur pour un utilisateur non profane peut présenter un exploit. L'utilisateur final ne doit connaître l'erreur que dans le contexte de ce qu'il faisait.
Neil
1
@Neil: c'est un peu trop théorique. En pratique, l'avantage de masquer les journaux à l'utilisateur est compensé par la complexité de la journalisation en ligne. Sans compter l'aspect convivial. Imaginez que vous installez Visual Studio sur votre nouvelle machine virtuelle Windows. Soudain, l'installation échoue. Préférez-vous simplement consulter les journaux et diagnostiquer le problème vous-même (avec l'aide de Stack Exchange et des blogs), ou attendre que Microsoft résolve le problème et publie un correctif?
Arseni Mourzenko
4
Je pense que tout le monde est conscient que le message "référence d'objet ..." est inutile. La question pertinente est cependant de savoir quel code machine souhaitez-vous que la gigue génère pour produire le message que vous souhaitez? Si vous faites cet exercice, vous découvrez rapidement que le message ne peut pas être généré facilement sans un coût de performance énorme imposé à tous les cas non exceptionnels . L'avantage de rendre le travail d'un développeur imprudent très légèrement plus facile n'est pas payé par la performance atteinte à l'utilisateur final.
Eric Lippert
7
En ce qui concerne les exceptions de sécurité: moins il y a d'informations dans l'exception, mieux c'est. Mon anecdote préférée est que dans une version pré-bêta de .NET 1.0, vous pourriez obtenir un message d'exception comme "Vous n'avez pas l'autorisation de déterminer le nom du fichier C: \ foo.txt". Très bien, merci pour ce message d'exception détaillé!
Eric Lippert
2
Je ne suis pas d'accord pour ne jamais montrer à l'utilisateur final un message d'erreur détaillé. Il m'est arrivé plusieurs fois que j'obtiens un message d'erreur cryptique m'empêchant de démarrer mon jeu / logiciel, mais quand je le recherche sur Google, je découvre qu'il existe une solution de contournement facile. Sans ce message d'erreur, il n'y aurait aucun moyen de trouver la solution de contournement. Peut-être est-il préférable de simplement masquer les détails sous un bouton "plus de détails"?
BlueRaja - Danny Pflughoeft
2

La réponse à cela dépend entièrement de l'exception.

La question la plus importante à poser est "qui peut résoudre le problème?" Si l'exception est causée par une erreur de système de fichiers lors de l'ouverture d'un fichier, il peut être judicieux de transmettre ce message à l'utilisateur final. Le message peut contenir plus d'informations sur la façon de corriger l'erreur. Je peux penser à un cas définitif dans mon travail où j'avais un système de plugins qui chargeait des DLL. Si un utilisateur a fait une faute de frappe sur la ligne de commande pour charger le mauvais plugin, le message d'erreur système sous-jacent contenait en fait des informations utiles pour lui permettre de corriger le problème.

Cependant, la plupart du temps, une exception non interceptée ne peut pas être corrigée par l'utilisateur. La plupart d'entre eux impliquent d'apporter des modifications au code. Dans ces cas, le consommateur du message est clairement un développeur.

Le cas des exceptions interceptées est plus compliqué car vous avez la possibilité de traiter correctement l'exception et d'en lancer une plus conviviale. Un langage de script que j'ai écrit a levé des exceptions dont le message était très clairement destiné à un développeur. Cependant, lorsque la pile s'est déroulée, j'ai ajouté des traces de pile lisibles par l'utilisateur à partir du langage de script. Mes utilisateurs pouvaient très rarement bloquer le message, mais ils pouvaient regarder la trace de la pile dans leur script et comprendre ce qui s'était passé 95% du temps. Pour les 5% restants, quand ils m'ont appelé, nous avions non seulement une trace de pile, mais un message prêt à l'emploi pour m'aider à comprendre ce qui n'allait pas.

En tout, je traite le message d'exception comme un contenu inconnu. Quelqu'un plus en amont, qui en savait plus sur les implémentations, a accordé une exception à mon logiciel. Parfois, j'ai l'impression qu'un contenu inconnu sera utile à un utilisateur final, parfois ce n'est pas le cas.

Cort Ammon - Rétablir Monica
la source
1
"Si l'exception est causée par une erreur de système de fichiers lors de l'ouverture d'un fichier, il peut être judicieux de transmettre ce message à l'utilisateur final": mais lorsque vous lancez l'erreur de système de fichiers, vous ne connaissez pas le contexte: il peut s'agir de l'action de l'utilisateur, ou de quelque chose de complètement indépendant (comme votre application qui sauvegarde une configuration, sans que l'utilisateur ne s'en rende compte). Cela implique que vous aurez un bloc try / catch qui affiche un message d'erreur , ce qui est très différent de l'affichage direct du message d'exception .
Arseni Mourzenko
@MainMa Vous ne connaissez peut-être pas le contexte explicitement, mais il y a des tonnes d'indices. Comme, par exemple, s'ils sont en train d'ouvrir un fichier, et l'exception est "IOError: permission refusée", il y a une chance raisonnable que l'utilisateur puisse assembler 2 et 2 et comprendre le fichier en question. Mon éditeur préféré le fait - il sort simplement le texte d'exception dans une boîte de dialogue, pas de peluches. D'un autre côté, si je chargeais mon application et obtenais une "autorisation refusée" lors du chargement d'un tas d'images, il est beaucoup moins raisonnable de supposer que l'utilisateur peut faire quelque chose avec les informations.
Cort Ammon - Rétablir Monica
Arguant de votre point, je n'ai jamais vu aucune valeur à montrer à un utilisateur une NullReferenceException, à part que le simple fait d'afficher ce message montre que votre logiciel n'avait aucune idée de comment récupérer! Ce message d'exception n'est jamais utile à un utilisateur final.
Cort Ammon - Rétablir Monica