Gestion des erreurs - Si un programme échoue sur des erreurs ou les ignore silencieusement

20

J'écris un petit programme simple pour transmettre du MIDI sur un réseau. Je sais que le programme rencontrera des problèmes de transmission et / ou d'autres situations d'exception que je ne pourrai pas prévoir.

Pour la gestion des exceptions, je vois deux approches. Dois-je écrire le programme pour qu'il:

  • échoue avec un bang quand quelque chose ne va pas ou
  • Faut-il simplement ignorer l'erreur et continuer, au détriment de l'intégrité des données?

À quelle approche un utilisateur pourrait-il raisonnablement s'attendre?
Existe-t-il une meilleure façon de gérer les exceptions?

De plus, ma décision concernant la gestion des exceptions devrait-elle être affectée par le fait que je traite ou non avec une connexion réseau (c'est-à-dire quelque chose où je peux raisonnablement m'attendre à des problèmes)?

Arlen Beiler
la source
1
@ GlenH7 Pay it forward ... :)
moucher
Existe-t-il un moyen d'effectuer des modifications +1? ;) Oh, attends, je te suis. Bien sûr, fera l'affaire :)
Arlen Beiler
2
"Quelle approche un utilisateur pourrait-il raisonnablement attendre?". Avez-vous essayé de demander à l'un de vos utilisateurs? Les tests d'utilisabilité des couloirs peuvent être remarquablement efficaces. Voir blog.openhallway.com/?p=146
Bryan Oakley
Je n'ai pas d'utilisateurs :)
Arlen Beiler

Réponses:

34

Vous ne devez jamais ignorer une erreur que votre programme rencontre. Au strict minimum, vous devez le connecter à un fichier ou à un autre mécanisme pour notification. Il peut y avoir des situations occasionnelles où vous voudrez ignorer une erreur mais la documenter! N'écrivez pas un catchbloc vide sans aucun commentaire expliquant pourquoi il est vide.

Que le programme échoue ou non dépend beaucoup du contexte. Si vous pouvez gérer l'erreur avec élégance, allez-y. S'il s'agit d'une erreur inattendue, votre programme se bloquera. C'est à peu près la base de la gestion des exceptions.

marco-fiset
la source
18
Pas assez pour justifier un -1, mais "jamais" est un mot fort, et il y a des situations où ignorer les erreurs est la bonne ligne de conduite. Par exemple, un logiciel pour décoder les transmissions HDTV diffusées. Lorsque vous rencontrez des erreurs, ce que vous rencontrez fréquemment, tout ce que vous pouvez faire est de l'ignorer et de continuer à décoder ce qui vient après.
whatsisname
1
@whatsisname: true, mais vous devez clairement documenter pourquoi vous l' ignorez . Réponse mise à jour pour prendre en compte votre commentaire.
marco-fiset
1
@ marco-fiset je ne suis pas d'accord. Plantage de l'ensemble du programme n'est la solution que si c'est une situation où le redémarrage résoudrait le problème. Ce n'est pas toujours le cas, donc planter est souvent inutile et par défaut ce comportement est tout simplement stupide. Pourquoi voudriez-vous suspendre l'ensemble de votre opération en raison d'une erreur localisée? Ça n'a aucun sens. La plupart des applications le font et, pour une raison quelconque, les programmeurs sont parfaitement d'accord avec cela.
MaiaVictor
@Dokkat: le point est de ne pas continuer avec des valeurs erronées, ce qui donnera une mauvaise solution (garbage in, garbage out). Le plantage résout le problème: il oblige l'utilisateur à fournir différentes entrées ou à harceler le développeur pour corriger son programme;)
André Paramés
1
@whatsisname Je pense que vous voudrez peut-être réfléchir à votre définition de "l'erreur" dans ce cas. Les erreurs dans les données reçues ne sont pas la même chose que les erreurs dans la structure et l'exécution d'un logiciel.
joshin4colours
18

Vous ne devez jamais ignorer les erreurs en silence, car votre programme est construit sur une série d'actions qui dépendent implicitement de tout ce qui s'est passé avant qu'elles ne se déroulent correctement. Si quelque chose se passe mal à l'étape 3 et que vous essayez de passer à l'étape 4, l'étape 4 commencera sur la base d'hypothèses non valides, ce qui rend plus probable qu'elle finira également par générer une erreur. (Et si vous l'ignorez également, l'étape 5 génère une erreur et les choses commencent à faire boule de neige à partir de là.)

