Arguments contre la suppression d'erreur


35

J'ai trouvé un morceau de code comme celui-ci dans l'un de nos projets:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Autant que je sache, supprimer de telles erreurs est une mauvaise pratique, car cela détruit des informations utiles provenant de l'exception du serveur d'origine et fait en sorte que le code continue à exister alors qu'il devrait se terminer.

Quand est-il approprié de supprimer complètement toutes les erreurs de ce type?


13
Eric Lippert a écrit un article de blog intitulé Exceptions contrariantes où il classe les exceptions en 4 catégories différentes et suggère quelle est la bonne approche pour chacune d’elles. Écrit dans une perspective C #, mais applicable dans toutes les langues, je crois.
Damien_The_Unbeliever

3
Je crois que ce code viole le principe "Fail-fast". Il y a une forte probabilité que le bogue réel y soit caché.
Euphoric


4
Vous attrapez trop. Vous attraperiez également des bogues, tels que NRE, un index de tableau hors limite, une erreur de configuration, ... Cela vous empêche de trouver ces bogues et ils causeront continuellement des dommages.
Usr

Réponses:


52

Imaginez du code avec des milliers de fichiers en utilisant plusieurs bibliothèques. Imaginez que tous soient codés comme ceci.

Imaginons, par exemple, qu'une mise à jour de votre serveur provoque la disparition d'un fichier de configuration. et maintenant tout ce que vous avez, c'est une trace de pile, c'est une exception de pointeur nul lorsque vous essayez d'utiliser cette classe: comment voulez-vous résoudre ce problème? Cela peut prendre des heures. Au moins, la journalisation de la trace de pile brute du fichier non trouvé [chemin du fichier] peut vous permettre de résoudre momentanément.

Ou pire encore: une défaillance de l'une des bibliothèques que vous utilisez après une mise à jour qui fait planter votre code plus tard. Comment pouvez-vous suivre ce retour à la bibliothèque?

Même sans gestion d'erreur robuste, il suffit de faire

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

ou

LOGGER.error("[method name]/[arguments] should not be there")

peut vous faire gagner des heures.

Mais dans certains cas, vous pouvez vraiment vouloir ignorer l'exception et renvoyer null comme ceci (ou ne rien faire). Surtout si vous intégrez du code hérité mal conçu et que cette exception est attendue comme un cas normal.

En fait, vous devriez simplement vous demander si vous ignorez vraiment l'exception ou la "gestion appropriée" de vos besoins. Si renvoyer la valeur null "traite correctement" votre exception dans ce cas, faites-le. Et ajoutez un commentaire expliquant pourquoi c’est la bonne chose à faire.

Les meilleures pratiques sont les choses à suivre dans la plupart des cas, peut-être 80%, peut-être 99%, mais vous trouverez toujours un cas où elles ne s'appliqueront pas. Dans ce cas, laissez un commentaire sur la raison pour laquelle vous ne suivez pas la pratique pour les autres (ou même pour vous-même) qui liront votre code des mois plus tard.


7
+1 pour expliquer pourquoi vous pensez avoir besoin de le faire dans un commentaire. Sans une explication, une exception à avaler soulèverait toujours des alarmes d'alarme dans ma tête.
jpmc26

Mon problème avec cela est que le commentaire est coincé dans ce code. En tant que consommateur de la fonction, il est possible que je ne voie jamais ce commentaire.
CorsiKa

@ jpmc26 Si l'exception est gérée (par exemple en renvoyant une valeur spéciale), il n'y a aucune exception.
Déduplicateur

2
@corsiKa un consommateur n'a pas besoin de connaître le détail de la mise en œuvre si la fonction fait son travail correctement. peut-être sont-ils présents dans les bibliothèques Apache ou au printemps et vous ne le savez pas.
Walfrat

@DuDuplicator oui, mais utiliser un catch dans votre algorithme n'est pas censé être une bonne pratique aussi :)
Walfrat

14

Il existe des cas où ce modèle est utile - mais ils sont généralement utilisés lorsque l'exception générée n'aurait jamais dû l'être (c'est-à-dire lorsque des exceptions sont utilisées pour un comportement normal).

