38 votes

Comment devrais-je programmer de manière défensive?

je travaillais avec une petite routine qui est utilisé pour créer une connexion de base de données:

Avant

public DbConnection GetConnection(String connectionName)
{
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);
   DbConnection conn = factory.CreateConnection();
   conn.ConnectionString = cs.ConnectionString;
   conn.Open();

   return conn;
}

Puis j'ai commencé à chercher dans la .NET framework documentation, pour voir ce qui l' a documenté le comportement de diverses choses sont, et de voir si je peux les manipuler.

Par exemple:

ConfigurationManager.ConnectionStrings...

La documentation indique que l'appel ConnectionStrings jette un ConfigurationErrorException si elle n'a pas pu récupérer la collection. Dans ce cas, il n'y a rien que je peux faire pour gérer cette exception, donc je vais le laisser aller.


La prochaine partie est l'indexation de la ConnectionStrings pour trouver nomconnexion:

...ConnectionStrings[connectionName];

Dans ce cas, le ConnectionStrings la documentation dit que la propriété renvoie la valeur null si le nom de connexion n'a pas pu être trouvé. je peux vérifier ce qui se passe, et de lever une exception de laisser quelqu'un d'en haut qu'ils ont donné une invalide nomconnexion:

ConnectionStringSettings cs= 
      ConfigurationManager.ConnectionStrings[connectionName];
if (cs == null)
   throw new ArgumentException("Could not find connection string \""+connectionName+"\"");


je le répète le même exercice avec:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);

Le GetFactory méthode n'a pas de documentation sur ce qui se passe si une usine pour l' ProviderName ne pouvait pas être trouvé. Ce n'est pas documentée pour revenir null, mais je peux toujours être sur la défensive, et de vérifier pour les nuls:

DbProviderFactory factory = 
      DbProviderFactories.GetFactory(cs.ProviderName);
if (factory == null) 
   throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");


Suivante est la construction de la DbConnection objet:

DbConnection conn = factory.CreateConnection()

De nouveau, la documentation ne dit pas ce qui se passe s'il ne pourrait pas créer une connexion, mais encore une fois je peux vérifier pour un retour null objet:

DbConnection conn = factory.CreateConnection()
if (conn == null) 
   throw new Exception.Create("Connection factory did not return a connection object");


Suivant est la définition d'une propriété de l'objet de Connexion:

conn.ConnectionString = cs.ConnectionString;

Les docs ne pas dire ce qui se passe si on ne peut pas définir la chaîne de connexion. Est-il lever une exception? Est-il l'ignorer? Comme avec la plupart des exception, s'il y avait une erreur lors de la tentative de définition de la ConnectionString d'une connexion, il n'y a rien que je peux faire pour la récupérer. Je vais donc ne rien faire.


Et enfin, l'ouverture de la connexion de base de données:

conn.Open();

La méthode Ouverte de DbConnection est abstrait, c'est donc jusqu'à ce que le fournisseur est décroissant à partir de DbConnection de décider quelles sont les exceptions qu'ils jettent. Il n'y a également aucune indication dans le résumé des méthodes Ouvertes de la documentation sur ce que je peux attendre si il y a une erreur. Si il y avait une erreur lors de la connexion, je sais que je ne peut pas gérer elle - je vais devoir laisser de bulle où l'appelant peut montrer certains de l'INTERFACE utilisateur pour l'utilisateur, et laissez-les essayer de nouveau.


Après

public DbConnection GetConnection(String connectionName)
{
   //Get the connection string info from web.config
   ConnectionStringSettings cs= ConfigurationManager.ConnectionStrings[connectionName];

   //documented to return null if it couldn't be found
    if (cs == null)
       throw new ArgumentException("Could not find connection string \""+connectionName+"\"");

   //Get the factory for the given provider (e.g. "System.Data.SqlClient")
   DbProviderFactory factory = DbProviderFactories.GetFactory(cs.ProviderName);

   //Undefined behaviour if GetFactory couldn't find a provider.
   //Defensive test for null factory anyway
   if (factory == null)
      throw new Exception("Could not obtain factory for provider \""+cs.ProviderName+"\"");

   //Have the factory give us the right connection object
   DbConnection conn = factory.CreateConnection();

   //Undefined behaviour if CreateConnection failed
   //Defensive test for null connection anyway
   if (conn == null)
      throw new Exception("Could not obtain connection from factory");

   //Knowing the connection string, open the connection
   conn.ConnectionString = cs.ConnectionString;
   conn.Open()

   return conn;
}

Résumé

