2010-05-21 5 views
38

Quels sont quelques-unes des erreurs les plus courantes que vous avez vu lors de la manipulation des exceptions faites?erreurs de programmation courantes dans .Net lors de la manipulation des exceptions?

Il semble que la gestion des exceptions peut être l'une des choses les plus difficiles à apprendre comment faire « droit » en .Net. Surtout en considérant la réponse actuellement classé # 1-Common programming mistakes for .NET developers to avoid? est liée à la gestion des exceptions.

Espérons en énumérant quelques-unes des erreurs les plus courantes que nous pouvons tous apprendre à mieux gérer les exceptions.

+2

Peut-être que cela devrait être un wiki communautaire – chilltemp

+0

http://haacked.com/archive/2006/01/09/FinallyBlockDoesNotRequireExceptionClause.aspx –

+0

C'est un wiki maintenant, je viens oublié. – jColeson

Répondre

44

What are some of the most common mistakes you've seen made when handling exceptions?

(ou même du CLR dans son ensemble?)

Je peux penser à beaucoup.

d'abord lu mon article sur la catégorisation des exceptions en vexant, crétin, fatale et exogène:

http://ericlippert.com/2008/09/10/vexing-exceptions/

Quelques erreurs courantes:

  • non gérer les exceptions exogènes.
  • Échec de la gestion des exceptions vexantes.
  • Construction de méthodes qui lancent des exceptions vexantes.
  • Gestion des exceptions que vous ne pouvez pas gérer, comme des exceptions fatales.
  • Gestion des exceptions qui masquent les bogues dans votre code; ne pas gérer une exception boneheaded, corriger le bug afin qu'il ne soit pas jeté en premier lieu

  • Erreur de sécurité: échec au mode non sécurisé

    try 
    { 
        result = CheckPassword(); 
        if (result == BadPassword) throw BadPasswordException(); 
    } 
    catch(BadPasswordException ex) { ReportError(ex); return; } 
    catch(Exception ex) { LogException(ex); } 
    AccessUserData(); 
    

    Voir ce qui est arrivé? Nous avons échoué au mode dangereux.Si CheckPassword a lancé NetworkDriverIsAllMessedUpException, nous l'avons intercepté, enregistré et accédé aux données de l'utilisateur, que le mot de passe soit correct ou non. Échouer au mode sans échec; quand vous avez une exception, supposez le pire. Erreur de sécurité: la production d'exceptions qui fuite d'informations sensibles, directement ou indirectement.

    Il ne s'agit pas exactement de gérer des exceptions dans votre code, il s'agit de produire des exceptions qui sont gérées par un code hostile.

    Histoire drôle. Avant .NET 1.0 livré aux clients, nous avons trouvé un bug où il était possible d'appeler une méthode qui lançait l'exception "l'assembly qui a appelé cette méthode n'a pas la permission de déterminer le nom du fichier C: \ foo.txt". Génial. Merci de me le faire savoir. Qu'est-ce qui empêche ledit assemblage d'attraper l'exception et d'interroger son message pour obtenir le nom du fichier? Rien. Nous avons réparé cela avant d'expédier.

    C'est un problème direct. Un problème indirect serait un problème que j'ai implémenté dans LoadPicture, dans VBScript. Il a donné un message d'erreur différent selon que l'argument incorrect est un répertoire, un fichier qui n'est pas une image ou un fichier qui n'existe pas. Ce qui signifie que vous pouvez l'utiliser comme un navigateur de disque très lent! En essayant tout un tas de choses différentes, vous pouvez progressivement construire une image de ce que les fichiers et les répertoires étaient sur le disque dur de quelqu'un. Les exceptions doivent être conçues de telle sorte que si elles sont traitées par un code non fiable, ce code n'apprend aucune information privée de l'utilisateur de ce qu'elles ont fait pour provoquer l'exception. (LoadPicture donne maintenant beaucoup moins utiles les messages d'erreur.)

  • erreur de sécurité et la gestion des ressources: Handlers qui ne sont les fuites de ressources attente de se produire pas nettoyer les ressources. Les fuites de ressources peuvent être utilisées comme déni de service attaques par un code de confiance partiel hostile qui crée délibérément des situations produisant des exceptions. Erreur de robustesse: Les gestionnaires doivent supposer que l'état du programme est foiré sauf si une exception exogène spécifique est gérée. Ceci est particulièrement vrai des blocs finalement. Lorsque vous gérez une exception inattendue, il est tout à fait possible et même probable que quelque chose soit profondément perturbé dans votre programme. Vous n'avez aucune idée si l'un de vos sous-systèmes fonctionne, et s'ils le sont, si les appeler rendra la situation meilleure ou pire. Concentrez-vous sur la consignation de l'erreur et enregistrez les données de l'utilisateur si possible et arrêtez le plus proprement possible. Supposer que rien ne fonctionne correctement.

  • Erreur de sécurité: mutations étatiques globales temporaires qui ont des effets de sécurité doivent être annulées avant tout code qui pourrait être hostile peut fonctionner. Code hostile peut exécuter avant enfin blocs exécuter! Voir mon article sur ce pour plus de détails:

http://blogs.msdn.com/ericlippert/archive/2004/09/01/224064.aspx

+1

Alors que l'on devrait seulement reprendre après une exception si on peut le "gérer", il y a beaucoup de situations (comme lors de l'importation d'un "document" d'un fichier fourni par l'utilisateur) où 99% des exceptions peuvent être correctement traitées. tous les objets partiellement construits et signalant que l'opération a échoué. Dans une application bien écrite, une tentative de chargement de quelque chose qui n'est pas un fichier valide ne devrait pas faire planter l'application, mais devrait simplement échouer. Certains des objets en cours de construction peuvent avoir été corrompus *, mais s'ils sont défaussés lors du déroulement de la pile, cela ne devrait pas faire de mal *. – supercat

+0

Je souhaite que les langages .NET comme C# et VB.NET permettent au code de nettoyage de connaître le résultat de son 'try' associé, sans devoir" attraper "quoi que ce soit. Si le départ d'un flux de programme normal peut avoir laissé un objet dans un état invalide, cela peut être un problème grave si l'objet, et pas de problème si elle va être rejetée. Plutôt que les exceptions soient par défaut irrécupérables, il semblerait préférable de dire que le déroulement de la pile devrait explicitement invalider les objets potentiellement corrompus. Cela permettrait une récupération quand il est sûr, mais force l'arrêt quand c'est nécessaire. – supercat

+0

@supercat: Ce serait bien. CLR supporte un bloc 'try-fault' qui est comme un bloc finally qui est seulement exécuté s'il y avait une exception mais il n'a pas le type de sémantique de nettoyage que vous décrivez. Ce que j'aimerais voir, c'est un système de type STM qui restaure l'état d'un objet corrompu en cas d'exception, mais les expériences menées jusqu'ici ont montré que le coût de la performance est trop élevé. –

4

prises à vide:

//What's the point? 
catch() 
{} 

rethrowing:

//Exceptions are for *adding* detail up the stack 
catch (Exception ex) 
{throw ex;} 
+0

La capture vide est une mauvaise pratique de programmation? Vous suggérez que c'est une mauvaise idée de faire ce qui suit? Supposons qu'il existe un programme qui affiche les informations météorologiques à l'écran. Toutes les cinq minutes, il se connecte à une base de données pour récupérer une mise à jour de la météo et affiche le nouveau bulletin météo avec l'heure du dernier rapport météorologique récupéré. Il serait alors inapproprié de mettre la connexion de base de données dans un 'Try' avec un vide' Catch' afin que le programme puisse continuer à afficher la météo il y a cinq minutes si le programme ne parvient pas à obtenir un nouveau rapport météo? –

+1

@Rising Star: Dans ce cas, vous devriez enregistrer une erreur de base de données. –

+3

@Rising étoiles absolument ce serait mauvais, que les prises doit consigner l'événement et/ou rendre l'utilisateur conscient que le prochain rapport est retardé – Pharabus

25

exceptions rethrowing comme ceci:

try 
{ 
    // some code here 
} 
catch(Exception ex) 
{ 
    // logging, etc 
    throw ex; 
} 

Cela tue la trace de la pile, la fabrication est beaucoup moins utilisable. La bonne façon de réémettre serait comme ceci:

try 
{ 
    // some code here 
} 
catch(Exception ex) 
{ 
    // logging, etc 
    throw; 
} 
+1

Il l'a mentionné dans la question .. –

+3

@BlueRaja: Je n'ai pas réellement cliqué sur le lien. Et bien. Ça ne peut pas faire de mal de l'avoir mentionné ici, puisque c'est toujours lié à cette question. –

+2

"La bonne façon" est un peu extrême; il y a des cas valables pour 'jeter ex'. –

12

Attraper toutes les exceptions alors que dans bien des cas, vous devriez tenter d'intercepter les exceptions spécifiques:

try { 
    // Do something. 
} catch (Exception exc) { 
    // Do something. 
} 

Plutôt que:

try { 
    // Do something. 
} catch (IOException exc) { 
    // Do something. 
} 

Exceptions devrait être commandé du plus spécifique au moins.

7

Ne pas utiliser using sur IDisposable objets:

File myFile = File.Open("some file"); 
callSomeMethodWhichThrowsException(myFile); 
myFile.Close(); 

myFile ne soit pas fermé jusqu'à ce que finaliseur myFile est appelé (qui peut être jamais) parce qu'une exception a été levée avant myFile.Close() a été appelé.

La bonne façon de le faire est

using(File myFile = File.Open("some file")) 
{ 
    callSomeMethodWhichThrowsException(myFile); 
} 

Cela se traduit par le compilateur en quelque chose comme:

File myFile = File.Open("some file"); 
try 
{ 
    callSomeMethodWhichThrowsException(myFile); 
} 
finally 
{ 
    if(myFile != null) 
     myFile.Dispose(); //Dispose() calls Close() 
} 

Ainsi, le fichier est fermé, même face à des exceptions.

0

Mauvais

try 
{ 
    // Do something stupid 
} 
catch 
{ 
    // ignore the resulting error because I'm lazy, and later spend 
    // a week trying to figure out why my app is crashing all over 
    // the place. 
} 

Mieux

try 
{ 
    /// do something silly. 
} 
catch (InvalidOperationException ex) 
{ 
    /// respond, or log it. 
} 
catch (Exception e) 
{ 
    /// log it. 
} 
10

une exception avec Renvoi des un message vide de sens.

try 
{ 
    ... 
} 
catch (Exception ex) 
{ 
    throw new Exception("An error ocurred when saving database changes"). 
} 

Vous ne croirez pas combien de fois je vois un code comme celui en cours d'exécution dans la production.

+1

Un autre défaut dans cet exemple est que l'exception d'origine n'est pas transmise en tant qu'exception interne au nouvel objet d'exception. Probablement encore pire que le message d'erreur sans signification, au moins l'exception interne aurait fourni une idée du problème original. –

+21

Il s'agit souvent d'une bonne idée si l'exception d'origine contient des informations sensibles sur les détails d'implémentation du serveur et si l'appelant qui gère l'exception n'est pas approuvé. Un attaquant essayant, par exemple, de créer une attaque par injection SQL contre une base de données aimerait voir un message d'erreur détaillé décrivant exactement comment la requête s'est mal passée. –

+0

D'accord avec le commentaire d'Eric, donc nous devrions considérer les soucis de sécurité en décidant d'inclure ou non l'information d'exception interne. Cependant, la majeure partie du code cache la cause sous-jacente de l'exception, c'est pourquoi j'ai créé une autre réponse à propos de l'inclusion de l'exception interne. Je vais inclure un commentaire à ce sujet là. Merci. –

3

Supposer qu'une exception couvrant de nombreux scénarios était spécifique. Un scénario de vie réel était une application Web où la gestion des exceptions supposait toujours que toutes les erreurs étaient des dépassements de délai de session et consignaient toutes les erreurs en tant que dépassements de délai de session.

Un autre exemple:

try 
{ 
    Insert(data); 
} 
catch (SqlException e) 
{ 
    //oh this is a duplicate row, lets change to update 
    Update(data); 
} 
6

Oublier de mettre l'exception interne lorsque rethrowing une exception attrapées

try 
{ 
    ... 
} 
catch (IOException ioException) 
{ 
    throw new AppSpecificException("It was not possible to save exportation file.") 
    // instead of 
    throw new AppSpecificException("It was not possible to save exportation file.", ioException); 
} 

Quand je posté cette réponse, j'oublie de mentionner que nous devrions toujours considérer quand inclure exception interne ou non pour des raisons de sécurité. Comme Eric Lippert a souligné sur another answer for this topic, certaines exceptions peuvent fournir des informations sensibles sur les détails d'implémentation du serveur. Ainsi, si l'appelant qui gère l'exception n'est pas approuvé, il n'est pas recommandé d'inclure les informations d'exception interne.

+0

+1 pour l'exception interne :) –

+0

n'avait pas pensé à ça. Merci! – earthling

+1

Bit d'un mauvais exemple ici - il est recommandé de ne pas utiliser ApplicationException – ScottE

3

Pour vous connecter Exception.Message au lieu de Exception.ToString()

Plusieurs fois, je vois le code que vous connecter le message d'exception alors qu'il devrait enregistrer le retour de la méthode ToString. ToString fournit beaucoup plus d'informations sur l'exception que le message. Il inclut des informations telles que l'exception interne et la trace de la pile en plus du message.

1

Essayer d'attraper OutOfMemoryException ou StackOverflowException - ceux qui mènent à un arrêt du moteur d'exécution, donc de façon de les attraper à l'intérieur du même processus

OutOfMemoryException: The exception that is thrown when there is not enough memory to continue the execution of a program.

"Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow."

9

Personne ne parle de voir des blocs catch vides comme ceux-ci ....

try{ 
     //do something 
    } 
catch(SQLException sqex){ 
     // do nothing 
    } 

jamais utiliser les exceptions de manipulation pour créer autre méthode les flux...

try{ 
    //do something 

}catch(SQLException sqex){ 

    //do something else 
} 
+4

+1 pour ne pas utiliser d'exceptions pour le flux de contrôle. –

0

Utilisation d'exceptions pour le contrôle de flux normal. Les exceptions devraient exceptionnelles. Si c'est une opération bonne/prévu, utilisez des valeurs de retour, etc.

1

parvient pas à intercepter les exceptions possibles à l'intérieur d'un gestionnaire catch. Cela peut entraîner la propagation de la mauvaise exception vers le haut.

Par exemple:

try 
{ 
    DoImportantWork(); 
} 
catch 
{ 
    Cleanup();   
    throw; 
} 

Qu'est-ce qui se passe si Cleanup() déclenche une exception? Vous ne voulez pas voir une exception pointant vers la méthode Cleanup() dans ce gestionnaire de catch. Vous voulez l'erreur d'origine. Vous pouvez essayer d'enregistrer l'erreur de nettoyage, mais même votre code de journalisation nécessite une gestion des exceptions pour éviter de générer des exceptions.

try 
{ 
    DoImportantWork(); 
} 
catch 
{ 
    try 
    { 
     Cleanup();   
    } 
    catch 
    { 
     // We did our best to clean up, and even that failed. 
     // If you try to log this error, the logging may throw yet another Exception. 
    } 
    throw; 
} 
+1

-1 pour la clause vide 'catch'. –

+1

+1: Bon point. Il n'y a rien de fondamentalement mauvais avec une prise vide ... assurez-vous juste que vous comprenez les conséquences. Par exemple, comment implémenteriez-vous IDisposable.Dispose sans qu'il propage des exceptions? Parfois, il suffit de punt, en particulier lorsque votre composant de journalisation tombe en panne. –

+0

@Brian: Exactement. J'ai même vu Debug.WriteLine jeter une exception! À un moment donné, il suffit d'abandonner. –

Questions connexes