Meilleures pratiques pour la gestion des exceptions en Java ou en C # [fermé]
-
03-07-2019 - |
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:
- 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)?
- 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:
- Meilleures pratiques pour la gestion des exceptions | O'Reilly Media
- Gestion des exceptions en matière de meilleures pratiques dans .NET
- Meilleures pratiques: Gestion des exceptions (cet article pointe maintenant la copie du fichier archive.org)
- des antécédents de gestion des exceptions
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 :)!
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:
- 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.
- 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:
- 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.
- 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.
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 attraperThrowable
s en tant queLes erreurs
sont des sous-classes de cette dernière. Leserreurs
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.
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.