Quand dois-je utiliser Debug.Assert ()?

220

Je suis ingénieur logiciel professionnel depuis environ un an maintenant, après avoir obtenu un diplôme CS. Je connais les assertions depuis un certain temps en C ++ et C, mais je ne savais pas du tout qu'elles existaient en C # et .NET jusqu'à récemment.

Notre code de production ne contient aucune assertion que ce soit et ma question est la suivante ...

Dois-je commencer à utiliser Asserts dans notre code de production? Et si oui, quand son utilisation est-elle la plus appropriée? Serait-il plus logique de faire

Debug.Assert(val != null);

ou

if ( val == null )
    throw new exception();
Nicholas Mancuso
la source
2
La dichotomie que vous mettez en place est l'indice. Il ne s'agit ni de - ni d'exceptions et d'affirmations, à la fois - et de code défensif. Quand faire, c'est ce que vous cherchez à comprendre.
Casper Leon Nielsen
5
J'ai lu une fois que quelqu'un suggérait qu'une exception ou une autre méthode de plantage est appropriée pour les conditions où "il n'y a aucun moyen que je puisse raisonnablement m'en remettre", et en outre une assertion est appropriée pour les conditions où "cela ne devrait jamais arriver, jamais." Mais quelles circonstances réalistes remplissent ces dernières conditions sans satisfaire également les premières? Issu d'un arrière-plan Python où les assertions restent en production, je n'ai jamais compris l'approche Java / C # consistant à désactiver une partie de votre validation en production. Le seul cas pour lequel je peux vraiment voir, c'est si la validation coûte cher.
Mark Amery
2
Personnellement, j'utilise des exceptions pour les méthodes publiques et des assertions pour les méthodes privées.
Fred

Réponses:

230

Dans le débogage des applications Microsoft .NET 2.0, John Robbins a une grande section sur les assertions. Ses principaux points sont:

  1. Affirmez libéralement. Vous ne pouvez jamais avoir trop d'affirmations.
  2. Les assertions ne remplacent pas les exceptions. Les exceptions couvrent les exigences de votre code; les assertions couvrent les choses qu'elle suppose.
  3. Une assertion bien écrite peut vous dire non seulement ce qui s'est passé et où (comme une exception), mais pourquoi.
  4. Un message d'exception peut souvent être cryptique, vous obligeant à reculer dans le code pour recréer le contexte à l'origine de l'erreur. Une assertion peut conserver l'état du programme au moment où l'erreur s'est produite.
  5. Les assertions font également office de documentation, indiquant aux autres développeurs les hypothèses implicites dont dépend votre code.
  6. La boîte de dialogue qui apparaît en cas d'échec d'une assertion vous permet d'attacher un débogueur au processus, vous pouvez donc fouiller la pile comme si vous y aviez placé un point d'arrêt.

PS: Si vous avez aimé Code Complete, je vous recommande de le suivre avec ce livre. Je l'ai acheté pour en savoir plus sur l'utilisation de WinDBG et des fichiers de vidage, mais la première moitié est remplie de conseils pour éviter les bogues en premier lieu.

Rory MacLeod
la source
3
+1 pour le résumé concis et utile. Très directement applicable. La principale chose qui me manque, cependant, est de savoir quand utiliser Trace.Assert contre Trace.Assert. C'est-à-dire quelque chose sur le fait que vous les voulez / ne les voulez pas dans votre code de production.
Jon Coombs
2
JonCoombs est "Trace.Assert vs Trace.Assert" une faute de frappe?
thelem
1
@thelem Peut-être que Jon voulait dire Debug.Assertcontre Trace.Assert. Ce dernier est exécuté dans une version Release ainsi qu'une version Debug.
DavidRR
Pourquoi devrais-je préférer Debug.Assert à lancer l'exception?
Barış Akkurt
86

Mettez Debug.Assert()partout dans le code où vous voulez avoir des contrôles d'intégrité pour garantir les invariants. Lorsque vous compilez une version Release (c.-à-d. Aucune DEBUGconstante de compilation), les appels à Debug.Assert()seront supprimés afin de ne pas affecter les performances.

Vous devez toujours lever des exceptions avant d'appeler Debug.Assert(). L'assertion s'assure simplement que tout est comme prévu pendant que vous êtes encore en développement.

Mark Cidade
la source
35
Pourriez-vous préciser pourquoi mettre une assertion si vous lâchez toujours une exception avant de l'appeler? Ou ai-je mal compris votre réponse?
Roman Starkov
2
@romkyns Vous devez toujours les inclure car si vous ne le faites pas, lorsque vous générez votre projet en mode Release , toutes les validations / vérifications d'erreurs disparaîtront.
Oscar Mederos
28
@Oscar Je pensais que c'était tout l'intérêt d'utiliser des assertions en premier lieu ... OK alors, alors vous mettez les exceptions devant eux - alors pourquoi mettre les assertions après?
Roman Starkov,
4
@superjos: Je ne suis pas d'accord: le point n ° 2 de la réponse de MacLeod indique que vous avez en effet besoin d'affirmation ET d'exceptions, mais pas au même endroit. Il est inutile de lancer un NullRefEx sur une variable et juste après de faire un Assert dessus (la méthode assert n'affichera jamais de boîte de dialogue dans ce cas, ce qui est tout le point d'assert). Ce que MacLeod signifie, c'est que dans certains endroits, vous aurez besoin d'une exception, dans d'autres, une assertion sera suffisante.
David
1
Cela peut devenir compliqué d'interpréter mon interprétation de la réponse de quelqu'un d'autre :) Quoi qu'il en soit, je vous accompagne à ce sujet: vous avez besoin des deux, et vous ne devriez pas mettre l'exception avant l'affirmation. Je ne suis pas sûr de la signification de «pas au même endroit». Encore une fois, refusant d'interpréter, je vais simplement énoncer mes pensées / préférences: mettre une ou plusieurs affirmations pour vérifier les conditions préalables avant qu'une opération ne commence, ou pour vérifier les postconditions après l'opération. Outre les assertions, et après elles de toute façon, vérifiez si quelque chose ne va pas et doit lever des exceptions.
superjos
52

À partir du code terminé

8 Programmation défensive

8.2 Assertions

Une assertion est un code utilisé pendant le développement - généralement une routine ou une macro - qui permet à un programme de se vérifier pendant son exécution. Lorsqu'une assertion est vraie, cela signifie que tout fonctionne comme prévu. Lorsqu'il est faux, cela signifie qu'il a détecté une erreur inattendue dans le code. Par exemple, si le système suppose qu'un fichier d'informations client n'aura jamais plus de 50 000 enregistrements, le programme peut contenir une affirmation selon laquelle le nombre d'enregistrements est inférieur ou égal à 50 000. Tant que le nombre d'enregistrements est inférieur ou égal à 50 000, l'assertion sera silencieuse. S'il rencontre plus de 50 000 enregistrements, cependant, il «affirmera» fortement qu'il y a une erreur dans le programme.

Les assertions sont particulièrement utiles dans les programmes volumineux et complexes et dans les programmes de haute fiabilité. Ils permettent aux programmeurs de supprimer plus rapidement les hypothèses d'interface incompatibles, les erreurs qui se glissent lorsque le code est modifié, etc.

Une assertion prend généralement deux arguments: une expression booléenne qui décrit l'hypothèse censée être vraie et un message à afficher si ce n'est pas le cas.

(…)

Normalement, vous ne voulez pas que les utilisateurs voient les messages d'assertion dans le code de production; les affirmations sont principalement destinées à être utilisées pendant le développement et la maintenance. Les assertions sont normalement compilées dans le code au moment du développement et compilées hors du code pour la production. Pendant le développement, les assertions éliminent les hypothèses contradictoires, les conditions inattendues, les mauvaises valeurs transmises aux routines, etc. Pendant la production, ils sont compilés hors du code afin que les assertions ne dégradent pas les performances du système.

Juan
la source
7
Alors, que se passe-t-il si un fichier d'informations client rencontré en production contient plus de 50 000 enregistrements? Si l'assertion est compilée en dehors du code de production et que cette situation n'est pas gérée autrement, cela ne pose-t-il pas problème?
DavidRR
1
@DavidRR Oui en effet. Mais dès que la production signale un problème et qu'un développeur (qui ne connaît pas bien ce code) débogue le problème, l'assertion échouera et le développeur saura immédiatement que le système n'est pas utilisé comme prévu.
Marc
48

FWIW ... Je trouve que mes méthodes publiques ont tendance à utiliser le if () { throw; }modèle pour s'assurer que la méthode est appelée correctement. Mes méthodes privées ont tendance à utiliser Debug.Assert().

