Je peux voir plusieurs articles où l'importance de la gestion des exceptions à l'emplacement central ou à la limite du processus a été soulignée comme une bonne pratique plutôt que de joncher chaque bloc de code autour de try / catch. Je crois fermement que la plupart d'entre nous en comprenons l'importance, mais je vois des gens qui se retrouvent toujours avec un modèle anti-capture-journalisation, principalement parce que pour faciliter le dépannage lors de toute exception, ils veulent enregistrer des informations plus spécifiques au contexte (exemple: paramètres de méthode passé) et la façon est d'enrouler la méthode autour de try / catch / log / rethrow.
public static bool DoOperation(int num1, int num2)
{
try
{
/* do some work with num1 and num2 */
}
catch (Exception ex)
{
logger.log("error occured while number 1 = {num1} and number 2 = {num2}");
throw;
}
}
Existe-t-il un bon moyen d'y parvenir tout en conservant les bonnes pratiques de gestion des exceptions? J'ai entendu parler du cadre AOP comme PostSharp pour cela, mais j'aimerais savoir s'il y a un inconvénient ou un coût de performance majeur associé à ces cadres AOP.
Merci!
la source
Réponses:
Le problème n'est pas le bloc de capture local, le problème est le journal et la relance . Gérez l'exception ou encapsulez-la avec une nouvelle exception qui ajoute un contexte supplémentaire et lève cela. Sinon, vous rencontrerez plusieurs entrées de journal en double pour la même exception.
L'idée ici est d' améliorer la capacité de débogage de votre application.
Exemple # 1: manipulez-le
Si vous gérez l'exception, vous pouvez facilement rétrograder l'importance de l'entrée du journal des exceptions et il n'y a aucune raison de percoler cette exception dans la chaîne. C'est déjà réglé.
La gestion d'une exception peut consister à informer les utilisateurs qu'un problème s'est produit, à consigner l'événement ou simplement à l'ignorer.
REMARQUE: si vous ignorez intentionnellement une exception, je recommande de fournir un commentaire dans la clause catch vide qui indique clairement pourquoi. Cela permet aux futurs responsables de savoir que ce n'était pas une erreur ou une programmation paresseuse. Exemple:
Exemple # 2: ajouter du contexte supplémentaire et lancer
L'ajout de contexte supplémentaire (comme le numéro de ligne et le nom de fichier dans le code d'analyse) peut aider à améliorer la capacité de débogage des fichiers d'entrée - en supposant que le problème existe. Ceci est une sorte de cas spécial, donc reconditionner une exception dans une "ApplicationException" juste pour renommer cela ne vous aide pas à déboguer. Assurez-vous d'ajouter des informations supplémentaires.
Exemple # 3: ne faites rien à l'exception
Dans ce dernier cas, vous autorisez simplement l'exception à quitter sans la toucher. Le gestionnaire d'exceptions de la couche la plus externe peut gérer la journalisation. La
finally
clause est utilisée pour s'assurer que toutes les ressources nécessaires à votre méthode sont nettoyées, mais ce n'est pas l'endroit pour consigner que l'exception a été levée.la source
Je ne crois pas que les captures locales soient un anti-modèle, en fait, si je me souviens bien, elles sont en fait appliquées en Java!
Ce qui est essentiel pour moi lors de la mise en œuvre de la gestion des erreurs, c'est la stratégie globale. Vous voudrez peut-être un filtre qui capture toutes les exceptions à la limite du service, vous pouvez les intercepter manuellement - les deux sont très bien tant qu'il existe une stratégie globale, qui tombera dans les normes de codage de vos équipes.
Personnellement, j'aime détecter les erreurs dans une fonction lorsque je peux effectuer l'une des opérations suivantes:
Si ce n'est pas un de ces cas, je n'ajoute pas de try / catch local. Si c'est le cas, selon le scénario, je peux gérer l'exception (par exemple une méthode TryX qui retourne un faux) ou relancer afin que l'exception soit gérée par la stratégie globale.
Par exemple:
Ou un exemple de retour:
Ensuite, vous attrapez l'erreur en haut de la pile et présentez un bon message convivial à l'utilisateur.
Quelle que soit l'approche que vous adoptez, il est toujours utile de créer des tests unitaires pour ces scénarios afin de vous assurer que la fonctionnalité ne change pas et ne perturbe pas le flux du projet à une date ultérieure.
Vous n'avez pas mentionné la langue dans laquelle vous travaillez, mais vous êtes un développeur .NET et vous l'avez vu trop de fois pour ne pas le mentionner.
N'ÉCRIS PAS:
Utilisation:
Le premier réinitialise la trace de la pile et rend votre capture de niveau supérieur totalement inutile!
TLDR
La capture locale n'est pas un anti-modèle, elle peut souvent faire partie d'une conception et peut aider à ajouter un contexte supplémentaire à l'erreur.
la source
Cela dépend beaucoup de la langue. Par exemple, C ++ n'offre pas de traces de pile dans les messages d'erreur d'exception, il peut donc être utile de suivre l'exception par le biais de captures-journaux-relances fréquentes. En revanche, Java et les langages similaires offrent de très bonnes traces de pile, bien que le format de ces traces de pile ne soit pas très configurable. La capture et la réexception d'exceptions dans ces langages sont totalement inutiles, sauf si vous pouvez vraiment ajouter un contexte important (par exemple, la connexion d'une exception SQL de bas niveau avec le contexte d'une opération de logique métier).
Toute stratégie de gestion des erreurs implémentée par réflexion est presque nécessairement moins efficace que les fonctionnalités intégrées au langage. En outre, la journalisation omniprésente a des coûts inévitables de performances. Vous devez donc équilibrer le flux d'informations que vous obtenez par rapport aux autres exigences de ce logiciel. Cela dit, les solutions comme PostSharp qui sont construites sur une instrumentation au niveau du compilateur feront généralement beaucoup mieux que la réflexion au moment de l'exécution.
Personnellement, je pense que tout enregistrer n'est pas utile, car il comprend des tonnes d'informations non pertinentes. Je suis donc sceptique vis-à-vis des solutions automatisées. Étant donné un bon cadre de journalisation, il pourrait être suffisant d'avoir une directive de codage convenue qui discute du type d'informations que vous souhaitez enregistrer et de la façon dont ces informations doivent être formatées. Vous pouvez ensuite ajouter la journalisation là où cela est important.
La connexion à la logique métier est beaucoup plus importante que la connexion aux fonctions utilitaires. De plus, la collecte de traces de pile de rapports de plantage réels (qui ne nécessite que la journalisation au niveau supérieur d'un processus) vous permet de localiser les zones du code où la journalisation aurait le plus de valeur.
la source
Quand je vois
try/catch/log
dans chaque méthode, cela soulève des inquiétudes que les développeurs n'avaient aucune idée de ce qui pourrait ou ne pourrait pas se produire dans leur application, ont supposé le pire et ont tout enregistré de manière préventive partout en raison de tous les bogues qu'ils attendaient.C'est un symptôme que les tests unitaires et d'intégration sont insuffisants et que les développeurs sont habitués à parcourir beaucoup de code dans le débogueur et espèrent que beaucoup de journalisation leur permettront de déployer du code bogué dans un environnement de test et de trouver les problèmes en regardant les journaux.
Le code qui lève des exceptions peut être plus utile que le code redondant qui intercepte et enregistre les exceptions. Si vous lancez une exception avec un message significatif lorsqu'une méthode reçoit un argument inattendu (et l'enregistrez à la limite du service), il est beaucoup plus utile que de consigner immédiatement l'exception levée comme effet secondaire de l'argument non valide et d'avoir à deviner ce qui l'a provoqué .
Les nuls en sont un exemple. Si vous obtenez une valeur en tant qu'argument ou le résultat d'un appel de méthode et qu'elle ne devrait pas être nulle, lancez l'exception alors. Ne vous contentez pas de consigner les
NullReferenceException
cinq lignes générées par la suite en raison de la valeur nulle. Quoi qu'il en soit, vous obtenez une exception, mais l'un vous dit quelque chose tandis que l'autre vous fait rechercher quelque chose.Comme d'autres l'ont dit, il est préférable de consigner les exceptions à la limite du service, ou chaque fois qu'une exception n'est pas renvoyée car elle est gérée avec élégance. La différence la plus importante est entre quelque chose et rien. Si vos exceptions sont enregistrées dans un seul endroit facile d'accès, vous trouverez les informations dont vous avez besoin quand vous en avez besoin.
la source
Si vous devez enregistrer des informations de contexte qui ne figurent pas déjà dans l'exception, vous les encapsulez dans une nouvelle exception et fournissez l'exception d'origine sous la forme
InnerException
. De cette façon, vous avez toujours la trace de pile d'origine préservée. Donc:Le deuxième paramètre du
Exception
constructeur fournit une exception interne. Ensuite, vous pouvez consigner toutes les exceptions en un seul endroit, et vous obtenez toujours la trace complète de la pile et les informations contextuelles dans la même entrée de journal.Vous voudrez peut-être utiliser une classe d'exception personnalisée, mais le point est le même.
try / catch / log / rethrow est un gâchis car cela conduira à des journaux confus - par exemple, si une exception différente se produit dans un autre thread entre la journalisation des informations contextuelles et la journalisation de l'exception réelle dans le gestionnaire de niveau supérieur? try / catch / throw est bien si la nouvelle exception ajoute des informations à l'original.
la source
L'exception elle-même devrait fournir toutes les informations nécessaires à une bonne journalisation, y compris le message, le code d'erreur, etc. Par conséquent, il ne devrait pas être nécessaire d'attraper une exception uniquement pour la relancer ou lever une exception différente.
Souvent, vous verrez le modèle de plusieurs exceptions interceptées et renvoyées comme une exception commune, comme intercepter une DatabaseConnectionException, une InvalidQueryException et une InvalidSQLParameterException et renvoyer une DatabaseException. Bien qu'à cela, je dirais que toutes ces exceptions spécifiques devraient dériver de DatabaseException en premier lieu, donc une nouvelle tentative n'est pas nécessaire.
Vous constaterez que la suppression des clauses try catch inutiles (même celles à des fins de journalisation uniquement) rendra le travail plus facile, pas plus difficile. Seuls les emplacements de votre programme qui gèrent l'exception doivent consigner l'exception et, en cas d'échec, un gestionnaire d'exceptions à l'échelle du programme pour une dernière tentative de consignation de l'exception avant de quitter le programme en douceur. L'exception doit avoir une trace de pile complète indiquant le point exact où l'exception a été levée, il n'est donc souvent pas nécessaire de fournir une journalisation «contextuelle».
Cela dit, l'AOP peut être une solution miracle pour vous, bien qu'elle entraîne généralement un léger ralentissement global. Je vous encourage à supprimer à la place les clauses try catch inutiles où rien de valeur n'est ajouté.
la source
try { tester.test(); } catch (NullPointerException e) { logger.error("Variable tester was null!"); }
. La trace de la pile est suffisante dans la plupart des cas, mais à défaut, le type d'erreur est généralement adéquat.