120 votes

Meilleure pratique pour le traitement des exceptions dans une application Windows Forms ?

Je suis actuellement en train d'écrire ma première application Windows Forms. J'ai lu quelques livres sur le C# et j'ai donc une assez bonne compréhension des fonctionnalités du langage C# pour traiter les exceptions. Cependant, ces livres sont tous assez théoriques et je ne sais pas encore comment traduire les concepts de base en un bon modèle de gestion des exceptions dans mon application.

Quelqu'un souhaite-t-il partager des perles de sagesse sur le sujet ? Publiez les erreurs courantes que vous avez vues commettre par des débutants comme moi, ainsi que des conseils généraux sur la gestion des exceptions de manière à rendre mon application plus stable et plus robuste.

Les principales choses que j'essaie actuellement de régler sont :

  • Quand dois-je relancer une exception ?
  • Devrais-je essayer d'avoir un mécanisme central de traitement des erreurs ?
  • La gestion des exceptions qui pourraient être levées a-t-elle un impact sur les performances par rapport à la vérification préemptive de choses comme l'existence d'un fichier sur le disque ?
  • Tout le code exécutable doit-il être enfermé dans des blocs try-catch-finally ?
  • Y a-t-il des cas où un bloc de capture vide pourrait être acceptable ?

Tous les conseils sont les bienvenus !

83voto

John Rudy Points 16436

Quelques éléments supplémentaires ...

Vous devez absolument mettre en place une politique centralisée de traitement des exceptions. Cela peut être aussi simple que d'envelopper Main() dans un try/catch, en échouant rapidement avec un message d'erreur gracieux pour l'utilisateur. C'est le gestionnaire d'exception de "dernier recours".

Les contrôles préemptifs sont toujours corrects s'ils sont réalisables, mais pas toujours parfaits. Par exemple, entre le code où vous vérifiez l'existence d'un fichier et la ligne suivante où vous l'ouvrez, le fichier peut avoir été supprimé ou un autre problème peut entraver votre accès. Vous avez toujours besoin de try/catch/finally dans ce monde. Utilisez à la fois la vérification préemptive et le try/catch/finally comme il convient.

Ne jamais "avaler" une exception, sauf dans les cas les plus documentés où vous êtes absolument, positivement sûr que l'exception levée est vivable. Ce ne sera presque jamais le cas. (Et si c'est le cas, assurez-vous que vous n'avalez que l'exception spécifique classe d'exception -- ne jamais avaler System.Exception .)