L'idée est qu'avec mes méthodes privées, je suis celle sous contrôle, donc si je commence à appeler l'une de mes propres méthodes privées avec des paramètres incorrects, alors j'ai brisé ma propre hypothèse quelque part - je n'aurais jamais dû obtenir dans cet état. En production, ces affirmations privées devraient idéalement être un travail inutile car je suis censé garder mon état interne valide et cohérent. Contrairement aux paramètres donnés aux méthodes publiques, qui peuvent être appelés par n'importe qui au moment de l'exécution: j'ai encore besoin d'imposer des contraintes de paramètres en lançant des exceptions.

De plus, mes méthodes privées peuvent toujours lever des exceptions si quelque chose ne fonctionne pas au moment de l'exécution (erreur réseau, erreur d'accès aux données, mauvaises données récupérées à partir d'un service tiers, etc.). Mes affirmations sont juste là pour m'assurer que je n'ai pas brisé mes propres hypothèses internes sur l'état de l'objet.

Nicholas Piasecki
la source
3
Il s'agit d'une description très claire d'une bonne pratique et elle donne une réponse très raisonnable à la question posée.
Casper Leon Nielsen
42

Utilisez les assertions pour vérifier les hypothèses du développeur et les exceptions pour vérifier les hypothèses environnementales.

Justin R.
la source
31

Si j'étais vous je ferais:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

Ou pour éviter un contrôle d'état répété

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
Mark Ingram
la source
5
Comment cela résout-il le problème? Avec cela, le debug.assert devient inutile.
Quibblesome
43
Non, ce n'est pas le cas - il se décompose en code juste avant que l'exception ne soit levée. Si vous avez un try / catch ailleurs dans votre code, vous ne remarquerez peut-être même pas l'exception!
Mark Ingram
2
+1 J'ai eu beaucoup de problèmes où les gens essayaient / interceptaient simplement des exceptions sans rien faire, donc le suivi du bug était un problème
dance2die
5
Je suppose qu'il y a des cas où vous voudrez peut-être faire cela, mais vous ne devriez jamais attraper une exception générale!
Casebash
8
@MarkIngram -1 à votre réponse et +1 à votre commentaire le justifiant. C'est une bonne astuce pour certaines circonstances particulières, mais cela semble être une chose bizarre à faire en général pour toute validation.
Mark Amery
24