Donc ma ligne de quatre la fonction, est devenu de 12 lignes, et de 5 mintues de la documentation de recherche. En fin de compte je l'ai fait attraper l'un des cas où une méthode est autorisé à retourner la valeur null. Mais, dans la pratique, j'ai tout convertir une exception de violation d'accès (si je tente d'appeler des méthodes sur une référence null) dans un InvalidArgumentException.

j'ai aussi attraper les deux cas, il pourrait être null retour des objets; mais encore une fois je ne négocié une exception pour l'autre.

Sur le côté positif, il n'a attraper deux problèmes, et d'expliquer ce qui s'est passé dans le message de l'exception, plutôt que de mauvaises choses qui se passe en bas de la route (c'est à dire le mâle s'arrête ici)

Mais est-il utile? Est-ce exagéré? Est-ce une programmation défensive qui a mal tourné?

31voto

JacquesB Points 19878

Vérifier manuellement pour une configuration et lancer une exception ne vaut pas mieux que simplement en laissant le cadre de jeter l'exception si la configuration est manquant. Vous êtes un peu double emploi avec condition préalable vérifie ce qui se passe à l'intérieur du cadre des méthodes de toute façon, et ça rend le code détaillé avec aucun avantage. (En fait, vous pourriez être en retrait de l'information en jetant tout ce que la base de l'Exception de la classe. Les exceptions levées par le cadre sont généralement plus précis.)

Edit: Cette réponse semble être quelque peu controversée, donc un peu d'élaboration: une programmation Défensive signifie "se préparer à l'inattendu" (ou "être paranoïaque") et l'une des façons de le faire est de faire beaucoup de préalable des contrôles. Dans de nombreux cas, c'est une bonne pratique, cependant, comme avec toutes les pratiques que les coûts devraient être pesés contre les avantages.

Par exemple, il n'apporte aucun avantage, à un jet de "ne Pouvait pas obtenir une connexion à partir de l'usine" exception, puisqu'elle ne dit rien sur le pourquoi, le fournisseur ne pouvait pas être obtenue et la très prochaine de la ligne lèvera une exception de toute façon si le fournisseur est null. De sorte que le coût de la condition à vérifier (dans le temps de développement et de complexité de code) n'est pas justifiée.

D'autre part, le contrôle afin de vérifier que la chaîne de connexion de configuration est présent peut être justifiée, étant donné que l'exception peut aider à dire le développeur comment résoudre le problème. Le null-exception que vous obtiendrez dans la ligne suivante, de toute façon, ne dites pas le nom de la chaîne de connexion qui est absent, de sorte que votre condition de vérifier une certaine valeur. Si votre code est une partie d'un composant par exemple, la valeur est assez grande, puisque l'utilisateur du composant ne peut pas connaître les configurations de la composante exige.

Une interprétation différente de la défensive de la programmation, c'est que vous ne devriez pas simplement détecter les conditions d'erreur, vous devriez aussi essayer de récupérer d'une erreur ou d'exception qui peuvent se produire. Je ne crois pas que ce soit une bonne idée en général.

Fondamentalement, vous devez seulement de gérer les exceptions que vous pouvez faire quelque chose. Les Exceptions que vous ne pouvez pas récupérer de toute façon, faut juste être transmise vers le haut pour le haut niveau de gestionnaire. Dans une application web au niveau supérieur gestionnaire probablement juste montre une page d'erreur générique. Mais il n'y a pas vraiment beaucoup à faire dans la plupart des cas, si la base de données est hors-ligne ou un élément crucial de configuration est manquant.

Certains cas où ce genre de programmation défensives de sens, est si vous acceptez la saisie de l'utilisateur, et que la saisie peut conduire à des erreurs. Par exemple, si l'utilisateur fournit une URL comme entrée, et l'application tente d'aller chercher quelque chose à partir de cette URL, alors il est très important que vous vérifiez que l'URL est correct, et vous gérer toutes les exceptions qui peuvent résulter de la demande. Cela vous permet de fournir de précieuses informations à l'utilisateur.

13voto

mquander Points 32650

Eh bien, cela dépend de qui est votre public cible.

Si vous écrivez du code de bibliothèque que vous vous attendez à être utilisé par un grand nombre d'autres personnes, qui ne sera pas vous parler de la façon de l'utiliser, alors il n'est pas exagéré. Ils apprécieront vos efforts.

(Cela dit, si vous faites cela, je vous suggère de mieux définir les exceptions de Système juste.Exception, pour le rendre plus facile pour les gens qui veulent attraper certains de vos exceptions, mais pas d'autres.)

Mais si vous êtes juste de l'utiliser vous-même (ou, vous et votre meilleur ami), alors il est évident que c'est exagéré, et probablement vous fait mal à la fin, en faisant de votre code moins lisible.

7voto

Bobby Alexander Points 1155

Je souhaite que je pourrais obtenir mon équipe de code comme celui-ci. La plupart des gens n'avez même pas obtenir le point de programmation défensives. Le mieux à faire est d'envelopper l'ensemble de la méthode dans un try catch déclaration et de laisser toutes les exceptions être traitées par le générique bloc d'exception!

Chapeaux de pour vous, Ian. Je comprends votre dilemme. J'ai été dans la même moi-même. Mais ce que vous n'avez probablement aidé certains développeur de plusieurs heures de clavier bashing.

N'oubliez pas, lorsque vous utilisez un .net framework api, qu'attendez-vous de cela? Ce qui semble naturel? Faire de même avec votre code.

Je sais que ça prend du temps. Mais alors la qualité a un coût.

PS: Vous avez vraiment n'avez pas à gérer toutes les erreurs et de lever une exception personnalisée. Rappelez-vous que votre méthode sera uniquement utilisé par d'autres développeurs. Ils devraient être en mesure de comprendre le cadre commun des exceptions elles-mêmes. C'est pas la peine.

6voto

Robert Harvey Points 103562

"Avant" exemple a le mérite d'être clair et concis.

Si quelque chose est incorrect, une exception sera levée par la suite par le cadre. Si vous ne pouvez pas faire quelque chose au sujet de l'exception, vous pourriez tout aussi bien le laisser se propager jusqu'à la pile d'appel.

Il ya des moments, cependant, quand une exception est levée profondément à l'intérieur du cadre qui n'a pas vraiment faire la lumière sur ce qu'est le véritable problème. Si votre problème est que vous n'avez pas de chaîne de connexion valide, mais le cadre déclenche une exception comme "non valide utilisation de null," alors parfois, il est mieux de prendre l'exception et de la renvoyer avec un message qui est plus significatif.

Je dois vérifier la nullité des objets beaucoup, car j'ai besoin d'un objet réel pour fonctionner sur, et si l'objet est vide, l'exception est levée sera oblique, pour dire le moins. Mais j'ai seulement des objets null si je sais que c'est ce qui va se produire. Un objet usines de ne pas retourner des objets null; ils jettent une exception au lieu de cela, et la vérification de la valeur null sera inutile dans ces cas.

3voto

Robert Rossney Points 43767

Je ne pense pas que je voudrais écrire le null-la vérification des références à la logique, au moins, pas de la façon que vous l'avez fait.

Mes programmes que d'obtenir les paramètres de configuration du fichier de configuration d'application de contrôler tous ces paramètres au démarrage. J'ai l'habitude de construire une classe statique contenant les paramètres de référence et que les propriétés de la classe (et non pas l' ConfigurationManager) ailleurs dans l'application. Il y a deux raisons à cela.

Tout d'abord, si l'application n'est pas configuré correctement, il ne va pas au travail. Je préfère savoir ce à l'heure actuelle, le programme lit le fichier de configuration qu'à un certain moment dans l'avenir, lorsque j'essaie de créer une connexion de base de données.

Deuxièmement, la vérification de la validité de la configuration ne devrait pas vraiment être la préoccupation des objets qui dépendent de la configuration. Il n'y a pas de sens dans la prise vous-même insérer des contrôles partout dans votre code si vous avez déjà procédé à ces vérifications à l'avant. (Il ya des exceptions à cela, certainement, par exemple, de longue durée les applications où vous avez besoin pour être en mesure de modifier la configuration lorsque le programme est en cours d'exécution et avoir ces changements reflétés dans le programme du comportement; dans de tels programmes, vous aurez besoin d'aller à l' ConfigurationManager toutes les fois que vous besoin d'un réglage.)

Je ne ferais pas la valeur null-la vérification des références sur l' GetFactory et CreateConnection appels d'. Comment écririez-vous un cas de test à l'exercice de ce code? Vous ne pouvez pas, parce que vous ne savez pas comment rendre ces méthodes retournent null - vous ne savez même pas qu'il est possible de rendre ces méthodes renvoient la valeur null. Si vous avez remplacé un problème - votre programme peut jeter un NullReferenceException dans des conditions que vous ne comprenez pas - avec une autre, plus importante: dans les mêmes conditions mystérieuses, votre programme va exécuter le code que vous n'avez pas testé.

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