Maison > Article > développement back-end > Meilleures pratiques pour la gestion des exceptions dans .NET (traduit)
Adresse originale : Cliquez pour ouvrir le lien
Cet article est traduit d'un article sur CodeProject Article, adresse d'origine.
Table des matières
Introduction
Soyez prêt au pire
Vérifiez à l'avance
Ne faites pas confiance aux données externes
Appareils de confiance : appareil photo, souris et clavier
"L'opération d'écriture" peut également échouer
Programmation sécurisée
Ne lancez pas "new Exception()"
Ne stockez pas les informations d'exception importantes dans la propriété Message
Chaque fil doit contenir un bloc try/catch
Après avoir intercepté l'exception, enregistrez-la
Ne vous contentez pas d'enregistrer la valeur de Exception.Message, vous devez également enregistrer Exception.ToString()
Pour capturer les exceptions spécifiques
N'abandonnez pas le lancement d'exceptions
Nettoyage le code doit être placé dans le bloc final
N'oubliez pas d'utiliser using
N'utilisez pas de valeurs de retour spéciales pour indiquer ce qui s'est passé dans la méthode Exceptions
N'utilisez pas la méthode "lancer une exception" pour indiquer que le la ressource n'existe pas
N'utilisez pas « lancer une exception » comme résultat de l'exécution de la fonction
Vous pouvez utiliser la méthode de « lancer une exception » pour souligner ce qui ne peut pas être ignoré Erreur
Ne pas effacer les informations de trace de la pile
La classe d'exception doit être marquée sérialisable
Utilisez "Throw Exception" au lieu de Debug.Assert
par Une classe d'exception contient au moins trois constructeurs
Ne réinventez pas la roue
VB.NET
Simuler l'instruction using en C#
Ne pas utiliser la gestion des exceptions non structurées (On Error goto) 🎜>Introduction
"Mon les logiciels ne se trompent jamais". Le croyez-vous ? Je suis presque certain que tout le monde criera que je suis un menteur. "Il est presque impossible qu'un logiciel soit exempt de bugs !"
En d'autres termes, ce que je veux dire, c'est que le logiciel est relativement stable.
Il est compréhensible qu'il y ait des bugs dans les logiciels. Mais s’il s’agit d’un bug récurrent et que vous ne pouvez pas le corriger rapidement parce qu’il n’y a pas assez d’informations, c’est impardonnable.
Pour mieux comprendre ce que j'ai dit ci-dessus, permettez-moi de vous donner un exemple : je vois souvent d'innombrables logiciels commerciaux donner ce message d'erreur lorsqu'ils rencontrent un disque dur insuffisant :
"Échec de la mise à jour des informations client, veuillez contacter l'administrateur système et réessayer."
En dehors de cela, aucune autre information n'est enregistrée. Déterminer la cause de cette erreur prend beaucoup de temps. Les programmeurs devront peut-être faire diverses suppositions avant de réellement trouver la cause du problème.
Notez que dans cet article, je parle principalement de la façon de mieux gérer les exceptions dans la programmation .NET. Je n'ai pas l'intention de discuter de la façon d'afficher un "message d'erreur" approprié car je pense que ce travail appartient à l'interface utilisateur. . développeurs, et cela dépend principalement du type d’interface utilisateur et de qui finira par utiliser le logiciel. Par exemple, le « message d'erreur » d'un éditeur de texte destiné aux utilisateurs ordinaires devrait être complètement différent de celui d'un framework de communication Socket, car les utilisateurs directs de ce dernier sont des programmeurs.
Soyez prêt au pire
Suivez quelques bases Les principes de conception peuvent rendre votre programme plus robuste et améliorer l'expérience utilisateur lorsque des erreurs se produisent. L'"amélioration de l'expérience utilisateur" dont je parle ici ne signifie pas que le formulaire d'invite d'erreur peut rendre les utilisateurs heureux, mais cela signifie que les erreurs qui se produisent n'endommageront pas les données d'origine ni ne feront planter l'ordinateur tout entier. Si votre programme rencontre une erreur de disque dur insuffisant, mais que le programme ne provoque aucun autre effet négatif (il provoque uniquement un message d'erreur et ne provoque pas d'autres problèmes, ndlr du traducteur), alors l'expérience utilisateur est améliorée.
Vérifiez à l'avance
Une vérification et une validation de type solides sont Un moyen puissant d’éviter les bugs. Plus tôt vous détecterez un problème, plus tôt vous pourrez le résoudre. Il n'est pas facile et assez ennuyeux de comprendre après quelques mois "Pourquoi y a-t-il des données CustomerID dans la colonne ProductID de la table InvoiceItems ?" Si vous utilisez une classe au lieu de types de base (tels que int, string) pour stocker les données client, le compilateur ne permettra pas que ce qui vient de se passer (faisant référence à la confusion CustomerID et ProductID, note du traducteur) se produise.
Ne faites pas confiance aux données externes
Données externes n'est pas fiable, nos logiciels doivent être rigoureusement vérifiés avant de les utiliser. Que ces données externes proviennent du registre, de la base de données, du disque dur, du socket ou de fichiers écrits par vous à l'aide du clavier, toutes ces données externes doivent être strictement vérifiées avant utilisation. Souvent, je vois des programmes qui font entièrement confiance au fichier de configuration, car les programmeurs qui développent ces programmes supposent toujours que personne ne modifiera le fichier de configuration ni ne le corrompra.
Appareils de confiance : appareil photo, souris et clavier
Lorsque vous devez utiliser des données externes, vous pouvez rencontrer les situations suivantes :
1) Autorisations de sécurité insuffisantes
2) Les données ne sont pas existe
3) Les données sont incomplètes
4) Les données sont complètes, mais le format est erroné
La situation ci-dessus peut se produire indépendamment du fait que la source de données soit une clé du registre, un fichier, un socket, une base de données, un service Web ou un port série. Toutes les données externes sont sujettes à défaillance.
L'opération d'écriture peut également échouer
Non disponible Une source de données fiable est également un entrepôt de données non fiable. Lorsque vous stockez des données, des situations similaires peuvent encore se produire :
1) Autorisations de sécurité insuffisantes
2) L'appareil n'existe pas
3) Pas assez d'espace
4) Une erreur physique s'est produite dans le périphérique de stockage
Ceci C'est pourquoi certains logiciels de compression créent un fichier temporaire pendant le travail puis le renomme une fois le travail terminé, au lieu de modifier directement le fichier source. La raison en est que si le disque dur est endommagé (ou si le logiciel est anormal), les données originales peuvent être perdues. (Le traducteur a rencontré cette situation. Il y a eu une panne de courant lors de la sauvegarde des données et l'ancienne sauvegarde originale a été endommagée. Note du traducteur)
Programmation sécurisée
Un de mes amis m'a dit : Un bon programmeur n'écrit jamais de mauvais code dans son programme. Je pense que c’est une condition nécessaire mais pas suffisante pour être un bon programmeur. Ci-dessous, j'ai compilé un "mauvais code" que vous pourriez écrire lors de la gestion des exceptions :
Ne lancez pas "new Exception() ”
S'il vous plaît, ne faites pas ça. L'exception est une classe d'exception très abstraite. La capture de ce type d'exception a généralement de nombreux effets négatifs. Normalement, nous devrions définir nos propres classes d'exceptions et faire la distinction entre les exceptions lancées par le système (framework) et les exceptions lancées par nous-mêmes.
Ne stockez pas les informations d'exception importantes dans la propriété Message
Les exceptions sont encapsulées dans les classes. Lorsque vous devez renvoyer des informations d'exception, veuillez stocker les informations dans certaines propriétés distinctes (pas dans la propriété Message), sinon il sera difficile pour les utilisateurs d'analyser les informations dont ils ont besoin à partir de la propriété Message. Par exemple, lorsque vous avez simplement besoin de corriger une faute d'orthographe, si vous écrivez le message d'erreur et d'autres contenus d'invite dans la propriété Message sous la forme d'une chaîne, comment les autres peuvent-ils simplement obtenir le message d'erreur qu'ils souhaitent ? Il est difficile d'imaginer combien d'efforts ils doivent fournir.
Chaque fil de discussion doit contenir un bloc try/catch
Généralement, la gestion des exceptions est placée dans un endroit relativement centralisé dans le programme. Chaque thread doit avoir un bloc try/catch, sinon vous manquerez certaines exceptions et provoquerez des problèmes difficiles à comprendre. Lorsqu'un programme démarre plusieurs threads pour gérer des tâches en arrière-plan, vous créez généralement un type pour stocker les résultats de l'exécution de chaque thread. A ce stade, n'oubliez pas d'ajouter un champ au type pour stocker les exceptions qui peuvent survenir dans chaque thread, sinon le thread principal ne connaîtra pas les exceptions des autres threads. Dans certaines situations de type « déclencher et oublier » (ce qui signifie que le thread principal ne se soucie plus de l'état d'exécution du thread après le démarrage du thread, ndlr), vous devrez peut-être copier la logique de gestion des exceptions du thread principal vers votre thread enfant. . aller.
Enregistrez l'exception après l'avoir détectée
Peu importe Peu importe la méthode utilisée par votre programme pour enregistrer les journaux : log4net, EIF, journal des événements, TraceListeners ou fichiers texte. L'important est le suivant : lorsque vous rencontrez une exception, vous devez la consigner quelque part. Mais veuillez ne vous connecter qu'une seule fois, sinon vous vous retrouverez avec un très gros fichier journal contenant de nombreuses informations répétées.
Ne vous contentez pas d'enregistrer la valeur de Exception.Message, vous devez également enregistrer Exception.ToString()
Lorsque nous parlons de journalisation, n'oubliez pas que nous devons enregistrer la valeur de Exception.ToString(), pas Exception.Message. Parce que Exception.ToString() contient des informations de « trace de pile », des informations d'exception internes et un message. Habituellement, ces informations sont très importantes et si vous enregistrez uniquement Exception.Message, vous ne verrez peut-être qu'une invite du type "La référence de l'objet ne pointe pas vers une instance dans le tas".
Pour détecter des exceptions spécifiques
Si vous le souhaitez interceptez les exceptions, veuillez essayer d'intercepter des exceptions spécifiques (pas des exceptions).
Je vois souvent des débutants dire qu'un bon morceau de code est un code qui ne peut pas lever d'exceptions. En fait, cette affirmation est fausse. Un bon code devrait lever les exceptions correspondantes lorsque cela est nécessaire, et un bon code ne peut intercepter que les exceptions qu'il sait gérer (notez cette phrase, note du traducteur).
Le code suivant sert d'explication à cette règle. Je parie que le gars qui a écrit le code ci-dessous me tuerait s'il le voyait, mais c'est un morceau de code tiré d'un véritable travail de programmation.
La première classe MyClass est dans un assembly et la deuxième classe GenericLibrary est dans un autre assembly. Il fonctionne normalement sur la machine de développement, mais sur la machine de test, il renvoie toujours une exception « Données invalides ! » même si les données saisies à chaque fois sont légales.
Pouvez-vous me dire pourquoi ?
public class MyClass { public static string ValidateNumber(string userInput) { try { int val = GenericLibrary.ConvertToInt(userInput); return "Valid number"; } catch (Exception) { return "Invalid number"; } } } public class GenericLibrary { public static int ConvertToInt(string userInput) { return Convert.ToInt32(userInput); } }
La raison de ce problème est que la gestion des exceptions n'est pas très spécifique. Selon l'introduction sur MSDN, la méthode Convert.ToInt32 ne lève que trois exceptions : ArgumentException, FormatException et OverflowException. Nous ne devrions donc traiter que ces trois exceptions.
问题发生在我们程序安装的步骤上,我们没有将第二个程序集(GenericLibrary.dll)打包进去。所以程序运行后,ConvertToInt方法会抛出FileNotFoundException异常,但是我们捕获的异常是Exception,所以会提示“数据不合法”。
不要中止异常上抛
最坏的情况是,你编写catch(Exception)这样的代码,并且在catch块中啥也不干。请不要这样做。
清理代码要放在finally块中
大多数时候,我们只处理某一些特定的异常,其它异常不负责处理。那么我们的代码中就应该多一些finally块(就算发生了不处理的异常,也可以在finally块中做一些事情,译者注),比如清理资源的代码、关闭流或者回复状态等。请把这当作习惯。
有一件大家容易忽略的事情是:怎样让我们的try/catch块同时具备易读性和健壮性。举个例子,假设你需要从一个临时文件中读取数据并且返回一个字符串。无论什么情况发生,我们都得删除这个临时文件,因为它是临时性的。
让我们先看看最简单的不使用try/catch块的代码:
string ReadTempFile(string FileName) { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; }
这段代码有一个问题,ReadToEnd方法有可能抛出异常,那么临时文件就无法删除了。所以有些人修改代码为:
string ReadTempFile(string FileName) { try { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; } catch (Exception) { File.Delete(FileName); throw; } }
这段代码变得复杂一些,并且它包含了重复性的代码。
那么现在让我们看看更简介更健壮的使用try/finally的方式:
string ReadTempFile(string FileName) { try { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; } catch (Exception) { File.Delete(FileName); throw; } }
变量fileContents去哪里了?它不再需要了,因为返回点在清理代码前面。这是让代码在方法返回后才执行的好处:你可以清理那些返回语句需要用到的资源(方法返回时需要用到的资源,所以资源只能在方法返回后才能释放,译者注)。
不要忘记使用using
仅仅调用对象的Dispose()方法是不够的。即使异常发生时,using关键字也能够防止资源泄漏。(关于对象的Dispose()方法的用法,可以关注我的书,有一章专门介绍。译者注)
不要使用特殊返回值去表示方法中发生的异常
因为这样做有很多问题:
1)直接抛出异常更快,因为使用特殊的返回值表示异常时,我们每次调用完方法时,都需要去检查返回结果,并且这至少要多占用一个寄存器。降低代码运行速度。
2)特殊返回值能,并且很可能被忽略
3)特殊返回值不能包含堆栈跟踪(stack trace)信息,不能返回异常的详细信息
4)很多时候,不存在一个特殊值去表示方法中发生的异常,比如,除数为零的情况:
public int pide(int x, int y) { return x / y; }
不要使用“抛出异常”的方式去表示资源不存在
微软建议在某些特定场合,方法可以通过返回一些特定值来表示方法在执行过程中发生了预计之外的事情。我知道我上面提到的规则恰恰跟这条建议相反,我也不喜欢这样搞。但是一些API确实使用了某些特殊返回值来表示方法中的异常,并且工作得很好,所以我还是觉得你们可以谨慎地遵循这条建议。
我看到了.NET Framework中很多获取资源的API方法使用了特殊返回值,比如Assembly.GetManifestStream方法,当找不到资源时(异常),它会返回null(不会抛出异常)。
不要将“抛出异常”作为函数执行结果的一种
这是一个非常糟糕的设计。代码中包含太多的try/catch块会使代码难以理解,恰当的设计完全可以满足一个方法返回各种不同的执行结果(绝不可能到了必须使用抛出异常的方式才能说明方法执行结果的地步,译者注),如果你确实需要通过抛出异常来表示方法的执行结果,那只能说明你这个方法做了太多事情,必须进行拆分。(这里原文的意思是,除非确实有异常发生,否则一个方法不应该仅仅是为了说明执行结果而抛出异常,也就是说,不能无病呻呤,译者注)
可以使用“抛出异常”的方式去着重说明不能被忽略的错误
我可以举个现实中的例子。我为我的Grivo(我的一个产品)开发了一个用来登录的API(Login),如果用户登录失败,或者用户并没有调用Login方法,那么他们调用其他方法时都会失败。我在设计Login方法的时候这样做的:如果用户登录失败,它会抛出一个异常,而并不是简单的返回false。正因为这样,调用者(用户)才不会忽略(他还没登录)这个事实。
不要清空了堆栈跟踪(stack trace)信息
堆栈跟踪信息是异常发生时最重要的信息,我们经常需要在catch块中处理一些异常,有时候还需要重新上抛异常(re-throw)。下面来看看两种方法(一种错误的一种正确的):
错误的做法:
try { // Some code that throws an exception } catch (Exception ex) { // some code that handles the exception throw ex; }
为什么错了?因为当我们检查堆栈跟踪信息时,异常错误源变成了“thorw ex;”,这隐藏了真正异常抛出的位置。试一下下面这种做法:
try { // Some code that throws an exception } catch (Exception ex) { // some code that handles the exception throw; }
有什么变化没?我们使用“throw;”代替了“throw ex;”,后者会清空原来的堆栈跟踪信息。如果我们在抛出异常时没有指定具体的异常(简单的throw),那么它会默认地将原来捕获的异常继续上抛。这样的话,上层代码捕获的异常还是最开始我们通过catch捕获的同一个异常。
拓展阅读:
C# 异常处理(Catch Throw)IL分析
异常类应标记为Serializable
很多时候,我们的异常需要能被序列化。当我们派生一个新的异常类型时,请不要忘了给它加上Serializable属性。谁会知道我们的异常类会不会用在Remoting Call或者Web Services中呢?
使用”抛出异常”代替Debug.Assert
当我们发布程序后,不要忘了Debug.Assert将会被忽略。我们在代码中做一些检查或者验证工作时,最好使用抛出异常的方式代替输出Debug信息。
将输出Debug信息这种方式用到单元测试或者那些只需要测试当软件真正发布后确保不会出错的场合。
每个异常类至少包含三个构造方法
做这件事相当简单(直接从其他的类型粘贴拷贝相同的代码即可),如果你不这样做,那么别人在使用你编写的异常类型时,很难遵守上面给出的一些规则的。
我指的哪些构造方法呢?这三个构造方法可以参见这里。
不要重复造轮子
已经有很多在异常处理方面做得比较好的框架或库,微软提供的有两个:
Exception Management Application Block
Microsoft Enterprise Instrumentation Framework
Notez que ces bibliothèques peuvent ne pas vous être d'une grande utilité si vous ne suivez pas certaines des règles que j'ai mentionnées ci-dessus.
VB.NET
Si vous avez lu le entier Après avoir lu cet article, vous constaterez que tous les exemples de code sont écrits en C#. C'est parce que C# est mon langage .NET préféré et que VB.NET a ses propres règles spéciales.
Simulation de l'instruction using en C#
Malheureusement Oui , il n'y a pas d'instruction using dans VB.NET. Chaque fois que vous libérez les ressources non gérées d'un objet, vous devez faire ceci :
Si vous ne suivez pas Si vous appelez la méthode DISpose de la manière ci-dessus, des erreurs sont susceptibles de se produire (Pour plus d'informations sur l'appel de la méthode Dispose, veuillez faire attention au nouveau livre. Note du traducteur ).
Ne pas utiliser la gestion des exceptions non structurées (On Error Goto)
La gestion des exceptions non structurées est également appelée "On Error Goto". Djikstra a déclaré en 1974 que "les instructions goto sont nuisibles plutôt qu'utiles". Veuillez supprimer toutes les instructions goto de votre code, je vous le garantis, elles ne servent à rien. (Edsger Dykstra a proposé la "théorie nuisible goto", les sémaphores et les primitives PV, résolvant le problème intéressant des philosophes de la restauration. En parlant du langage Fortran dans le livre "Software Story", je l'ai visité. Note du traducteur)
>J'espère que cet article pourra permettre à certaines personnes d'améliorer la qualité de leur codage. J'espère également que cet article est le début d'une discussion sur la façon de gérer efficacement les exceptions et de les rendre efficaces. les programmes que nous écrivons sont plus robustes.
Mots du traducteur :
J'ai un défaut, je ne sais pas si See More il y a des internautes comme moi. Je suis un consommateur lent, et il en va de même pour la technologie. Je n'ai commencé à ressentir quelque chose qu'après le pic de popularité de beaucoup de choses. La raison principale est que je n'aime pas beaucoup les choses nouvelles ; la deuxième raison est que j'ai toujours l'impression de changer les choses avant de les maîtriser, ce qui signifie abandonner à mi-chemin. En fait, je sais aussi que c'est très mauvais. Après tout, l'industrie informatique est une industrie en développement rapide, et elle prendra du retard dès qu'elle ne parviendra pas à suivre le rythme.
C'est précisément lorsque je rencontre une situation aussi contradictoire que lorsque j'apprends des connaissances, je me concentre sur l'apprentissage de la communication entre Il est peu probable que la nature, ce qu'on appelle l'universalité, change ou décline dans dix, vingt ou même trente ans si l'entreprise dans laquelle vous travaillez actuellement a utilisé un certain ensemble de cadres dans le processus de développement réel. si vous insistez sur Si vous ne vous concentrez pas sur « comment utiliser ce cadre pour créer un bon système », vous risquez de prendre du retard dans quelques années. Et si vous étudiez les points communs de la programmation, tels que les protocoles, les principes d'interaction entre les systèmes, etc., ceux-ci sont utilisés dans chaque système de communication réseau, qu'il s'agisse d'un programme PC apparemment obsolète, d'un programme Web ou d'une application mobile actuellement populaire. être utilisé, et les principes de base sont les mêmes. Plus je le regarde, plus je me rends compte que de nouvelles choses sortent qui semblent changer la soupe sans changer le médicament (légère exagération :-))
Donc je lui donne à ceux qui sont comme moi et qui ne suivent pas vraiment. Le conseil pour les personnes qui découvrent de nouvelles choses, ou celles qui sont engagées dans un certain type de travail de développement fixe depuis longtemps, est de découvrir les points communs entre technologies, et ne restez pas à la surface de la technologie à moins d'être suffisamment intéressé par les nouvelles choses et de disposer de suffisamment d'énergie.
Les mots ci-dessus ont également été partagés lors de la réunion de discussion de notre entreprise.
Auteur : Zhou Jianzhi
Source : http://www.php.cn/Le droit d'auteur de cet article appartient à l'auteur et au parc du blog. La réimpression est la bienvenue. Cependant, cette déclaration doit être conservée sans le consentement de l'auteur, et un lien vers le texte original doit être fourni en évidence sur la page de l'article. Dans le cas contraire, le droit de poursuivre la responsabilité juridique est réservé.
Supplément :
À propos de la stratégie de gestion des exceptions « traversée en deux tours » de CLR.
Lorsqu'une application a une structure de capture d'exceptions imbriquée multicouche, si une exception se produit au niveau de la couche la plus basse (en fait, la même chose est vraie dans la couche intermédiaire), le CLR donnera la priorité à la recherche du bloc d'instruction catch au niveau de la couche intermédiaire. couche où l'exception est générée. Existe-t-il un code de gestion
« compatible » pour ce type d'exception ? Sinon, "passez" simplement à la couche précédente pour rechercher. continuez à rechercher le "calque précédent" du calque précédent. De là au niveau supérieur de l'application.
Il s'agit du "premier tour" de l'application par le CLR des structures de capture d'exceptions imbriquées - trouver le gestionnaire d'exceptions approprié.
Si un gestionnaire d'exceptions est trouvé sur une certaine couche, notez que le CLR ne l'exécutera pas immédiatement, mais reviendra à la "scène de l'accident" et effectuera à nouveau le parcours du "deuxième tour" pour exécuter le dernier de tous. niveaux "intermédiaires", puis exécutez
pour trouver le gestionnaire d'exceptions, et enfin, parcourez cette couche vers le haut, en exécutant tous les blocs d'instructions final.
Ce qui précède est le contenu des meilleures pratiques (traduction) pour la gestion des exceptions dans . NET, pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !