Question

Je ne suis pas en mesure de décider comment gérer les exceptions dans mon application.

Si les problèmes avec les exceptions viennent en grande partie de 1) accéder aux données via un service distant ou 2) désérialiser un objet JSON. Malheureusement, je ne peux pas garantir le succès de l’une ou l’autre de ces tâches (couper la connexion réseau, objet JSON mal formé qui n’est pas sous mon contrôle).

Par conséquent, si je rencontre une exception, je la capture simplement dans la fonction et renvoie FALSE à l'appelant. Ma logique est que l’appelant se préoccupe vraiment de savoir si la tâche a abouti, et non pourquoi elle a échoué.

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 convient, mais je suis vraiment curieux de savoir quelles sont les meilleures pratiques en matière de gestion des exceptions (devrais-je réellement masquer une exception jusqu'à la pile d'appels?).

En résumé des questions clés:

  1. Peut-on simplement intercepter les exceptions, mais ne pas les masquer ou notifier formellement le système (via un journal ou une notification à l'utilisateur)?
  2. Quelles sont les meilleures pratiques pour les exceptions qui ne génèrent pas tout ce qui nécessite un bloc try / catch?

Suivre / Modifier

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

Il semble que la gestion des exceptions soit l’une des 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, surveillez le code-rot via des tentatives excessives d'essais / captures ou ne donnant pas le respect à une exception (une exception est d'avertir le système, quoi d'autre doit être averti?).

C’est également un commentaire très intéressant de m3rLinEz .

  

Je suis plutôt d’accord avec Anders Hejlsberg et vous-même pour dire que la plupart des appelants   attention si l'opération est réussie ou non.

Ce commentaire soulève quelques questions à prendre en compte lors de la gestion des exceptions:

  • Quel est le but de cette exception?
  • Comment est-il judicieux de le gérer?
  • L'appelant s'intéresse-t-il vraiment à l'exception ou se soucie-t-il uniquement du succès de l'appel?
  • Forcer un appelant à gérer une exception potentielle est-il gracieux?
  • Êtes-vous respectueux des idoms de la langue?
    • Avez-vous vraiment besoin de retourner un drapeau de succès comme un booléen? Le renvoi d'un booléen (ou d'un entier) est plus un état d'esprit en C qu'un état Java (en Java, vous ne traiteriez que l'exception).
    • Suivez les constructions de gestion des erreurs associées au langage :)!
Était-ce utile?

La solution

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 en Java et en C #?

Quant à vos questions:

  1. Vous ne devez capturer que les exceptions que vous pouvez réellement gérer. Juste 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 discussions), mais même dans ces cas, vous devriez généralement renvoyer les exceptions.
  2. Vous ne devriez vraiment pas avoir beaucoup de déclarations try / catch dans votre code. Encore une fois, l’idée est de ne capturer que les exceptions que vous pouvez gérer. Vous pouvez inclure un gestionnaire d'exception de premier ordre pour transformer tout traitement non géré. exceptions en quelque chose d'un peu utile pour l'utilisateur final, mais sinon vous ne devriez pas essayer d’attraper chaque exception dans chaque lieu possible.

Autres conseils

Cela dépend de l'application et de la situation. Si vous construisez un composant de bibliothèque, vous devez éliminer les exceptions, même si elles doivent être enveloppées pour être contextuelles avec votre composant. Par exemple, si vous construisez 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 de bulles avec une exception FileIOAccessDenied car cela fuit votre implémentation. Au lieu de cela, vous devriez emballer l'exception et émettre une erreur AccessDenied. Cela est particulièrement vrai si vous distribuez le composant à des tiers.

Quant à savoir s’il est acceptable d’avaler des exceptions. Cela dépend de votre système. Si votre application peut gérer les cas d'échec et qu'il est inutile d'avertir l'utilisateur de la cause de son échec, continuez, mais je vous recommande vivement de consigner cet é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 la remplaçait et en jetait une nouvelle à la place sans définir l'exception interne).

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

  1. Dans mes composants & amp; bibliothèques Je n’obtiens une exception que si j’ai l’intention de la gérer ou de faire quelque chose à partir de celle-ci. Ou si je souhaite fournir des informations contextuelles supplémentaires dans une exception.
  2. J'utilise une capture d'essai générale au point d'entrée de l'application, ou le niveau le plus élevé possible. Si une exception survient, il suffit de la consigner et de la laisser é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 devrait pas être inclus.

Je voudrais recommander une autre bonne source sur le sujet. Il s’agit d’une interview avec les inventeurs de C # et Java, respectivement Anders Hejlsberg et James Gosling, 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.

Je suis plutôt d’accord avec Anders Hejlsberg et vous-même pour dire que la plupart des appelants ne se soucient que de la réussite de l’opération.

  

Bill Venners : vous avez mentionné   problèmes d'évolutivité et de gestion des versions   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   la gestion des versions, car les problèmes sont   assez facile à voir là-bas. Disons que je   créer une méthode foo qui la déclare   jette les exceptions A, B et C. Dans   version deux de foo, je veux ajouter un   tas de fonctionnalités, et maintenant foo pourrait   jeter l'exception D. C'est une rupture   changer pour moi d'ajouter D aux lancers   clause de cette méthode, parce que   appelant existant de cette méthode sera   presque certainement pas gérer ça   exception.

     

Ajouter une nouvelle exception à un lancer   clause dans une nouvelle version casse le client   code. C'est comme ajouter une méthode à un   interface. Après avoir publié un   interface, c'est pour tout pratique   immuable, parce que tout   la mise en œuvre pourrait avoir la   méthodes que vous souhaitez ajouter à la   prochaine version. Donc, vous devez créer   une nouvelle interface à la place. De même   avec des exceptions, vous auriez soit   pour créer une toute nouvelle méthode appelée   foo2 qui jette plus d'exceptions, ou   vous auriez à attraper l'exception D dans   le nouveau foo, et transformer le D en   un A, un B ou un C.

     

Bill Venners : Mais ne craignez-vous pas   leur code dans ce cas de toute façon, même   dans une langue sans check   exceptions? Si la nouvelle version de foo   va lancer une nouvelle exception qui   les clients devraient penser à la manipulation,   n'est pas leur code cassé juste par le   le fait qu'ils ne s'attendaient pas à ce que   exception quand ils ont écrit le code?

     

Anders Hejlsberg : Non, car dans beaucoup   des cas, les gens s'en moquent. Ils sont   ne va pas gérer l'un de ces   exceptions. Il y a un niveau inférieur   gestionnaire d'exception autour de leur message   boucle. Ce gestionnaire va juste   faire apparaître un dialogue qui dit ce qui s'est passé   mal et continuer. Les programmeurs   protéger leur code en écrivant essayer   est enfin partout, donc ils vont revenir   correctement si une exception se produit,   mais ils ne sont pas réellement intéressés par   gérer les exceptions.

     

La clause throws, du moins le chemin   il est implémenté en Java, ne le fait pas   oblige nécessairement à gérer le   exceptions, mais si vous ne gérez pas   eux, il vous oblige à reconnaître   précisément quelles exceptions pourraient passer   à travers. Il vous faut soit   attraper des exceptions déclarées ou les mettre   dans votre propre clause de jets. Travailler   autour de cette exigence, les gens font   des choses ridicules. Par exemple, ils   décorer chaque méthode avec, "jette   Exception. & Quot; Cela vient complètement   défait la fonctionnalité, et vous venez de faire   le programmeur écrit plus gobbledy   gunk. Cela n'aide personne.

EDIT: ajout de plus de détails sur la conversation

Les exceptions cochées sont un sujet controversé en général, et en Java en particulier (plus tard, je tenterai de trouver des exemples pour ceux qui sont pour et qui sont opposés).

En règle générale, la gestion des exceptions devrait correspondre à ces instructions, sans ordre particulier:

  • Par souci de maintenabilité, enregistrez toujours les exceptions afin que, lorsque vous commencez à voir des bogues, le journal vous aide à vous indiquer l'endroit où votre bogue a probablement commencé. Ne laissez jamais printStackTrace () ou un élément similaire, il est probable qu'un de vos utilisateurs obtiendra éventuellement l'une de ces traces de pile et disposera de connaissances nulles pour savoir quoi faire. avec elle.
  • Attrapez les exceptions que vous pouvez gérer et uniquement celles-ci, , et les gérez , ne les jetez pas dans la pile.
  • Attrapez toujours une classe d'exception spécifique et, en règle générale, vous ne devez jamais attraper le type Exception . Il est très probable que vous avaliez des exceptions autrement importantes.
  • Jamais attraper Erreur s !! , ce qui signifie: Jamais attraper Throwable s en tant que Les erreurs sont des sous-classes de cette dernière. Les erreurs sont des problèmes que vous ne pourrez probablement jamais jamais gérer (par exemple, OutOfMemory ou d'autres problèmes liés à la JVM)

En ce qui concerne 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 renvoyant un booléen peut renvoyer false, mais assurez-vous que les endroits où vous appelez cette méthode sont capables de le gérer.

Vous ne devriez intercepter que les exceptions que vous pouvez gérer. Par exemple, si vous êtes en train de lire sur un réseau et que la connexion a expiré 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 (bien, dans ce cas, vous ne saurez pas) en connaître la cause. Si vous retournez false ou -1 ou null, assurez-vous que ce soit pour des exceptions spécifiques. Je ne souhaite pas qu'une bibliothèque que j'utilise renvoie une valeur false sur une lecture réseau lorsque l'exception générée est que le tas est saturé.

Les exceptions sont des erreurs qui ne font pas partie de l'exécution normale du programme. En fonction de ce que votre programme fait et de ses utilisations (c'est-à-dire un traitement de texte par rapport à un moniteur cardiaque), vous voudrez faire différentes choses 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 vraiment une odeur de code.