Lorsque vous construisez des bibliothèques (utilisées par votre application), n'avalez pas les exceptions, et n'ayez pas peur de laisser les exceptions remonter. Ne relancez pas les exceptions à moins que vous n'ayez quelque chose d'utile à ajouter. Ne faites jamais cela (en C#) :

throw ex;

Comme vous allez effacer la pile d'appels. Si vous devez relancer l'appel (ce qui est parfois nécessaire, par exemple lorsque vous utilisez le bloc de traitement des exceptions de la bibliothèque Enterprise), utilisez ce qui suit :

throw;

En fin de compte, la très grande majorité des exceptions lancées par une application en cours d'exécution devrait être exposée quelque part. Elles ne doivent pas être exposées aux utilisateurs finaux (car elles contiennent souvent des données propriétaires ou des données précieuses), mais plutôt être enregistrées, et les administrateurs doivent être informés de l'exception. L'utilisateur peut être présenté avec une boîte de dialogue générique, peut-être avec un numéro de référence, pour garder les choses simples.

Le traitement des exceptions dans .NET est plus un art qu'une science. Chacun aura ses préférés à partager ici. Ce ne sont que quelques-unes des astuces que j'ai acquises en utilisant .NET depuis le premier jour, des techniques qui m'ont sauvé la mise plus d'une fois. Votre expérience peut varier.

65voto

Micah Points 28683

Il existe un excellent code Article de CodeProject ici . En voici quelques exemples :

  • Prévoyez le pire*.
  • Vérifier tôt
  • Ne faites pas confiance aux données externes
  • Les seuls dispositifs fiables sont : la vidéo, la souris et le clavier.
  • Les écritures peuvent aussi échouer
  • Codez en toute sécurité
  • Ne lancez pas une nouvelle Exception()
  • Ne mettez pas d'informations importantes sur les exceptions dans le champ "Message".
  • Mettre un seul catch (Exception ex) par thread
  • Les exceptions génériques capturées devraient être publiées
  • Log Exception.ToString() ; ne jamais enregistrer seulement Exception.Message !
  • Ne pas attraper (Exception) plus d'une fois par thread
  • N'avalez jamais d'exceptions
  • Le code de nettoyage devrait être placé dans des blocs finaux
  • Utilisez "utiliser" partout
  • Ne pas renvoyer de valeurs spéciales en cas d'erreur
  • Ne pas utiliser d'exceptions pour indiquer l'absence d'une ressource
  • N'utilisez pas le traitement des exceptions comme moyen de renvoyer des informations à partir d'une méthode.
  • Utilisez des exceptions pour les erreurs qui ne doivent pas être ignorées.
  • Ne pas effacer la trace de la pile lorsque l'on relance une exception.
  • Éviter de modifier les exceptions sans ajouter de valeur sémantique
  • Les exceptions doivent être marquées [Serializable].
  • En cas de doute, n'affirmez pas, lancez une exception.
  • Chaque classe d'exception devrait avoir au moins les trois constructeurs originaux
  • Soyez prudent lorsque vous utilisez l'événement AppDomain.UnhandledException.
  • Ne réinventez pas la roue
  • N'utilisez pas de gestion d'erreur non structurée (VB.Net)

16voto

user95680 Points 91

Notez que Windows Forms possède son propre mécanisme de traitement des exceptions. Si un bouton du formulaire est cliqué et que son gestionnaire lève une exception qui n'est pas prise en compte dans le gestionnaire, Windows Forms affichera sa propre boîte de dialogue d'exception non gérée.

Pour empêcher l'affichage de la boîte de dialogue Unhandled Exception et attraper ces exceptions pour les consigner et/ou pour fournir votre propre boîte de dialogue d'erreur, vous pouvez vous attacher à l'événement Application.ThreadException avant l'appel à Application.Run() dans votre méthode Main().

15voto

Robert Rossney Points 43767

Tous les conseils postés ici jusqu'à présent sont bons et méritent d'être pris en compte.

Une chose que j'aimerais développer est votre question "Est-ce que la gestion des exceptions qui pourraient être levées a un impact sur les performances par rapport à la vérification préemptive de choses comme l'existence d'un fichier sur le disque ?".

La règle naïve est "les blocs try/catch sont chers". Ce n'est pas vraiment vrai. Essayer n'est pas cher. C'est le fait d'attraper, où le système doit créer un objet Exception et le charger avec la trace de la pile, qui est coûteux. Dans de nombreux cas, l'exception est suffisamment exceptionnelle pour qu'il soit parfaitement possible d'envelopper le code dans un bloc try/catch.

Par exemple, si vous remplissez un dictionnaire, ceci :

try
{
   dict.Add(key, value);
}
catch(KeyException)
{
}

est souvent plus rapide que de faire cela :

if (!dict.ContainsKey(key))
{
   dict.Add(key, value);
}

pour chaque élément que vous ajoutez, car l'exception n'est levée que lorsque vous ajoutez une clé en double. (Les requêtes agrégées LINQ font cela).

Dans l'exemple que vous avez donné, j'utiliserais try/catch presque sans réfléchir. Tout d'abord, ce n'est pas parce que le fichier existe quand vous le vérifiez qu'il existera quand vous l'ouvrirez, donc vous devriez vraiment traiter l'exception de toute façon.

Deuxièmement, et je pense que c'est le plus important, à moins que a) votre processus n'ouvre des milliers de fichiers et que b) les chances qu'un fichier qu'il essaie d'ouvrir n'existe pas ne soient pas trivialement faibles, l'impact sur les performances de la création de l'exception n'est pas quelque chose que vous allez jamais remarquer. En général, lorsque votre programme essaie d'ouvrir un fichier, il n'essaie d'ouvrir qu'un seul fichier. Dans ce cas, il est presque certain qu'il est préférable d'écrire un code plus sûr que d'écrire le code le plus rapide possible.

9voto

Sijin Points 3682

Voici quelques lignes directrices que je suis

  1. Fail-Fast : Pour chaque hypothèse que vous faites et chaque paramètre que vous introduisez dans une fonction, vérifiez que vous partez des bonnes données et que les hypothèses que vous faites sont correctes. Les vérifications typiques comprennent : argument non nul, argument dans la plage attendue, etc.

  2. Lors d'un nouveau lancement, conservez la trace de la pile - Cela signifie simplement qu'il faut utiliser throw lors d'un nouveau lancement au lieu de throw new Exception(). Si vous pensez pouvoir ajouter plus d'informations, vous pouvez envelopper l'exception originale dans une exception interne. Mais si vous attrapez une exception uniquement pour l'enregistrer, utilisez définitivement throw ;

  3. N'attrapez pas les exceptions que vous ne pouvez pas gérer, donc ne vous inquiétez pas de choses comme OutOfMemoryException car si elles se produisent, vous ne pourrez pas faire grand-chose de toute façon.

  4. Accrochez les gestionnaires d'exception globaux et assurez-vous d'enregistrer autant d'informations que possible. Pour winforms, accrochez à la fois les événements d'exception non gérés du domaine de l'application et du thread.

  5. Les performances ne doivent être prises en compte que lorsque vous avez analysé le code et constaté qu'il provoque un goulot d'étranglement au niveau des performances, en optimisant par défaut la lisibilité et la conception. Si vous pouvez faire quelque chose pour éviter que le fichier ne soit pas là, alors oui, faites cette vérification. Sinon, si tout ce que vous allez faire est de lancer une exception si le fichier n'est pas là, alors je ne vois pas l'intérêt.

  6. Il y a certainement des moments où des blocs de capture vides sont nécessaires, je pense que les personnes qui disent le contraire n'ont pas travaillé sur des bases de code qui ont évolué sur plusieurs versions. Mais ils doivent être commentés et revus pour s'assurer qu'ils sont vraiment nécessaires. L'exemple le plus typique est celui des développeurs qui utilisent try/catch pour convertir une chaîne en entier au lieu d'utiliser ParseInt().

  7. Si vous attendez de l'appelant de votre code qu'il soit capable de gérer les conditions d'erreur, créez des exceptions personnalisées qui détaillent la situation inattendue et fournissent des informations pertinentes. Sinon, utilisez autant que possible les types d'exceptions intégrés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X