web-dev-qa-db-fra.com

Meilleures pratiques pour la gestion des exceptions dans Java ou C #

Je suis coincé à décider comment gérer les exceptions dans mon application.

Beaucoup si mes problèmes avec les exceptions proviennent de 1) l'accès aux données via un service distant ou 2) la désérialisation d'un objet JSON. Malheureusement, je ne peux garantir le succès de l'une ou l'autre de ces tâches (coupure de la connexion réseau, objet JSON mal formé qui est hors de mon contrôle).

Par conséquent, si je rencontre une exception, je la rattrape simplement dans la fonction et renvoie FAUX à l'appelant. Ma logique est que tout l'appelant se soucie vraiment de savoir si la tâche a réussi, pas pourquoi elle n'a pas réussi.

Voici un exemple de code (en Java) d'une méthode typique)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

Je pense que cette approche est très bien, mais je suis vraiment curieux de savoir quelles sont les meilleures pratiques pour gérer les exceptions (devrais-je vraiment faire bouillir une exception tout le long d'une pile d'appels?).

En résumé des questions clés:

  1. Est-il correct de simplement attraper des exceptions mais pas de les faire bouillonner ou d'aviser officiellement le système (soit via un journal ou une notification à l'utilisateur)?
  2. Quelles sont les meilleures pratiques pour les exceptions qui ne nécessitent pas tout ce qui nécessite un bloc try/catch?

Suivi/Édition

Merci pour tous les commentaires, trouvé d'excellentes sources sur la gestion des exceptions en ligne:

Il semble que la gestion des exceptions soit l'une de ces choses qui varient en fonction du contexte. Mais surtout, il faut être cohérent dans la façon dont ils gèrent les exceptions au sein d'un système.

