2008-09-14 7 views
33

Je viens de commencer à écraser 'Debugging MS .Net 2.0 Applications' par John Robbins, et je suis devenu confus par son évangélisation pour Debug.Assert (...).Debug.Assert vs exceptions spécifiques lancées

Il souligne que Affirme bien mis en œuvre stocker l'état, un peu, d'une condition d'erreur, par exemple:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter"); 

Maintenant, personnellement, il me semble fou pour qu'il aime tant retraiter son test sans commentaire logique logique d'entreprise, peut-être "i < = 3 ne doit jamais arriver à cause du processus de widgitification de flobittyjam". Donc, je pense que j'obtiens des assertions comme un genre de bas niveau "Supposons mes hypothèses" ... en supposant que l'on pense que c'est un test qu'il suffit de faire en débogage - c'est à dire que vous êtes vous protéger contre vos collègues et futurs programmeurs, et espérer qu'ils testent réellement les choses. Mais ce que je ne comprends pas, il continue en disant que vous devriez utiliser des assertions en plus de la gestion des erreurs normales; maintenant ce que je prévois est quelque chose comme ceci:

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status"); 
if (i <= 3) 
{ 
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString()); 
} 

Qu'ai-je gagné par la répétition Debug.Assert du test de condition d'erreur? Je pense que je l'obtenir si nous parlions de double contrôle de débogage uniquement d'un calcul très important ...

double interestAmount = loan.GetInterest(); 
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc"); 

... mais je ne suis pas pour les tests de paramètres qui sont sûrement la peine vérification (dans les versions DEBUG et Release) ... ou non. Qu'est-ce que je rate?

Répondre

48

Les assertions ne sont pas destinées à la vérification des paramètres. La vérification des paramètres doit toujours être effectuée (et précisément en fonction des conditions préalables spécifiées dans votre documentation et/ou spécification), et le ArgumentOutOfRangeException doit être lancé si nécessaire.

Les assertions servent à tester des situations "impossibles", c'est-à-dire des choses que vous (dans votre logique de programme) supposez sont vraies. Les assertions sont là pour vous dire si ces hypothèses sont brisées pour une raison quelconque.

Espérons que cela aide!

+10

assertions peuvent être utilisées pour la vérification du paramètre de * appels de méthodes internes d'* (appelée par le code appartenant au même composant) des procédés, par opposition à des appels de méthodes externes (appelé par un autre composant) . Par exemple, je pourrais affirmer qu'un paramètre de méthode privé de type Double n'est pas un NaN. – RoadWarrior

+0

@Chris: Vous indiquez que les assertions ne doivent pas être utilisées pour la vérification des paramètres. Y a-t-il une raison à cela? J'ai tendance à jeter des exceptions pour les contrôles de paramètres, en particulier dans les constructeurs lorsque j'injecte des objets dépendants. Cependant, on me dit d'utiliser Assertions. Je n'ai pas d'explication logique à l'utilisation d'exceptions plutôt que d'affirmations. Êtes-vous capable de clarifier? Cheers –

+7

Ne vous inquiétez pas, j'ai découvert pourquoi. Selon Jon Skeet, http://stackoverflow.com/questions/1276308/exception-vs-assertion: "Utilisez des assertions pour les vérifications logiques internes dans votre code, et des exceptions normales pour les conditions d'erreur en dehors du contrôle de votre code immédiat." –

2

IMO c'est une perte de temps de développement seulement. Une exception correctement mise en œuvre vous donne une image claire de ce qui s'est passé. J'ai vu trop applications montrant obscur "Échec de l'assertion: i < 10" erreurs. Je vois l'affirmation comme une solution temporaire. À mon avis, aucune affirmation ne devrait figurer dans une version finale d'un programme. Dans ma pratique, j'ai utilisé des assertions pour des contrôles rapides et sales. La version finale du code devrait prendre en compte la situation erronée et se comporter en conséquence. Si quelque chose se passe mal, vous avez 2 choix: le gérer ou le laisser. La fonction devrait lancer une exception avec une description significative si les mauvais paramètres sont passés. Je ne vois aucun point dans la duplication de la logique de validation.

4

J'utilise des contrôles explicites qui jettent des exceptions sur publiques et protégées des méthodes et des affirmations sur des méthodes privées.

Généralement, les vérifications explicites empêchent de toute façon les méthodes privées de voir des valeurs incorrectes. Donc vraiment, l'affirmation vérifie une condition qui devrait être impossible. Si une affirmation tire, elle me dit qu'il y a un défaut dans la logique de validation contenue dans l'une des routines publiques de la classe.

17

Il y a un aspect de la communication à affirmer par rapport au lancement d'exception. Disons que nous avons une classe User avec une propriété Name et une méthode ToString.

Si ToString est mis en œuvre comme ceci:

public string ToString() 
{ 
    Debug.Assert(Name != null); 
    return Name; 
} 

Il dit ce nom ne devrait jamais nulle et il y a un bogue dans la classe utilisateur si elle est.

Si ToString est mise en œuvre comme ceci:

public string ToString() 
{ 
    if (Name == null) 
    { 
      throw new InvalidOperationException("Name is null"); 
    } 

    return Name; 
} 

Il dit que l'appelant utilise ToString correctement si le nom est nul et doit vérifier que avant d'appeler.

La mise en œuvre à la fois

public string ToString() 
{ 
    Debug.Assert(Name != null); 
    if (Name == null) 
    { 
      throw new InvalidOperationException("Name is null"); 
    } 

    return Name; 
} 