Si vous voulez des Asserts dans votre code de production (c'est-à-dire des versions Release), vous pouvez utiliser Trace.Assert au lieu de Debug.Assert.

Bien sûr, cela ajoute des frais généraux à votre exécutable de production.

De plus, si votre application s'exécute en mode interface utilisateur, la boîte de dialogue Assertion s'affiche par défaut, ce qui peut être un peu déconcertant pour vos utilisateurs.

Vous pouvez remplacer ce comportement en supprimant le DefaultTraceListener: consultez la documentation de Trace.Listeners dans MSDN.

En résumé,

  • Utilisez Debug.Assert généreusement pour aider à détecter les bogues dans les versions Debug.

  • Si vous utilisez Trace.Assert en mode interface utilisateur, vous souhaiterez probablement supprimer DefaultTraceListener pour éviter de déconcerter les utilisateurs.

  • Si la condition que vous testez est quelque chose que votre application ne peut pas gérer, vous feriez probablement mieux de lever une exception pour vous assurer que l'exécution ne se poursuit pas. Sachez qu'un utilisateur peut choisir d'ignorer une assertion.

Joe
la source
1
+1 pour avoir souligné la distinction cruciale entre Debug.Assert et Trace.Assert, puisque l'OP a spécifiquement posé des questions sur le code de production.
Jon Coombs
21

Les assertions sont utilisées pour intercepter l'erreur de votre programmeur, pas l'erreur de l'utilisateur. Ils ne doivent être utilisés que lorsqu'il n'y a aucune chance qu'un utilisateur puisse provoquer l'assertion à se déclencher. Si vous écrivez une API, par exemple, les assertions ne doivent pas être utilisées pour vérifier qu'un argument n'est pas nul dans une méthode qu'un utilisateur d'API pourrait appeler. Mais il pourrait être utilisé dans une méthode privée non exposée dans le cadre de votre API pour affirmer que VOTRE code ne passe jamais un argument nul lorsqu'il n'est pas censé le faire.

Je préfère généralement les exceptions aux assertions lorsque je ne suis pas sûr.

user19113
la source
11

En bref

Asserts sont utilisés pour les gardes et pour vérifier les contraintes de conception par contrat, à savoir:

  • Assertsdevrait être pour les versions de débogage et non-production uniquement. Les assertions sont généralement ignorées par le compilateur dans les versions Release.
  • Asserts peut vérifier les bogues / conditions inattendues qui SONT sous le contrôle de votre système
  • Asserts NE SONT PAS un mécanisme de validation de première ligne des entrées utilisateur ou des règles métier
  • Assertsne doit pas être utilisé pour détecter des conditions environnementales inattendues (qui échappent au contrôle du code), par exemple, manque de mémoire, défaillance du réseau, défaillance de la base de données, etc. Bien que rares, ces conditions sont à prévoir (et le code de votre application ne peut pas résoudre des problèmes tels que panne matérielle ou épuisement des ressources). En règle générale, des exceptions seront levées - votre application peut alors soit prendre des mesures correctives (par exemple réessayer une opération de base de données ou de réseau, tenter de libérer de la mémoire cache), soit abandonner gracieusement si l'exception ne peut pas être gérée.
  • Une assertion qui a échoué devrait être fatale à votre système - c'est-à-dire que contrairement à une exception, n'essayez pas d'attraper ou de gérer l'échec Asserts- votre code fonctionne en territoire inattendu. Les traces de pile et les vidages sur incident peuvent être utilisés pour déterminer ce qui n'a pas fonctionné.

Les affirmations ont un énorme avantage:

  • Pour aider à trouver la validation manquante des entrées utilisateur ou les bogues en amont dans le code de niveau supérieur.
  • Les assertions dans la base de code transmettent clairement les hypothèses faites dans le code au lecteur
  • Assert sera vérifié lors de l'exécution dans les Debugversions.
  • Une fois que le code a été testé de manière exhaustive, la reconstruction du code en tant que version supprimera la surcharge de performances de la vérification de l'hypothèse (mais avec l'avantage qu'une version de débogage ultérieure reviendra toujours aux vérifications, si nécessaire).

... Plus de détails

Debug.Assertexprime une condition qui a été supposée sur l'état par le reste du bloc de code sous le contrôle du programme. Cela peut inclure l'état des paramètres fournis, l'état des membres d'une instance de classe ou le retour d'un appel de méthode dans sa plage contractée / conçue. En règle générale, les assertions doivent planter le thread / processus / programme avec toutes les informations nécessaires (trace de pile, vidage sur incident, etc.), car elles indiquent la présence d'un bogue ou d'une condition non prise en compte qui n'a pas été conçue pour (c.-à-d. N'essayez pas d'attraper ou de gérer les échecs d'assertion), à une exception possible quand une assertion elle-même pourrait causer plus de dommages que le bogue (par exemple, les contrôleurs de la circulation aérienne ne voudraient pas d'un YSOD lorsqu'un aéronef passe en sous-marin, bien qu'il soit inutile de savoir si une version de débogage doit être déployée pour production ...)

Quand devez-vous utiliser Asserts? - À tout moment dans un système, une API de bibliothèque ou un service où les entrées d'une fonction ou de l'état d'une classe sont supposées valides (par exemple, lorsque la validation a déjà été effectuée sur les entrées utilisateur dans le niveau de présentation d'un système , les classes métier et données supposent généralement que les vérifications nulles, les vérifications de plage, les vérifications de longueur de chaîne, etc. en entrée ont déjà été effectuées). - Les Assertvérifications courantes incluent les cas où une hypothèse invalide entraînerait une déréférence nulle de l'objet, un diviseur nul, un dépassement arithmétique numérique ou de la date, et hors bande général / non conçu pour le comportement (par exemple, si un entier 32 bits a été utilisé pour modéliser l'âge d'un être humain). , il serait prudent Assertque l'âge soit en réalité compris entre 0 et 125 environ - les valeurs de -100 et 10 ^ 10 n'ont pas été conçues pour).

Contrats de code .Net
Dans la pile .Net, les contrats de code peuvent être utilisés en complément ou en remplacement de l' utilisation Debug.Assert. Les contrats de code peuvent formaliser davantage la vérification de l'état et peuvent aider à détecter les violations d'hypothèses au moment de la compilation (ou peu de temps après, s'ils sont exécutés en tant que vérification des antécédents dans un IDE).