De plus, faites attention à la pourriture de code via des tentatives/captures excessives ou ne pas donner à une exception son respect (une exception avertit le système, quoi d'autre doit être averti?).

En outre, c'est un joli commentaire de choix de m3rLinEz .

J'ai tendance à être d'accord avec Anders Hejlsberg et vous que la plupart des appelants ne se soucient que si l'opération réussit ou non.

À partir de ce commentaire, il soulève quelques questions à considérer lors du traitement des exceptions:

  • À quoi sert cette exception?
  • Comment est-il sensé de le gérer?
  • L'appelant se soucie-t-il vraiment de l'exception ou se soucie-t-il simplement de la réussite de l'appel?
  • Forcer un appelant à gérer une exception potentielle est-il gracieux?
  • Êtes-vous respectueux des idoles de la langue?
    • Avez-vous vraiment besoin de renvoyer un indicateur de réussite comme booléen? Renvoyer un booléen (ou un int) est plus un état d'esprit C qu'un Java (dans Java vous géreriez juste l'exception)).
    • Suivez les constructions de gestion des erreurs associées au langage :)!
117
AtariPete

Il me semble étrange que vous souhaitiez intercepter des exceptions et les transformer en codes d'erreur. Pourquoi pensez-vous que l'appelant préférerait les codes d'erreur aux exceptions lorsque cette dernière est la valeur par défaut dans les deux Java et C #?

Quant à vos questions:

  1. Vous ne devez intercepter que les exceptions que vous pouvez réellement gérer. Attraper des exceptions n'est pas la bonne chose à faire dans la plupart des cas. Il existe quelques exceptions (par exemple, les exceptions de journalisation et de marshalling entre les threads), mais même dans ces cas, vous devez généralement annuler les exceptions.
  2. Vous ne devriez certainement pas avoir beaucoup d'instructions try/catch dans votre code. Encore une fois, l'idée est de ne détecter que les exceptions que vous pouvez gérer. Vous pouvez inclure un gestionnaire d'exceptions le plus haut pour transformer toutes les exceptions non gérées en quelque chose d'utile pour l'utilisateur final, mais sinon vous ne devriez pas essayer d'attraper chaque exception à chaque endroit possible.
61
Brian Rasmussen

Cela dépend de l'application et de la situation. Si vous créez un composant de bibliothèque, vous devez faire des bulles, bien qu'elles doivent être encapsulées pour être contextuelles avec votre composant. Par exemple, si vous créez une base de données Xml et supposons que vous utilisez le système de fichiers pour stocker vos données et que vous utilisez les autorisations du système de fichiers pour sécuriser les données. Vous ne voudriez pas faire exploser une exception FileIOAccessDenied car cela fuit votre implémentation. Au lieu de cela, vous envelopperiez l'exception et lèveriez une erreur AccessDenied. Cela est particulièrement vrai si vous distribuez le composant à des tiers.

Quant à savoir si c'est bien d'avaler des exceptions. Cela dépend de votre système. Si votre application peut gérer les cas d'échec et qu'il n'y a aucun avantage à informer l'utilisateur de la raison de l'échec, allez-y, bien que je recommande fortement que votre journal enregistre l'échec. J'ai toujours trouvé frustrant d'être appelé pour aider à résoudre un problème et découvrir qu'ils avalaient l'exception (ou le remplaçaient et en jetaient un nouveau à la place sans définir l'exception interne).

En général, j'utilise les règles suivantes:

  1. Dans mes composants et bibliothèques, je n'attrape une exception que si j'ai l'intention de le gérer ou de faire quelque chose en fonction de celui-ci. Ou si je veux fournir des informations contextuelles supplémentaires dans une exception.
  2. J'utilise une prise d'essai générale au point d'entrée de l'application, ou au plus haut niveau possible. Si une exception arrive ici, je l'enregistre et la laisse échouer. Idéalement, les exceptions ne devraient jamais arriver ici.

Je trouve que le code suivant est une odeur:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

Un code comme celui-ci ne sert à rien et ne doit pas être inclus.

25
JoshBerke

Je voudrais recommander une autre bonne source sur le sujet. Il s'agit d'une interview avec les inventeurs de C # et Java, Anders Hejlsberg et James Gosling respectivement, sur le sujet de l'exception vérifiée de Java.

Échec et exceptions

Il existe également d'excellentes ressources au bas de la page.

J'ai tendance à être d'accord avec Anders Hejlsberg et vous que la plupart des appelants ne se soucient que si l'opération réussit ou non.

Bill Venners : Vous avez mentionné des problèmes d'évolutivité et de version en ce qui concerne les exceptions vérifiées. Pourriez-vous préciser ce que vous entendez par ces deux questions?

Anders Hejlsberg : Commençons par le versioning, car les problèmes sont assez faciles à voir. Disons que je crée une méthode foo qui déclare qu'elle lève les exceptions A, B et C. Dans la version deux de foo, je veux ajouter un tas de fonctionnalités, et maintenant foo peut lever l'exception D.C'est un changement de rupture pour moi de ajoutez D à la clause throws de cette méthode, car l'appelant existant de cette méthode ne gérera certainement pas cette exception.

L'ajout d'une nouvelle exception à une clause throws dans une nouvelle version rompt le code client. C'est comme ajouter une méthode à une interface. Une fois que vous avez publié une interface, elle est à toutes fins pratiques immuable, car toute implémentation de celle-ci peut avoir les méthodes que vous souhaitez ajouter dans la prochaine version. Vous devez donc créer une nouvelle interface à la place. De même, avec les exceptions, vous devrez soit créer une toute nouvelle méthode appelée foo2 qui lève plus d'exceptions, soit intercepter l'exception D dans le nouveau foo et transformer le D en A, B ou C.

Bill Venners : Mais ne cassez-vous pas leur code dans ce cas de toute façon, même dans une langue sans exceptions vérifiées? Si la nouvelle version de foo va lever une nouvelle exception que les clients devraient penser à gérer, leur code n'est-il pas cassé simplement par le fait qu'ils ne s'attendaient pas à cette exception lorsqu'ils ont écrit le code?

Anders Hejlsberg : Non, parce que dans de nombreux cas, les gens s'en moquent. Ils ne géreront aucune de ces exceptions. Il y a un gestionnaire d'exceptions de niveau inférieur autour de leur boucle de message. Ce gestionnaire va simplement ouvrir une boîte de dialogue qui indique ce qui s'est mal passé et continuer. Les programmeurs protègent leur code en écrivant des essais finaux partout, donc ils reculeront correctement si une exception se produit, mais ils ne sont pas réellement intéressés par la gestion des exceptions.

La clause throws, du moins la façon dont elle est implémentée en Java, ne vous oblige pas nécessairement à gérer les exceptions, mais si vous ne les gérez pas, elle vous oblige à reconnaître précisément les exceptions qui peuvent passer. Cela vous oblige à intercepter les exceptions déclarées ou à les placer dans votre propre clause throws. Pour contourner cette exigence, les gens font des choses ridicules. Par exemple, ils décorent chaque méthode avec "jette l'exception". Cela défait complètement la fonctionnalité, et vous venez de faire écrire au programmeur plus de gobbledy gunk. Cela n'aide personne.

EDIT: Ajout de plus de détails sur la converstaion

9
Gant

Les exceptions vérifiées sont une question controversée en général, et en Java en particulier (plus tard, j'essaierai de trouver des exemples pour ceux qui sont pour et contre)).

En règle générale, la gestion des exceptions devrait être quelque chose autour de ces lignes directrices, sans ordre particulier:

  • Pour des raisons de maintenabilité, enregistrez toujours les exceptions afin que lorsque vous commencez à voir des bogues, le journal vous aide à vous indiquer où votre bogue a probablement commencé. Ne laissez jamais printStackTrace() ou autre chose du genre, il y a de fortes chances qu'un de vos utilisateurs obtienne éventuellement une de ces traces de pile, et ait exactement zéro connaissance quant à ce qu'il faut en faire.
  • Attrapez les exceptions que vous pouvez gérer, et seulement celles-ci, et gérez-les, ne les jetez pas simplement dans la pile.
  • Attrapez toujours une classe d'exception spécifique et, en règle générale, vous ne devez jamais intercepter le type Exception, vous êtes très susceptible d'avaler des exceptions autrement importantes.
  • Ne jamais (jamais) attraper Errors !!, ce qui signifie: Ne jamais attraper Throwables car Errors sont des sous-classes de cette dernière . Errors sont des problèmes que vous ne pourrez probablement jamais gérer (par exemple OutOfMemory, ou d'autres problèmes JVM)

Concernant votre cas spécifique, assurez-vous que tout client appelant votre méthode recevra la valeur de retour appropriée. Si quelque chose échoue, une méthode à retour booléen peut retourner false, mais assurez-vous que les emplacements que vous appelez cette méthode sont capables de gérer cela.

8
Yuval Adam

Vous ne devez intercepter que les exceptions que vous pouvez gérer. Par exemple, si vous avez affaire à la lecture sur un réseau et que la connexion expire et que vous obtenez une exception, vous pouvez réessayer. Cependant, si vous lisez sur un réseau et obtenez une exception IndexOutOfBounds, vous ne pouvez vraiment pas gérer cela parce que vous ne savez pas (eh bien, dans ce cas, vous ne savez pas) ce qui l'a causé. Si vous allez retourner false ou -1 ou null, assurez-vous que c'est pour des exceptions spécifiques. Je ne veux pas qu'une bibliothèque que j'utilise retourne un faux sur une lecture réseau lorsque l'exception levée est que le tas est en mémoire insuffisante.

5
Malfist

Les exceptions sont des erreurs qui ne font pas partie de l'exécution normale d'un programme. Selon ce que fait votre programme et ses utilisations (c'est-à-dire un traitement de texte par rapport à un moniteur cardiaque), vous voudrez faire des choses différentes lorsque vous rencontrez une exception. J'ai travaillé avec du code qui utilise des exceptions dans le cadre d'une exécution normale et c'est définitivement une odeur de code.

Ex.

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

Ce code me fait barf. OMI, vous ne devez pas récupérer d'exceptions, sauf si c'est un programme critique. Si vous lancez des exceptions, alors de mauvaises choses se produisent.

3
Holograham

Tout ce qui précède semble raisonnable, et souvent votre lieu de travail peut avoir une politique. Chez nous, nous avons défini des types d'exception: SystemException (non coché) et ApplicationException (coché).

Nous avons convenu que les SystemExceptions ne seront probablement pas récupérables et seront traités une fois en haut. Pour fournir un contexte supplémentaire, nos SystemExceptions sont étendus pour indiquer où ils se sont produits, par exemple RepositoryException, ServiceEception, etc.

ApplicationExceptions pourrait avoir une signification commerciale comme InsufficientFundsException et devrait être gérée par le code client.

Sans un exemple concret, il est difficile de commenter votre implémentation, mais je n'utiliserais jamais de codes retour, c'est un problème de maintenance. Vous pouvez avaler une exception, mais vous devez décider pourquoi et toujours consigner l'événement et le stacktrace. Enfin, comme votre méthode n'a pas d'autre traitement, elle est assez redondante (sauf pour l'encapsulation?), Donc doactualStuffOnObject(p_jsonObject); pourrait retourner un booléen!

2
romski

Si vous allez utiliser le modèle de code dans votre exemple, appelez-le TryDoSomething et interceptez uniquement des exceptions spécifiques.

Aussi pensez à utiliser un filtre d'exception lors de la journalisation des exceptions à des fins de diagnostic. VB prend en charge le langage pour les filtres d'exception. Le lien vers le blog de Greggm a une implémentation qui peut être utilisée à partir de C #. Les filtres d'exception ont de meilleures propriétés pour le débogage sur la capture et le retour. Plus précisément, vous pouvez enregistrer le problème dans le filtre et laissez l'exception continuer à se propager. Cette méthode permet à un attaché un débogueur JIT (Just in Time) d'avoir la pile d'origine complète. Une nouvelle opération de suppression coupe la pile au point de récupération.

Les cas où TryXXXX a un sens sont lorsque vous encapsulez une fonction tierce qui jette dans des cas qui ne sont pas vraiment exceptionnels, ou sont simplement difficiles à tester sans appeler la fonction. Un exemple serait quelque chose comme:

// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse); 

bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
     try
     {
         parsedInt = ParseHexidecimal(numberToParse);
         return true;
     }
     catch(NumberNotHexidecimalException ex)
     {
         parsedInt = 0;
         return false;
     }
     catch(Exception ex)
     {
         // Implement the error policy for unexpected exceptions:
         // log a callstack, assert if a debugger is attached etc.
         LogRetailAssert(ex);
         // rethrow the exception
         // The downside is that a JIT debugger will have the next
         // line as the place that threw the exception, rather than
         // the original location further down the stack.
         throw;
         // A better practice is to use an exception filter here.
         // see the link to Exception Filter Inject above
         // http://code.msdn.Microsoft.com/ExceptionFilterInjct
     }
}

Que vous utilisiez un modèle comme TryXXX ou non est plus une question de style. La question de rattraper toutes les exceptions et de les avaler n'est pas une question de style. Assurez-vous que les exceptions inattendues sont autorisées à se propager!

1
Steve Steiner

Je suggère de prendre vos repères de la bibliothèque standard pour la langue que vous utilisez. Je ne peux pas parler pour C #, mais regardons Java.

Par exemple, Java.lang.reflect.Array a une méthode statique set:

static void set(Object array, int index, Object value);

La voie C serait

static int set(Object array, int index, Object value);

... la valeur de retour étant un indicateur de réussite. Mais vous n'êtes plus dans le monde C.

Une fois que vous avez accepté les exceptions, vous devriez constater que cela rend votre code plus simple et plus clair, en éloignant votre code de gestion des erreurs de votre logique principale. Essayez d'avoir beaucoup d'instructions dans un seul bloc try.

Comme d'autres l'ont noté - vous devez être aussi précis que possible dans le type d'exception que vous attrapez.

1
slim

Après réflexion et lecture de votre code, il me semble que vous renoncez simplement à l'exception en tant que booléen. Vous pouvez simplement laisser la méthode passer cette exception (vous n'avez même pas besoin de l'attraper) et la gérer dans l'appelant, car c'est l'endroit où cela compte. Si l'exception oblige l'appelant à réessayer cette fonction, l'appelant doit être celui qui intercepte l'exception.

Il peut parfois arriver que l'exception que vous rencontrez n'a pas de sens pour l'appelant (c'est-à-dire qu'il s'agit d'une exception de réseau), auquel cas vous devez l'encapsuler dans une exception spécifique au domaine.

Si, d'autre part, l'exception signale une erreur irrécupérable dans votre programme (c'est-à-dire que le résultat final de cette exception sera la fin du programme), j'aime personnellement rendre cela explicite en l'attrapant et en lançant une exception d'exécution.

1
wds

les blocs try/catch forment un deuxième ensemble de logique intégré au premier ensemble (principal), en tant que tels, ils sont un excellent moyen de marteler du code spaghetti illisible et difficile à déboguer.

Pourtant, utilisés raisonnablement, ils font des merveilles en termes de lisibilité, mais vous devez simplement suivre deux règles simples:

  • utilisez-les (avec parcimonie) au bas niveau pour détecter les problèmes de gestion de bibliothèque et les retransmettre dans le flux logique principal. La plupart des erreurs que nous voulons gérer devraient provenir du code lui-même, en tant que partie des données elles-mêmes. Pourquoi faire des conditions spéciales, si les données retournées ne sont pas spéciales?

  • utiliser un seul gros gestionnaire au niveau supérieur pour gérer tout ou partie des conditions étranges survenant dans le code qui ne sont pas détectées à un niveau bas. Faites quelque chose d'utile avec les erreurs (journaux, redémarrages, récupérations, etc.).

Mis à part ces deux types de gestion des erreurs, tout le reste du code au milieu doit être libre et exempt de code try/catch et d'objets d'erreur. De cette façon, cela fonctionne simplement et comme prévu, peu importe où vous l'utilisez ou ce que vous en faites.

Paul.

0
Paul W Homer

Quelques excellentes réponses ici. Je voudrais ajouter que si vous vous retrouvez avec quelque chose comme vous avez posté, imprimez au moins plus que la trace de la pile. Dites ce que vous faisiez à l'époque, et Ex.getMessage (), pour donner au développeur une chance de se battre.

0
dj_segfault

Ma stratégie:

Si la fonction d'origine a retourné void je la change pour retourner bool. Si une exception/erreur s'est produite, retournez faux, si tout va bien, retournez vrai.

Si la fonction doit retourner quelque chose, quand une exception/erreur s'est produite, retournez null, sinon l'élément retournable.

Au lieu de bool un string pourrait être retourné contenant la description de l'erreur.

Dans tous les cas, avant de retourner quoi que ce soit, enregistrez l'erreur.

0
Germstorm

Je suis peut-être un peu en retard avec la réponse, mais la gestion des erreurs est quelque chose que nous pouvons toujours changer et évoluer avec le temps. Si vous voulez en savoir plus sur ce sujet, j'ai écrit un article à ce sujet dans mon nouveau blog. http://taoofdevelopment.wordpress.com

Codage heureux.

0
GRGodoi

Si vous allez intercepter une exception et renvoyer false, cela devrait être une exception très spécifique. Vous ne faites pas ça, vous les attrapez tous et vous retournez faux. Si je reçois une exception MyCarIsOnFireException, je veux le savoir tout de suite! Le reste des exceptions dont je ne me soucie peut-être pas. Donc, vous devriez avoir une pile de gestionnaires d'exceptions qui disent "whoa whoa quelque chose ne va pas ici" pour certaines exceptions (rethrow, ou intercepter et relancer une nouvelle exception qui explique mieux ce qui s'est passé) et renvoyer simplement false pour les autres.

Si c'est un produit que vous lancerez, vous devriez enregistrer ces exceptions quelque part, cela vous aidera à régler les choses à l'avenir.

Edit: Quant à la question de tout emballer dans un try/catch, je pense que la réponse est oui. Les exceptions doivent être si rares dans votre code que le code du bloc catch s'exécute si rarement qu'il n'atteint pas les performances du tout. Une exception devrait être un état où votre machine d'état est tombée en panne et ne sait pas quoi faire. Au moins, renvoyez une exception qui explique ce qui se passait à l'époque et qui contient l'exception interceptée. "Exception dans la méthode doSomeStuff ()" n'est pas très utile pour quiconque doit comprendre pourquoi il s'est cassé pendant que vous êtes en vacances (ou à un nouvel emploi).

0
jcollum