dit que si le nom est nul, il bug dans la classe User, mais nous voulons le traiter de toute façon. (L'utilisateur n'a pas besoin de vérifier le nom avant d'appeler.) Je pense que c'est le genre de sécurité recommandé par Robbins.

3

Une exception peut être interceptée et avalée, rendant l'erreur invisible aux tests. Cela ne peut pas arriver avec Debug.Assert. Personne ne devrait jamais avoir un gestionnaire de catch qui attrape toutes les exceptions, mais les gens le font quand même, et parfois c'est inévitable. Si votre code est appelé à partir de COM, la couche d'interopérabilité intercepte toutes les exceptions et les transforme en codes d'erreur COM, ce qui signifie que vous ne verrez pas vos exceptions non gérées. Les affirmations ne souffrent pas de cela.

De même, si l'exception n'est pas gérée, il est toujours préférable d'effectuer une mini-sauvegarde. Un domaine où VB est plus puissant que C# est que vous pouvez utiliser un filtre d'exception pour capturer une mini-sauvegarde lorsque l'exception est en vol, et laisser le reste de la gestion des exceptions inchangé. Gregg Miskelly's blog post on exception filter inject fournit un moyen utile de le faire à partir de C#.

Une autre remarque sur les actifs ... ils réagissent mal à l'unité en testant les conditions d'erreur dans votre code. Il vaut la peine d'avoir un emballage pour désactiver l'affirmation pour vos tests unitaires.

0

Voici 2 cents.

Je pense que le meilleur moyen est d'utiliser à la fois des assertions et des exceptions. Les principales différences entre les deux méthodes, à savoir, si les instructions Assert peuvent être facilement supprimées du texte de l'application (définit, attributs conditionnels ...), alors que Exception thrown dépend (tipiquement) d'un code conditionnel plus difficile à supprimer (section multine avec des conditions de préprocesseur).

Chaque exception d'application doit être gérée correctement, tandis que les assertions ne doivent être satisfaites que pendant le développement et le test de l'algorithme.

Si vous transmettez une référence d'objet null en tant que paramètre de routine et que vous utilisez cette valeur, vous obtenez une exception de pointeur NULL. En effet: pourquoi devriez-vous écrire une assertion? C'est une perte de temps dans ce cas. Mais qu'en est-il des membres de classe privée utilisés dans les routines de classe? Lorsque ces valeurs sont définies quelque part, il est préférable de vérifier avec une assertion si une valeur nulle est définie. C'est seulement parce que lorsque vous utilisez le membre, vous obtenez une exception de pointeur nul mais vous ne savez pas comment la valeur a été définie. Cela provoque un redémarrage de la rupture du programme sur tous les points d'entrée utilisés pour définir le membre privé.

Les exceptions sont plus utiles, mais elles peuvent être (imho) très lourdes à gérer et il y a la possibilité d'utiliser trop d'exceptions. Et ils nécessitent une vérification supplémentaire, peut-être indésirable pour optimiser le code. Personnellement, je n'utilise les exceptions que lorsque le code requiert un contrôle de capture profond (les instructions catch sont très faibles dans la pile d'appels) ou lorsque les paramètres de la fonction ne sont pas codés en dur dans le code.

4

J'ai longuement réfléchi à la question de la mise au point et de l'affirmation en ce qui concerne les problèmes de test.

Vous devriez être en mesure de tester votre classe avec une entrée erronée, un mauvais état, un ordre d'opération invalide et toute autre condition d'erreur imaginable et une affirmation jamais. Chaque affirmation vérifie quelque chose devrait toujours être vrai indépendamment des entrées ou des calculs effectués.

bonnes règles de base que j'ai arrivé à:

  1. Affirme ne sont pas un remplacement pour un code robuste qui fonctionne correctement indépendamment de la configuration. Ils sont complémentaires.

  2. Les assertions ne doivent jamais être déclenchées pendant un test unitaire, même en cas d'alimentation de valeurs non valides ou de conditions d'erreur de test. Le code doit gérer ces conditions sans que cela se produise.

  3. Si un assert se déclenche (soit dans un test unitaire soit pendant un test), la classe est mise en veille.

Pour toutes les autres erreurs - généralement vers le bas à l'environnement (connexion réseau perdu) ou une utilisation incorrecte (l'appelant a adopté une valeur null) - il est beaucoup plus agréable et plus compréhensible d'utiliser des contrôles durs & exceptions. Si une exception se produit, l'appelant sait que c'est probablement sa faute. Si une affirmation se produit, l'appelant sait qu'il s'agit probablement d'un bogue dans le code où se trouve l'assertion.

En ce qui concerne la duplication: Je suis d'accord. Je ne vois pas pourquoi vous devriez répliquer la validation avec un Debug.Assert ET une vérification d'exception. Non seulement cela ajoute un peu de bruit au code et trouble les eaux en ce qui concerne qui est en faute, mais c'est une forme de répétition.

1

Exemple d'une bonne utilisation de Assertion:

Debug.Assert(flibbles.count() < 1000000, "too many flibbles"); // indicate something is awry 
log.warning("flibble count reached " + flibbles.count()); // log in production as early warning 

Je pense personnellement que Assertion devrait ne être utilisé quand vous savez que quelque chose est en dehors limites souhaitables, mais vous pouvez être sûr qu'il est raisonnablement sûr continuer. Dans toutes les autres circonstances (n'hésitez pas à signaler des circonstances auxquelles je n'ai pas pensé), utilisez des exceptions pour échouer rapidement et sûrement. Le principal problème pour moi est de savoir si vous voulez faire tomber un système live/production avec une exception pour éviter la corruption et faciliter le dépannage, ou si vous avez rencontré une situation qui ne devrait jamais être laissée inaperçue dans test/versions de débogage, mais pourrait être autorisé à continuer en production (enregistrement d'un avertissement bien sûr).

cf.http://c2.com/cgi/wiki?FailFast copiée et modifiée de question java: Exception Vs Assertion