Les contrôles de conception par contrat (DBC) disponibles comprennent:

  • Contract.Requires - Conditions préalables contractuelles
  • Contract.Ensures - Postconditions contractuelles
  • Invariant - Exprime une hypothèse sur l'état d'un objet à tous les points de sa durée de vie.
  • Contract.Assumes - pacifie le vérificateur statique lorsqu'un appel à des méthodes décorées non contractuelles est effectué.
StuartLC
la source
Malheureusement, les contrats de code sont presque morts depuis que MS a cessé de le développer.
Mike Lowery
10

Surtout jamais dans mon livre. Dans la grande majorité des cas, si vous voulez vérifier si tout est sain, jetez-le si ce n'est pas le cas.

Ce que je n'aime pas, c'est le fait que cela rend une version de débogage fonctionnellement différente d'une version. Si une assertion de débogage échoue mais que la fonctionnalité fonctionne dans la version, comment cela a-t-il un sens? C'est encore mieux lorsque l'asserteur a quitté l'entreprise depuis longtemps et que personne ne connaît cette partie du code. Ensuite, vous devez tuer une partie de votre temps à explorer le problème pour voir s'il s'agit vraiment d'un problème ou non. Si c'est un problème, pourquoi la personne ne lance-t-elle pas en premier lieu?

Pour moi, cela suggère en utilisant Debug.Assert que vous reportez le problème à quelqu'un d'autre, traitez le problème vous-même. Si quelque chose est censé être le cas et ce n'est pas le cas, lancez-le.

Je suppose qu'il existe peut-être des scénarios de performances critiques où vous souhaitez optimiser vos assertions et ils sont utiles là-bas, mais je n'ai pas encore rencontré un tel scénario.

Quibblesome
la source
4
Votre réponse mérite cependant un certain mérite car vous soulignez certaines préoccupations souvent soulevées à leur sujet, le fait qu'elles interrompent la session de débogage et la possibilité de faux positifs. Cependant, vous manquez quelques subtilités et écrivez "optimiser les assertions" - ce qui ne peut être basé que sur la pensée que lever une exception et faire debug.assert est la même chose. Ce n'est pas le cas, ils ont des objectifs et des caractéristiques différents, comme vous pouvez le voir dans certaines des réponses votées. Dw
Casper Leon Nielsen
+1 pour "Ce que je n'aime pas, c'est le fait que la version de débogage soit fonctionnellement différente d'une version de version. Si une assertion de débogage échoue mais que la fonctionnalité fonctionne dans la version, comment cela a-t-il un sens?" Dans .NET, System.Diagnostics.Trace.Assert()s'exécute dans une version Release ainsi qu'une version Debug.
DavidRR
7

Selon la norme IDesign , vous devez

Affirmez chaque hypothèse. En moyenne, chaque cinquième ligne est une affirmation.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Comme avertissement, je dois mentionner que je n'ai pas trouvé pratique de mettre en œuvre cet IRL. Mais c'est leur standard.

devlord
la source
On dirait que Juval Lowy aime se citer.
devlord
6

Utilisez les assertions uniquement dans les cas où vous souhaitez supprimer la vérification des versions. N'oubliez pas que vos assertions ne se déclencheront pas si vous ne compilez pas en mode débogage.

Compte tenu de votre exemple de vérification de la nullité, s'il s'agit d'une API interne uniquement, je pourrais utiliser une assertion. Si c'est dans une API publique, j'utiliserais certainement la vérification et la levée explicites.

Parc Derek
la source
Dans .NET, on peut utiliser System.Diagnostics.Trace.Assert()pour exécuter une assertion dans une version de version (production).
DavidRR
Règle d'analyse de code CA1062: La validation des arguments des méthodes publiques nécessite de vérifier un argument pour savoir nullquand: "Une méthode visible de l'extérieur déréférence l'un de ses arguments de référence sans vérifier si cet argument est nul ." Dans une telle situation, la méthode ou la propriété doit lancer ArgumentNullException.
DavidRR
6

Toutes les assertions doivent être du code pouvant être optimisé pour:

Debug.Assert(true);

Parce que vérifier quelque chose que vous avez déjà supposé est vrai. Par exemple:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