Par exemple, imaginons qu'une classe ouvre un fichier et le stocke dans l'objet renvoyé. Si le fichier n'existe pas, vous pouvez considérer que ce n'est pas un cas d'erreur, vous renvoyez la valeur null et laissez l'utilisateur créer un nouveau fichier. La méthode file-open peut générer une exception ici pour indiquer qu'aucun fichier n'est présent. Il peut donc être correct de l'attraper en silence.

Cependant, il est rare que vous souhaitiez le faire, et si le code est encombré d'un tel motif, vous voudrez le traiter maintenant. À tout le moins, je m'attendrais à ce qu'une personne aussi silencieuse rédige une ligne de journal indiquant que c'est ce qui s'est passé (vous avez tracé, à droite) et un commentaire pour expliquer ce comportement.


2
"utilisé lorsque l'exception générée n'aurait jamais dû être" pas une telle chose. Le POINT d'exception est de signaler que quelque chose a mal tourné.
Euphoric

3
@Euphoric Mais parfois, l'auteur d'une bibliothèque ne connaît pas les intentions de l'utilisateur final de cette bibliothèque. Une personne "horriblement mal" peut être la condition facilement récupérable de quelqu'un d'autre.
Simon B

2
@Euphoric, cela dépend de ce que votre langage considère comme "terriblement faux": les gens de Python aiment les exceptions pour des choses qui sont très proches du contrôle de flux en (par exemple, C ++). Le renvoi de null peut être défendable dans certaines situations - par exemple, la lecture d'un cache où des éléments peuvent être expulsés de manière soudaine et imprévisible.
Matt Krause

10
@Euphoric, "Le fichier introuvable n'est pas une exception normale. Vous devez d'abord vérifier si le fichier existe s'il est possible qu'il n'existe pas." Non! Non! Non! Non! C’est une pensée erronée classique concernant les opérations atomiques. Le fichier peut être supprimé entre vous pour vérifier s'il existe et pour y accéder. La seule façon correcte de gérer de telles situations est d'y accéder et de gérer les exceptions si elles se produisent, par exemple en retournant nullsi c'est la meilleure langue que votre langue choisie peut offrir.
David Arno

3
@Euphoric: Alors, écrivez le code pour éviter de pouvoir accéder au fichier au moins deux fois? Cela viole DRY, et le code non exercé est un code cassé.
Déduplicateur

7

Cela dépend du contexte à 100%. Si l'appelant d'une telle fonction a l'obligation d'afficher ou de consigner les erreurs quelque part, c'est évidemment un non-sens. Si l'appelant a ignoré toutes les erreurs ou tous les messages d'exception, il serait peu judicieux de renvoyer l'exception ou son message. Lorsque le code doit obligatoirement se terminer sans afficher de raison, un appel est alors envoyé.

   if(QueryServer(args)==null)
      TerminateProgram();

serait probablement suffisant. Vous devez vous demander si cela rendra difficile la recherche de la cause de l'erreur par l'utilisateur. Si tel est le cas, il s'agit d'une forme incorrecte de traitement des erreurs. Cependant, si le code d'appel ressemble à ceci

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

alors vous devriez avoir un argument avec le développeur lors de la révision du code. Si quelqu'un implémente une telle forme de traitement sans erreur simplement parce qu'il est trop paresseux pour connaître les exigences correctes, ce code ne devrait pas entrer en production et son auteur devrait être informé des conséquences possibles d'une telle paresse. .


5

Au cours de toutes mes années passées à programmer et à développer des systèmes, il n’ya que deux situations dans lesquelles j’ai trouvé le modèle en question utile (dans les deux cas, la suppression contenait également la journalisation de l’exception levée, je ne considère pas que la prise et le nullretour simples soient une bonne pratique. ).

Les deux situations sont les suivantes:

1. Lorsque l'exception n'a pas été considérée comme un état exceptionnel

C’est à ce moment-là que vous effectuez une opération sur certaines données, ce qui peut générer des jets, mais vous souhaitez tout de même que votre application continue de fonctionner, car vous n’avez pas besoin des données traitées. Si vous les recevez, c'est bien, si vous ne les recevez pas, c'est aussi bien.

Certains attributs facultatifs d'une classe peuvent être pris en compte.