Ex.

try
{
   sendMessage();

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

Ce code me fait chier. OMI vous ne devriez pas récupérer d'exceptions à moins que ce ne soit un programme critique. Si vous lancez des exceptions, de mauvaises choses se produisent.

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

Nous sommes convenus qu'il est peu probable que les SystemException puissent être récupérés et seront traités une fois en haut. Pour fournir un contexte supplémentaire, nos SystemException sont étendus pour indiquer où ils se sont produits, par exemple. exception de référentiel , exception au service , etc.

Les

ApplicationException peuvent avoir une signification commerciale comme InsufficientFundsException et doivent être gérés par le code client.

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

Après réflexion et examen de votre code, il me semble que vous ne faites que renvoyer l’exception sous forme de booléen. Vous pouvez simplement laisser la méthode passer cette exception (vous n'avez même pas à l'attraper) et vous en occuper dans l'appelant, car c'est l'endroit où cela compte. Si l’exception demande à l’appelant de réessayer cette fonction, c’est l’appelant qui intercepte l’exception.

Il peut arriver que l’exception que vous rencontrez n’ait pas de sens pour l’appelant (c’est-à-dire qu’il s’agit d’une exception réseau), auquel cas vous devriez l’envelopper dans une exception spécifique à un domaine.

Si, par contre, l'exception signale une erreur irrémédiable dans votre programme (c'est-à-dire que le résultat éventuel de cette exception sera la fin du programme), j'aime rendre cela explicite en l'interceptant et en lançant une exception d'exécution.

Si vous allez utiliser le modèle de code dans votre exemple, appelez-le TryDoSomething et n'attrapez que des exceptions spécifiques.

De plus, envisagez d'utiliser un Filtre d'exceptions lors de la consignation des exceptions à des fins de diagnostic. VB prend en charge la langue 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 de débogage sur la capture et le rediffusion. Vous pouvez notamment enregistrer le problème dans le filtre et laisser l’exception continuer à se propager. Cette méthode permet de joindre un débogueur JIT (Just in Time) pour avoir la pile originale complète. Un retranchage coupe la pile au point où elle a été renversée.

TryXXXX est judicieux dans certains cas, lorsque vous encapsulez une fonction tierce lancée dans des cas qui ne sont pas vraiment exceptionnels, ou qu'il est simplement difficile de 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 tel que TryXXX ou non, cela relève davantage d'une question de style. La question de capturer toutes les exceptions et de les avaler n'est pas un problème de style. Assurez-vous que les exceptions inattendues sont autorisées à se propager!

Je suggère de prendre vos repères dans 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);

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

Une fois que vous avez adopté les exceptions, vous devriez constater que cela simplifie et clarifie votre code en éloignant votre code de gestion des erreurs de votre logique fondamentale. Vise à avoir beaucoup d’énoncés dans un seul bloc try .

Comme d’autres l’ont fait remarquer, vous devriez être aussi précis que possible en ce qui concerne le type d’exception que vous détectez.

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

S'il s'agit d'un produit que vous allez lancer, vous devriez enregistrer ces exceptions quelque part, cela vous aidera à régler les problèmes à l'avenir.

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

Ma stratégie:

Si la fonction d'origine a renvoyé void , je le modifie pour renvoyer bool . Si une exception / une erreur se produit, renvoyez false , si tout va bien, renvoyez true .

Si la fonction doit retourner quelque chose, alors quand une exception / erreur se produit, retournez null , sinon l'élément retournable.

Au lieu de bool , une chaîne contenant la description de l'erreur peut être renvoyée.

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

Quelques excellentes réponses ici. J'aimerais ajouter que si vous vous retrouvez avec quelque chose comme vous l'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.

Les blocs try / catch forment un deuxième ensemble de logique intégré au premier (principal) ensemble. Ils constituent donc un excellent moyen de supprimer le code spaghetti illisible et difficile à déboguer.

Néanmoins, si vous les utilisez raisonnablement, leur lisibilité est excellente, mais vous devez simplement suivre deux règles simples:

  • utilisez-les (avec parcimonie) au bas niveau pour résoudre les problèmes de gestion de la bibliothèque et les rediffuser 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 intégrante des données. Pourquoi créer des conditions spéciales si les données renvoyées ne sont pas spéciales?

  • utilisez un seul gros gestionnaire au niveau supérieur pour gérer tout ou partie des conditions étranges apparaissant 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.).

Hormis ces deux types de traitement des erreurs, tout le reste du code situé au centre devrait ê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 faites avec.

Paul.

Je peux ê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 lire quelque chose de plus sur ce sujet, j'ai écrit un post dans mon nouveau blog à ce sujet. http://taoofdevelopment.wordpress.com

Joyeux codage.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top