Dans ce qui précède, il existe trois approches différentes des paramètres nuls. Le premier l'accepte comme autorisé (il ne fait rien). Le second lève une exception pour le code appelant à gérer (ou non, ce qui entraîne un message d'erreur). Le troisième suppose que cela ne peut pas arriver et affirme qu'il en est ainsi.

Dans le premier cas, il n'y a pas de problème.

Dans le second cas, il y a un problème avec le code appelant - il n'aurait pas dû appeler GetFirstAndConsumeavec null, donc il récupère une exception.

Dans le troisième cas, il y a un problème avec ce code, car il aurait déjà dû être vérifié en != nullavant qu'il ne soit appelé, de sorte qu'il n'est pas vrai est un bug. Ou en d'autres termes, ce devrait être du code qui pourrait théoriquement être optimisé Debug.Assert(true), sicne en != nulldevrait toujours l'être true!

Jon Hanna
la source
1
Alors, dans le troisième cas, que se passe-t-il en == nullen production? Êtes-vous en train de dire que en == nullcela ne peut jamais arriver en production (puisque le programme a été complètement débogué)? Si c'est le cas, alors Debug.Assert(en != null)à tout le moins sert d'alternative à un commentaire. Bien sûr, si des modifications futures sont apportées, il continue également d'avoir une valeur pour détecter une régression possible.
DavidRR
@DavidRR, j'affirme en effet qu'il ne peut jamais être nul, tout comme l'affirmation dans le code, d'où le nom. Je pourrais bien sûr me tromper, ou me tromper en changeant, et c'est la valeur de l'appel d'assertion.
Jon Hanna
1
Les appels à Debug.Assert()sont supprimés dans une version Release. Donc, si vous êtes mal, dans le troisième cas , vous ne saurez pas dans la production ( en supposant l'utilisation d'une accumulation de presse dans la production). Cependant, le comportement des premier et deuxième cas est identique dans les versions Debug et Release.
DavidRR
@DavidRR, ce qui le rend approprié uniquement lorsque je considère que cela ne peut pas se produire, car encore une fois, c'est une affirmation de fait, pas une vérification de l'état. Bien sûr, il est également inutile d'avoir l'assertion, d'avoir un bogue qu'il attraperait et de ne jamais toucher ce cas lors des tests.
Jon Hanna
4

J'ai pensé que j'ajouterais quatre autres cas, où Debug.Assert peut être le bon choix.

1) Quelque chose que je n'ai pas vu mentionné ici est la couverture conceptuelle supplémentaire que les Asserts peuvent fournir pendant les tests automatisés . Comme exemple simple:

Lorsqu'un appelant de niveau supérieur est modifié par un auteur qui pense avoir étendu la portée du code pour gérer des scénarios supplémentaires, idéalement (!) Ils écriront des tests unitaires pour couvrir cette nouvelle condition. Il se peut alors que le code entièrement intégré semble fonctionner correctement.

Cependant, en réalité, une faille subtile a été introduite, mais non détectée dans les résultats des tests. Le non est devenu callee déterministe dans ce cas, et seulement arrive à donner le résultat escompté. Ou peut-être qu'il a produit une erreur d'arrondi qui n'a pas été remarquée. Ou a provoqué une erreur qui a été compensée également ailleurs. Ou accordé non seulement l'accès demandé mais des privilèges supplémentaires qui ne devraient pas être accordés. Etc.

À ce stade, les instructions Debug.Assert () contenues dans l'appelé couplées au nouveau cas (ou cas de bord) entraîné par des tests unitaires peuvent fournir une notification inestimable pendant le test que les hypothèses de l'auteur d'origine ont été invalidées, et le code ne doit pas être publié sans examen supplémentaire. Les assertions avec tests unitaires sont les partenaires parfaits.

2) De plus, certains tests sont simples à écrire, mais coûteux et inutiles compte tenu des hypothèses initiales . Par exemple:

Si un objet n'est accessible qu'à partir d'un certain point d'entrée sécurisé, une requête supplémentaire doit-elle être effectuée dans une base de données de droits réseau à partir de chaque méthode d'objet pour garantir que l'appelant dispose des autorisations? Sûrement pas. Peut-être que la solution idéale comprend la mise en cache ou une autre extension des fonctionnalités, mais la conception ne l'exige pas. Un Debug.Assert () affichera immédiatement lorsque l'objet a été attaché à un point d'entrée non sécurisé.