2. Lorsque vous fournissez une nouvelle implémentation (meilleure, plus rapide?) D'une bibliothèque en utilisant une interface déjà utilisée dans une application

Imaginez que vous ayez une application utilisant une sorte d’ancienne bibliothèque, qui ne lançait pas d’exceptions, mais renvoyait des nullerreurs. Vous avez donc créé un adaptateur pour cette bibliothèque, copiant à peu près l'API d'origine de la bibliothèque, et utilisez cette nouvelle interface (toujours non lancée) dans votre application et nullgérez vous-même les vérifications.

Une nouvelle version de la bibliothèque est fournie, ou peut-être une bibliothèque complètement différente offrant les mêmes fonctionnalités qui, au lieu de renvoyer nulls, lève des exceptions et que vous souhaitez l’utiliser.

Vous ne voulez pas laisser les exceptions dans votre application principale. Vous devez donc les supprimer et les consigner dans l'adaptateur que vous avez créé pour envelopper cette nouvelle dépendance.


Le premier cas n'est pas un problème, c'est le comportement souhaité du code. Dans le second cas, cependant, si partout la nullvaleur renvoyée par l'adaptateur de bibliothèque signifie vraiment une erreur, il nullpeut être judicieux de refactoriser l'API pour générer une exception et de la capturer au lieu de la rechercher (et le code est généralement une bonne idée).

Personnellement, je n’utilise la suppression d’exception que pour le premier cas. Je ne l'ai utilisé que dans le second cas, lorsque nous n'avions pas le budget nécessaire pour que le reste de la demande fonctionne avec les exceptions au lieu de l' nullart.


-1

Bien qu'il semble logique de dire que les programmes ne doivent capturer que les exceptions qu'ils savent comment gérer et ne peuvent pas savoir comment gérer des exceptions qui n'ont pas été anticipées par le programmeur, une telle revendication ignore le fait que de nombreuses opérations peuvent échouer dans un processus. un nombre presque illimité de voies qui n'ont pas d'effets secondaires, et que dans de nombreux cas, le traitement approprié pour la grande majorité de ces défaillances sera identique; les détails exacts de l'échec seront sans importance et, par conséquent, peu importe si le programmeur les a anticipés.

Si, par exemple, une fonction a pour but de lire un fichier de document dans un objet approprié et de créer une nouvelle fenêtre de document pour montrer à cet objet ou de signaler à l'utilisateur que le fichier n'a pas pu être lu, une tentative de chargement Un fichier de document non valide ne doit pas bloquer l'application. Il doit plutôt afficher un message signalant le problème, mais laisser le reste de l'application continuer à s'exécuter normalement sauf si, pour une raison quelconque, la tentative de chargement du document a corrompu l'état d'un autre élément du système. .

Fondamentalement, le traitement correct d’une exception dépendra souvent moins du type d’exception que de l’endroit où elle a été lancée; si une ressource est protégée par un verrou en lecture-écriture et qu'une exception est levée dans une méthode ayant acquis le verrou en lecture, le comportement approprié doit généralement être de le libérer, car la méthode n'a rien pu faire pour la ressource. . Si une exception est levée alors que le verrou est acquis pour l'écriture, le verrou doit souvent être invalidé, car la ressource protégée peut être dans un état non valide. si la primitive de verrouillage n'a pas d'état "invalidé", il convient d'ajouter un indicateur pour suivre cette invalidation. Libérer le verrou sans l'invalider est mauvais, car un autre code peut voir l'objet protégé dans un état non valide. Laisser la serrure pendante, cependant, n'est pas ta solution appropriée non plus. La bonne solution consiste à invalider le verrou afin que toute tentative d'acquisition en cours ou future échoue immédiatement.

S'il s'avère qu'une ressource invalide est abandonnée avant toute tentative d'utilisation, il n'y a aucune raison pour qu'elle supprime l'application. Si une ressource invalidée est essentielle au fonctionnement continu d'une application, celle-ci devra être supprimée, mais son invalidité le rendra probablement nécessaire. Le code qui a reçu l’exception initiale n’aura souvent aucun moyen de savoir quelle situation s’applique, mais s’il invalide la ressource, il peut s’assurer que les mesures correctes seront prises dans les deux cas.