Le fait est que, au fur et à mesure que les erreurs s'accumulent, vous finirez par rencontrer une erreur si grande que vous ne pouvez pas l'ignorer, car elle consistera en quelque chose donné à l'utilisateur, et que quelque chose sera complètement faux. Ensuite, des utilisateurs se plaignent que votre programme ne fonctionne pas correctement et vous devez le corriger. Et si la partie "donner quelque chose à l'utilisateur" se trouve à l'étape 28, et vous ne savez pas que l'erreur d'origine qui cause tout ce gâchis était à l'étape 3 parce que vous avez ignoré l'erreur à l'étape 3, vous allez avoir un diable d'un temps de débogage du problème!

D'un autre côté, si cette erreur de l'étape 3 fait tout exploser au visage de l'utilisateur et génère une erreur disant SOMETHING WENT BADLY WRONG IN STEP 3!(ou son équivalent technique, une trace de pile), alors le résultat est le même - l'utilisateur se plaignant de vous le programme ne fonctionne pas correctement - mais cette fois, vous savez exactement où commencer à chercher lorsque vous allez le réparer .

EDIT: En réponse aux commentaires, si quelque chose se passe mal que vous aviez prévu et savez comment gérer, c'est différent. Par exemple, en cas de réception d'un message mal formé, ce n'est pas une erreur de programme; c'est "l'utilisateur a fourni une mauvaise entrée qui a échoué à la validation". La réponse appropriée est de dire à l'utilisateur qu'il vous donne une entrée non valide, ce qui ressemble à ce que vous faites. Pas besoin de planter et de générer une trace de pile dans un cas comme celui-ci.

Mason Wheeler
la source
Que diriez-vous de revenir à la dernière bonne position connue, ou dans le cas d'une boucle de réception, pour simplement laisser tomber ce message et passer au suivant.
Arlen Beiler
@Arlen: Même cela peut être une mauvaise idée. Des erreurs se produisent parce que quelque chose s'est produit que vous n'aviez pas prévu lorsque vous le codiez . Cela signifie que toutes vos hypothèses ont disparu. Cette "dernière bonne position connue" pourrait ne plus être bonne si vous étiez à mi-chemin de la modification d'une structure de données et maintenant elle est dans un état incohérent, par exemple.
Mason Wheeler
Et si je m'y attendais, comme des messages corrompus empêchant la désérialisation. Dans certains cas, j'ai sérialisé l'exception et l'ai renvoyée sur le câble. Là, voir ma modification de la question.
Arlen Beiler
@Arlen: Si vous l'aviez anticipé et que vous êtes sûr de savoir quels sont les effets de l'erreur et qu'ils sont contenus correctement, alors c'est différent. Il me semble que vous gérez l'erreur et répondez de manière appropriée, sans l'ignorer. Je parlais d'ignorer les erreurs inattendues , ce qui ressemblait à ce que vous demandiez.
Mason Wheeler
16

Il existe d'autres options entre "exploser" et "ignorer".

Si l'erreur est prévisible et évitable, modifiez votre conception ou refactorisez votre code pour l'éviter.

Si l'erreur est prévisible mais non évitable, mais que vous savez quoi faire lorsqu'elle se produit, détectez l'erreur et gérez la situation. Mais évitez d'utiliser des exceptions comme contrôle de flux. Et vous souhaiterez peut-être enregistrer un avertissement à ce stade, et peut-être informer l'utilisateur s'il peut prendre des mesures pour éviter cette situation à l'avenir.

Si l'erreur est prévisible, inévitable, et lorsque cela se produit, vous ne pouvez rien faire qui garantisse l'intégrité des données, vous devez enregistrer l'erreur et revenir à un état sûr (ce qui, comme d'autres l'ont dit, peut signifier un crash).

Si l'erreur n'est pas quelque chose que vous avez anticipée, vous ne pouvez vraiment pas être sûr que vous pouvez même revenir à un état sûr, il est donc préférable de simplement vous connecter et planter.

En règle générale, ne détectez aucune exception à laquelle vous ne pouvez rien faire, sauf si vous prévoyez simplement de vous connecter et de la relancer. Et dans les rares cas où un try-catch-ignore est inévitable, ajoutez au moins un commentaire dans votre bloc catch pour expliquer pourquoi.

Voir l'excellent article d' Eric Lippert sur la gestion des exceptions pour plus de suggestions sur la catégorisation et la gestion des exceptions.

John M Gant
la source
1
La meilleure réponse à ce jour, à mon avis.
jeudijeudi
6

Voici mes vues sur la question:

Un bon principe de départ est d'échouer rapidement. Plus précisément, vous ne devez jamais écrire de code de gestion des erreurs pour toute défaillance dont vous ne connaissez pas la cause exacte.

Après avoir appliqué ce principe, vous pouvez ajouter du code de récupération pour les conditions d'erreur spécifiques que vous rencontrez. Vous pouvez également introduire plusieurs "états sûrs" pour revenir. L'abandon d'un programme est généralement sûr, mais parfois vous voudrez peut-être revenir à un autre bon état connu. Un exemple est de savoir comment un système d'exploitation moderne gère un programme incriminé. Il arrête uniquement le programme, pas l'ensemble du système d'exploitation.

En échouant rapidement et lentement à couvrir des conditions d'erreur de plus en plus spécifiques, vous ne compromettez jamais l'intégrité des données et vous vous dirigez régulièrement vers un programme plus stable.

Avaler des erreurs, c'est-à-dire essayer de planifier des erreurs dont vous ne connaissez pas la cause exacte et par conséquent n'avez pas de stratégie de récupération spécifique, ne fait que conduire à une quantité croissante de code pour ignorer et contourner les erreurs dans votre programme. Puisqu'on ne peut pas croire que les données précédentes ont été correctement traitées, vous commencerez à voir des contrôles étalés pour les données incorrectes ou manquantes. Votre complexité cyclomatique va déraper et vous vous retrouverez avec une grosse boule de boue.

Que vous soyez ou non au courant des cas d'échec est moins important. Mais si, par exemple, vous traitez avec une connexion réseau pour laquelle vous connaissez un certain nombre d'états d'erreur, retardez l'ajout de la gestion des erreurs jusqu'à ce que vous ajoutiez également un code de récupération. Cela est conforme aux principes énoncés ci-dessus.

Alexander Torstling
la source
6

Vous ne devez jamais ignorer silencieusement les erreurs. Et surtout pas au détriment de l'intégrité des données .

Le programme essaie de faire quelque chose. Si cela échoue, vous devez faire face au fait et faire quelque chose . La nature de ce quelque chose dépend de beaucoup de choses.

En fin de compte, l'utilisateur a demandé au programme de faire quelque chose et le programme devrait leur dire qu'il n'a pas réussi. Il y a plusieurs façons de le faire. Il peut s'arrêter immédiatement, il peut même annuler les étapes déjà terminées ou d'un autre côté, il peut continuer et terminer toutes les étapes qu'il peut et dire à l'utilisateur que ces étapes ont réussi et que les autres ont échoué.

La façon dont vous choisissez dépend de la façon dont les étapes sont liées et s'il est probable que l'erreur se reproduira pour toutes les étapes futures, ce qui pourrait à son tour dépendre de l'erreur exacte. Si une forte intégrité des données est requise, vous devez revenir au dernier état cohérent. Si vous copiez simplement des fichiers, vous pouvez en ignorer certains et simplement dire à l'utilisateur à la fin que ces fichiers n'ont pas pu être copiés. Vous ne devez pas ignorer les fichiers en silence et ne rien dire à l'utilisateur.

La modification des annonces, la seule différence qui fait que vous devriez envisager de réessayer un certain nombre de fois avant d'abandonner et de dire à l'utilisateur que cela ne fonctionne pas, car le réseau est susceptible d'avoir des erreurs transitoires qui ne se reproduiront pas si vous essayez à nouveau.