3) Ensuite, dans certains cas, votre produit peut n'avoir aucune interaction de diagnostic utile pour tout ou partie de ses opérations lorsqu'il est déployé en mode de publication . Par exemple:

Supposons qu'il s'agisse d'un périphérique embarqué en temps réel. Lancer des exceptions et redémarrer lorsqu'il rencontre un paquet mal formé est contre-productif. Au lieu de cela, l'appareil peut bénéficier d'un fonctionnement au meilleur effort, même au point de rendre du bruit dans sa sortie. Il peut également ne pas avoir d'interface humaine, de périphérique de journalisation ou même être physiquement accessible par l'homme lorsqu'il est déployé en mode de publication, et la sensibilisation aux erreurs est mieux fournie en évaluant la même sortie. Dans ce cas, les assertions libérales et les tests de pré-publication approfondis sont plus précieux que les exceptions.

4) Enfin, certains tests sont inutiles uniquement parce que l'appelé est perçu comme extrêmement fiable . Dans la plupart des cas, plus le code est réutilisable, plus l'effort a été mis pour le rendre fiable. Par conséquent, il est courant d'exceptionner les paramètres inattendus des appelants, mais d'assert pour les résultats inattendus des appels. Par exemple:

Si une String.Findopération principale indique qu'elle renverra un -1lorsque les critères de recherche ne sont pas trouvés, vous pourrez peut-être effectuer en toute sécurité une opération plutôt que trois. Cependant, s'il est effectivement retourné -2, vous n'aurez peut-être aucun plan d'action raisonnable. Il ne serait pas utile de remplacer le calcul plus simple par un calcul qui teste séparément une -1valeur, et déraisonnable dans la plupart des environnements de versions pour surcharger votre code de tests garantissant que les bibliothèques principales fonctionnent comme prévu. Dans ce cas, les assertions sont idéales.

Shannon
la source
4

Citation tirée du programmeur pragmatique: du compagnon au maître

Laisser les assertions activées

Il existe un malentendu commun au sujet des assertions, promulgué par les personnes qui écrivent des compilateurs et des environnements linguistiques. Ca fait plutot comme ca:

Les assertions ajoutent une surcharge au code. Parce qu'ils vérifient les choses qui ne devraient jamais se produire, ils ne seront déclenchés que par un bogue dans le code. Une fois que le code a été testé et expédié, il n'est plus nécessaire et doit être désactivé pour accélérer l'exécution du code. Les assertions sont une fonction de débogage.

Il y a ici deux hypothèses manifestement erronées. Tout d'abord, ils supposent que les tests détectent tous les bogues. En réalité, pour tout programme complexe, il est peu probable que vous testiez même un pourcentage infime des permutations que votre code subira (voir Ruthless Testing).

Deuxièmement, les optimistes oublient que votre programme s'exécute dans un monde dangereux. Pendant les tests, les rats ne rongeront probablement pas à travers un câble de communication, quelqu'un jouant à un jeu n'épuisera pas la mémoire et les fichiers journaux ne rempliront pas le disque dur. Ces choses peuvent se produire lorsque votre programme s'exécute dans un environnement de production. Votre première ligne de défense vérifie toute erreur possible, et votre seconde utilise des assertions pour essayer de détecter celles que vous avez manquées.

Désactiver des affirmations lorsque vous livrez un programme à la production, c'est comme traverser un fil sans fil parce que vous l'avez déjà fait en pratique . La valeur est dramatique, mais il est difficile d'obtenir une assurance-vie.

Même si vous rencontrez des problèmes de performances, désactivez uniquement les assertions qui vous touchent vraiment .

Teoman shipahi
la source
2

Vous devez toujours utiliser la deuxième approche (lever des exceptions).