2
Cela ne répond pas à la question car la gestion d'une exception sans connaître les détails de l'exception! = Consomme silencieusement une exception (générique).
Taemyr

@Taemyr: Si le but d'une fonction est de "faire X si possible, ou sinon d'indiquer que ce n'était pas", et qu'il ne l'était pas, il sera souvent peu pratique d'énumérer tous les types d'exceptions que ni la fonction ni l'appelant ne se souciera au-delà de "Il n'était pas possible de faire X". La gestion des exceptions Pokemon est souvent le seul moyen pratique de faire fonctionner les choses dans de tels cas, et ne doit pas être aussi diabolique que certaines personnes le diront si le code est discipliné pour invalider des choses qui sont corrompues par des exceptions inattendues.
Supercat

Exactement. Chaque méthode doit avoir un but. Si elle peut le remplir, qu’elle appelle une exception ou non, une exception est ensuite avalée. Quoi qu’il en soit, faire ce qu’il faut et faire son travail est la bonne chose à faire. Le traitement des erreurs consiste fondamentalement à terminer la tâche après une erreur. C'est ce qui compte, pas quelle erreur s'est produite.
Jmoreno

@jmoreno: Ce qui rend les choses difficiles, c'est que dans de nombreuses situations, il y aura un grand nombre d'exceptions, dont beaucoup ne seraient pas anticipées par un programmeur, ce qui n'indiquerait pas un problème, et quelques exceptions, dont certaines par un programmeur ne prévoyez pas en particulier, ce qui indique un problème, et aucun moyen systématique défini pour les distinguer. Le seul moyen que je connaisse pour résoudre ce problème consiste à faire en sorte que les exceptions invalident les éléments corrompus, de sorte que s’ils importent, ils sont remarqués et s’ils ne l’importent pas, ils sont ignorés.
Supercat

-2

Avaler ce genre d'erreur n'est utile à personne. Oui, l'appelant initial peut ne pas s'inquiéter de l'exception, mais quelqu'un d'autre pourrait .

Alors que font-ils? Ils ajouteront du code pour gérer l'exception afin de voir quelle sorte d'exception ils récupéreront. Génial. Mais si le gestionnaire d'exceptions est laissé, l'appelant d'origine n'est plus nul et quelque chose se casse ailleurs.

Même si vous êtes conscient que du code en amont peut générer une erreur et ainsi renvoyer null, il serait sérieusement inerte de votre part de ne pas au moins essayer de contourner l'exception contenue dans le code appelant IMHO.


"sérieusement inerte" - vouliez-vous dire "sérieusement inepte"?
Nom d’écran ésotérique

@EsotericScreenName Choisissez une ... ;-)
Robbie Dee

-3

J'ai vu des exemples où des bibliothèques tierces avaient des méthodes potentiellement utiles, sauf qu'elles génèrent des exceptions dans certains cas qui devraient fonctionner pour moi. Que puis-je faire?

  • Mettre en œuvre exactement ce dont j'ai besoin moi-même. Peut être difficile sur une date limite.
  • Modifiez le code tiers. Mais ensuite, je dois rechercher des conflits de fusion avec chaque mise à niveau.
  • Ecrivez une méthode wrapper pour transformer l'exception en un pointeur null ordinaire, un booléen faux ou autre.

Par exemple, la méthode de la bibliothèque

public foo findFoo() {...} 

retourne le premier toto, mais s'il n'y en a pas, il lève une exception. Mais il me faut une méthode pour renvoyer le premier toto ou un null. Alors j'écris celui-ci:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Pas chouette. Mais parfois, la solution pragmatique.


1
Que voulez-vous dire par "lancer des exceptions où elles devraient fonctionner pour vous"?
CorsiKa

@corsiKa, l'exemple qui me vient à l'esprit sont les méthodes de recherche dans une liste ou une structure de données similaire. Est-ce qu'ils échouent quand ils ne trouvent rien ou retournent-ils une valeur nulle? Un autre exemple concerne les méthodes waitUntil qui échouent lors de l'expiration du délai plutôt que de renvoyer un booléen faux.
om
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.