Jan Hudec
la source
Vouliez-vous vraiment mettre un point d'interrogation à la fin de la première ligne?
un CVn du
@ MichaelKjörling: Non. Erreur de copier-coller (copie de la formulation de la question et inclusion du point d'interrogation par erreur).
Jan Hudec
4

Il existe une catégorie de cas où ignorer les erreurs est la bonne chose à faire: lorsqu'il n'y a rien qui puisse être fait à propos de la situation et que des résultats médiocres et éventuellement incorrects valent mieux que pas de résultats.

Le cas du décodage du flux HDMI à des fins d'affichage en est un. Si le flux est mauvais, c'est mauvais, crier dessus ne le réparera pas comme par magie. Vous faites ce que vous pouvez pour l'afficher et laissez le spectateur décider s'il est tolérable ou non.

Loren Pechtel
la source
1

Je ne crois pas qu'un programme devrait ignorer silencieusement ou causer des ravages chaque fois qu'il rencontre un problème.

Ce que je fais avec les logiciels internes que j'écris pour mon entreprise ...

Cela dépend de l'erreur, disons que s'il s'agit d'une fonction critique qui entre des données dans MySQL, elle doit informer l'utilisateur qu'elle a échoué. Le gestionnaire d'erreurs doit essayer de collecter autant d'informations et fournir à l'utilisateur une idée de la façon de corriger l'erreur lui-même afin qu'il puisse enregistrer les données. J'aime également fournir un moyen de nous envoyer silencieusement les informations qu'ils essaient de sauvegarder, donc si le pire vient au pire, nous pouvons les entrer manuellement après la correction du bogue.

Si ce n'est pas une fonction critique, quelque chose qui peut générer des erreurs et ne pas affecter le résultat final de ce qu'ils essaient de réaliser, je ne leur montrerai peut-être pas de message d'erreur, mais je leur ferai envoyer un e-mail qui l'insérera automatiquement dans notre logiciel de suivi des bogues. ou un groupe de distribution d'e-mails qui alerte tous les programmeurs de l'entreprise afin que nous soyons conscients de l'erreur, même si l'utilisateur ne l'est pas. Cela nous permet de fixer le back-end tandis que sur le front-end, personne ne sait ce qui se passe.

L'une des plus grandes choses que j'essaie d'éviter est de faire planter le programme après l'erreur - de ne pas pouvoir récupérer. J'essaie toujours de donner à l'utilisateur la possibilité de continuer sans fermer l'application.

Je crois que si personne ne connaît le bug - il ne sera jamais corrigé. Je suis également un fervent partisan de la gestion des erreurs qui permet à l'application de continuer à fonctionner une fois qu'un bogue est découvert.

Si l'erreur est liée au réseau - pourquoi ne pas demander aux fonctions d'effectuer un test de communication réseau simple avant d'exécuter la fonction pour éviter l'erreur en premier lieu? Ensuite, simplement pour alerter l'utilisateur qu'une connexion n'est pas disponible, veuillez vérifier votre Internet, etc., etc. et réessayer?

Jeff
la source
1
Personnellement, je fais beaucoup de vérification avant d'exécuter des fonctions critiques pour essayer de limiter les erreurs que je ne comprends pas complètement et je ne peux pas fournir une gestion des erreurs utile qui renvoie des informations détaillées. Cela ne fonctionne pas à 100%, mais je ne me souviens pas de la dernière fois que j'ai eu un bug qui m'a fait perdre des heures.
Jeff
1

Ma propre stratégie consiste à distinguer les erreurs de codage (bogues) des erreurs d'exécution et, dans la mesure du possible, à rendre les erreurs de codage difficiles à créer.

Les bogues doivent être corrigés dès que possible, donc une approche de conception par contrat est appropriée. En C ++, j'aime vérifier toutes mes conditions préalables (entrées) avec des assertions en haut de la fonction afin de détecter le bogue dès que possible et de faciliter la connexion d'un débogueur et la correction du bogue. Si le développeur ou le testeur choisit à la place d'essayer de continuer à exécuter le programme, toute perte d'intégrité des données devient alors son problème.

Et trouvez des moyens d'empêcher le bogue en premier lieu. Être strict avec const-correctness et choisir les types de données appropriés pour les données qu'ils contiendront sont deux façons de rendre difficile la création de bogues. Fail-Fast est également bon en dehors du code critique de sécurité qui a besoin d'un moyen de récupération.

Pour les erreurs d'exécution susceptibles de se produire avec un code sans bogue, telles que des échecs de communication réseau ou série ou des fichiers manquants ou corrompus:

  1. Enregistrez l'erreur.
  2. (Facultatif) Essayez de réessayer en mode silencieux ou de récupérer d'une autre manière l'opération.
  3. Si l'opération échoue toujours ou est irrécupérable, signalez l'erreur visiblement à l'utilisateur. Ensuite, comme ci-dessus, l'utilisateur peut décider quoi faire. Rappelez-vous le principe du moindre étonnement , car une perte d'intégrité des données surprend l'utilisateur, sauf si vous l'avez averti à l'avance.
snips-n-escargots
la source
0

L'échec est la bonne option lorsque vous avez des raisons de penser que l'état général du programme est instable et que quelque chose de grave peut se produire si vous le laissez s'exécuter à partir de maintenant. Un peu "l'ignorer" (c'est-à-dire, comme d'autres l'ont fait remarquer, l'enregistrer quelque part ou afficher un message d'erreur à l'utilisateur, puis continuer) est correct lorsque vous savez que, bien sûr, l'opération en cours ne peut pas être effectuée, mais le programme peut continuer à courir.

Fabien
la source