De plus, si vous êtes en production (et avez une version de build), il est préférable de lever une exception (et de laisser l'application se bloquer dans le pire des cas) que de travailler avec des valeurs non valides et peut-être détruire les données de votre client (ce qui peut coûter des milliers de dollars).

Thomas Danecker
la source
1
Non, même chose que pour certaines autres réponses ici: vous ne comprenez pas vraiment la différence, alors vous vous retirez de l'une des offres, créant une fausse dichotomie entre elles dans le processus. Dw
Casper Leon Nielsen
3
C'est la seule bonne réponse sur cette liste OMI. Ne le rejetez pas si facilement Casper. Debug Assert est un anti-modèle. Si son invariant au moment du débogage son invariant à l'exécution. Le fait de permettre à votre application de continuer avec un invariant cassé vous laisse dans un état non déterministe et présente des problèmes potentiellement pires que le plantage. OMI, il est préférable d'avoir le même code dans les deux versions qui échouent rapidement avec des contrats rompus, puis d'implémenter une gestion des erreurs robuste au niveau supérieur. Par exemple, isoler les composants et implémenter une capacité à les redémarrer (comme un onglet qui plante dans un navigateur ne plante pas tout le navigateur).
justin.m.chase
1
Il pourrait être utile d'inclure Trace.Assert dans votre discussion ici, car il ne peut pas être rejeté par le même argument.
Jon Coombs
0

Vous devez utiliser Debug.Assert pour tester les erreurs logiques dans vos programmes. Le compliant ne peut que vous informer des erreurs de syntaxe. Vous devez donc absolument utiliser les instructions Assert pour tester les erreurs logiques. Par exemple, tester un programme qui vend des voitures que seules les BMW bleues devraient bénéficier d'une remise de 15%. Le compliant ne pourrait rien vous dire si votre programme est logiquement correct pour effectuer cela, mais une déclaration assert pourrait le faire.

orlando calresian
la source
2
désolé mais les exceptions font tout de même, donc cette réponse ne répond pas à la vraie question.
Roman Starkov
0

J'ai lu les réponses ici et j'ai pensé que je devrais ajouter une distinction importante. Il existe deux manières très différentes d'utiliser les assertions. L'une est un raccourci temporaire pour développeur pour «Cela ne devrait pas vraiment se produire, donc s'il me le fait savoir pour que je puisse décider quoi faire», un peu comme un point d'arrêt conditionnel, dans les cas où votre programme peut continuer. L'autre est un moyen de mettre des hypothèses sur les états de programme valides dans votre code.

Dans le premier cas, les assertions n'ont même pas besoin d'être dans le code final. Tu devrais utiliserDebug.Assert pendant le développement et vous pouvez les supprimer si / quand vous n'en avez plus besoin. Si vous voulez les laisser ou si vous oubliez de les supprimer, aucun problème, car ils n'auront aucune conséquence dans les compilations Release.

Mais dans le second cas, les assertions font partie du code. Ils affirment que vos hypothèses sont vraies et les documentent également. Dans ce cas, vous voulez vraiment les laisser dans le code. Si le programme est dans un état non valide, il ne doit pas être autorisé à continuer. Si vous ne pouviez pas vous permettre d'atteindre les performances, vous n'utiliseriez pas C #. D'une part, il peut être utile de pouvoir attacher un débogueur si cela se produit. De l'autre, vous ne voulez pas que la trace de pile apparaisse sur vos utilisateurs et peut-être plus important que vous ne vouliez pas qu'ils puissent l'ignorer. De plus, s'il est dans un service, il sera toujours ignoré. Par conséquent, en production, le comportement correct serait de lever une exception et d'utiliser la gestion normale des exceptions de votre programme, ce qui pourrait montrer à l'utilisateur un joli message et enregistrer les détails.

Trace.Asserta le moyen idéal pour y parvenir. Il ne sera pas supprimé en production et peut être configuré avec différents écouteurs à l'aide de app.config. Ainsi, pour le développement, le gestionnaire par défaut est correct, et pour la production, vous pouvez créer un TraceListener simple comme ci-dessous qui lève une exception et l'activer dans le fichier de configuration de production.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

Et dans le fichier de configuration de production:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
AlexDev
la source
-1

Je ne sais pas comment c'est en C # et .NET, mais en C, assert () ne fonctionnera que s'il est compilé avec -DDEBUG - l'utilisateur final ne verra jamais un assert () s'il est compilé sans. C'est uniquement pour les développeurs. Je l'utilise très souvent, il est parfois plus facile de suivre les bugs.

ne pas exister
la source
-1

Je ne les utiliserais pas dans le code de production. Jetez les exceptions, attrapez et connectez.

Vous devez également être prudent dans asp.net, car une assertion peut apparaître sur la console et geler la ou les demandes.

mattlant
la source