Question

Lors de la programmation par contrat d'une fonction ou d'une méthode, commence par vérifier si ses conditions préalables sont remplies avant de commencer à assumer ses responsabilités, n'est-ce pas? Les deux méthodes les plus importantes pour effectuer ces vérifications sont assert et exception .

  1. assert échoue uniquement en mode débogage. Pour vous assurer qu’il est crucial de tester (à l’unité) toutes les conditions préalables de contrat distinctes pour voir si elles échouent réellement.
  2. exception échoue en mode débogage et libération. Cela a pour avantage que le comportement de débogage testé est identique au comportement de publication, mais entraîne une pénalité de performances d'exécution.

Selon vous, lequel est préférable?

Voir la question récurrée ici

.
Était-ce utile?

La solution

Désactiver les assertions dans les versions de version revient à dire "je n’aurai jamais de problème dans une version de version", ce qui n’est souvent pas le cas. Donc, affirmer ne devrait pas être désactivé dans une version release. Mais vous ne voulez pas que la version de publication se bloque chaque fois que des erreurs se produisent, non?

Donc, utilisez les exceptions et utilisez-les bien. Utilisez une bonne hiérarchie d’exceptions solide et assurez-vous que vous capturez et vous pouvez mettre un crochet sur l’exception lancée dans votre débogueur pour l’intercepter, et en mode de publication vous pouvez compenser l’erreur plutôt qu’un crash direct. C'est la voie la plus sûre.

Autres conseils

En règle générale, vous devez utiliser des assertions lorsque vous essayez de détecter vos propres erreurs et des exceptions lorsque vous essayez de détecter des erreurs d’autres personnes. En d'autres termes, vous devez utiliser des exceptions pour vérifier les conditions préalables pour les fonctions de l'API publique et chaque fois que vous obtenez des données externes à votre système. Vous devez utiliser des assertions pour les fonctions ou les données internes à votre système.

Le principe que je suis est le suivant: si une situation peut être évitée de manière réaliste en codant, utilisez une assertion. Sinon, utilisez une exception.

Les affirmations visent à garantir que le contrat est respecté. Le contrat doit être équitable, de sorte que le client doit être en mesure de s’assurer qu’il se conforme. Par exemple, vous pouvez indiquer dans un contrat qu'une adresse URL doit être valide, car les règles relatives à ce qui est ou non une adresse URL valide sont connues et cohérentes.

Les exceptions concernent les situations hors du contrôle du client et du serveur. Une exception signifie que quelque chose a mal tourné et que rien n’aurait pu être fait pour l’éviter. Par exemple, la connectivité réseau ne relève pas du contrôle des applications. Vous ne pouvez donc rien faire pour éviter une erreur réseau.

J'aimerais ajouter que la distinction Assertion / Exception n'est pas vraiment la meilleure façon de penser. Ce que vous voulez vraiment penser, c'est le contrat et la manière de le faire respecter. Dans l'exemple de mon URL ci-dessus, la meilleure chose à faire est d'avoir une classe qui encapsule une URL et qui est soit Null, soit une URL valide. C'est la conversion d'une chaîne en une URL qui applique le contrat. Une exception est levée si elle n'est pas valide. Une méthode avec un paramètre d'URL est beaucoup plus claire qu'une méthode avec un paramètre de chaîne et une assertion qui spécifie une URL.

Les affirmations servent à attraper quelque chose qu'un développeur a mal fait (pas seulement vous-même - un autre développeur de votre équipe également). S'il est raisonnable qu'une erreur d'utilisateur puisse créer cette condition, il devrait alors s'agir d'une exception.

Pensez également aux conséquences. Une affirmation ferme généralement l'application. Si l’on s’attend raisonnablement à ce que la condition puisse être récupérée, vous devriez probablement utiliser une exception.

D'autre part, si le problème uniquement peut être dû à une erreur du programmeur, utilisez une assertion, car vous souhaitez le connaître le plus rapidement possible. Une exception peut être interceptée et gérée et vous ne le saurez jamais. Et oui, vous devez désactiver les assertions dans le code de version car vous souhaitez que l'application récupère s'il y a la moindre chance. Même si l'état de votre programme est profondément altéré, l'utilisateur pourrait peut-être sauvegarder son travail.

Il n'est pas tout à fait vrai que "l'assertion échoue uniquement en mode débogage".

Dans Construction de logiciels orientés objets, 2e édition de Bertrand Meyer, l'auteur laisse une porte ouverte pour la vérification des conditions préalables en mode de validation. Dans ce cas, quand une assertion échoue, il se produit ... une exception de violation d'assertion est déclenchée! Dans ce cas, la situation ne permet pas de remédier à la situation. Cependant, il est possible de réaliser quelque chose d'utile: générer un rapport d'erreur et, dans certains cas, redémarrer l'application.

La motivation derrière ceci est que les conditions préalables sont généralement moins chères à tester que les invariants et les conditions postérieures, et que, dans certains cas, la correction et la "sécurité" dans la version release sont plus importants que la vitesse. C'est-à-dire que pour de nombreuses applications, la vitesse n'est pas un problème, mais la robustesse (la capacité du programme à se comporter de manière sécurisée lorsque son comportement n'est pas correct, c'est-à-dire lorsqu'un contrat est rompu) est.

Devriez-vous toujours laisser les vérifications de précondition activées? Ça dépend. C'est à vous. Il n'y a pas de réponse universelle. Si vous créez des logiciels pour une banque, il peut être préférable d'interrompre l'exécution avec un message alarmant plutôt que de transférer 1 000 000 USD au lieu de 1 000 USD. Mais que se passe-t-il si vous programmez un jeu? Peut-être avez-vous besoin de toute la vitesse que vous pouvez obtenir, et si quelqu'un obtient 1000 points au lieu de 10 à cause d'un bogue que les conditions préalables ne rencontraient pas (car elles ne sont pas activées), pas de chance.

Dans les deux cas, vous devriez idéalement avoir détecté ce bogue lors des tests et effectuer une partie importante de vos tests avec les assertions activées. Ce qui est discuté ici, c'est quelle est la meilleure politique à suivre pour les rares cas où les conditions préalables échouent dans le code de production dans un scénario qui n'avait pas été détecté auparavant en raison de tests incomplets.

Pour résumer, vous pouvez avoir des assertions tout en obtenant les exceptions automatiquement , si vous les laissez activées, du moins dans Eiffel. Je pense que pour faire la même chose en C ++, vous devez taper vous-même.

Voir aussi: Quand les assertions doivent-elles rester dans le code de production?

Il y a eu un énorme coppro , I Si vous n'êtes pas sûr qu'une assertion puisse être désactivée dans une version validée, croyez qu'elle ne devrait pas être une affirmation. Les assertions doivent protéger contre les invariants de programme brisés. Dans un tel cas, en ce qui concerne le client de votre code, il y aura l'un des deux résultats possibles:

  1. Mourir avec une sorte de défaillance du type de système d'exploitation, entraînant un appel à l'abandon. (Sans affirmation)
  2. Mourir via un appel direct pour annuler. (Avec affirmation)

Il n'y a aucune différence pour l'utilisateur. Toutefois, il est possible que les assertions ajoutent un coût de performance inutile dans le code présent dans la grande majorité des exécutions où le code n'échoue pas.

La réponse à la question dépend beaucoup plus de l'identité des clients de l'API. Si vous écrivez une bibliothèque fournissant une API, vous avez besoin d'un mécanisme pour informer vos clients qu'ils n'ont pas utilisé l'API correctement. À moins que vous ne fournissiez deux versions de la bibliothèque (l’une avec des assertions, l’autre sans), alors l’affirmation est très improbable.

Personnellement, cependant, je ne suis pas sûr d’accepter des exceptions pour ce cas non plus. Les exceptions conviennent mieux aux endroits où une forme appropriée de récupération peut avoir lieu. Par exemple, vous essayez peut-être d'allouer de la mémoire. Lorsque vous attrapez une exception 'std :: bad_alloc', il peut être possible de libérer de la mémoire et de réessayer.

J'ai exposé ici mon point de vue sur l'état de la question: Comment valider l’état interne d’un objet? . En règle générale, faites valoir vos revendications et lancez-les pour violation par d'autres. Pour désactiver les assertions dans les versions de version, vous pouvez effectuer les opérations suivantes:

  • Désactiver les assertions pour les contrôles coûteux (par exemple, vérifier si une plage est commandée)
  • Laissez les contrôles simples activés (comme la recherche d'un pointeur null ou d'une valeur booléenne)

Bien sûr, dans les versions release, les assertions échouées et les exceptions non capturées doivent être gérées autrement que dans les versions debug (où il pourrait simplement appeler std :: abort). Ecrivez un journal de l'erreur quelque part (éventuellement dans un fichier), informez le client qu'une erreur interne s'est produite. Le client pourra vous envoyer le fichier journal.

vous parlez de la différence entre les erreurs de conception et d'exécution.

affirme être "bon programmeur, c'est des notifications brisées", elles sont là pour vous rappeler des bugs que vous n'auriez pas remarqués lorsqu'ils se sont produits.

Les exceptions

sont les notifications 'bon utilisateur, quelque chose qui va mal (vous pouvez évidemment coder pour les intercepter afin que l'utilisateur ne soit jamais averti), mais elles sont conçues pour se produire au moment de l'exécution lorsque l'utilisateur utilise Joe.

Donc, si vous pensez pouvoir éliminer tous vos bogues, utilisez uniquement des exceptions. Si vous pensez que vous ne pouvez pas, utilisez des exceptions. Vous pouvez toujours utiliser les assertions de débogage pour réduire le nombre d'exceptions, bien sûr.

N'oubliez pas que beaucoup de conditions préalables seront des données fournies par l'utilisateur. Vous aurez donc besoin d'un bon moyen d'informer l'utilisateur que ses données ne sont pas bonnes. Pour ce faire, vous devrez souvent renvoyer les données d'erreur de la pile d'appels aux bits avec lesquels il interagit. Les assertions ne seront alors plus utiles - et encore plus si votre application est à plusieurs niveaux.

Enfin, je n’utiliserais ni l’un ni l’autre - les codes d’erreur sont bien supérieurs aux erreurs qui, à votre avis, se produiraient régulièrement. :)

Je préfère le second. Murphy indique que quelque chose d'inattendu se passera mal. Ainsi, au lieu d’obtenir une exception lors de l’appel de méthode erroné, vous finissez par tracer une NullPointerException (ou l’équivalent) 10 images de pile plus profondes.

Les réponses précédentes sont correctes: utilisez des exceptions pour les fonctions de l'API publique. Le seul moment où vous voudrez peut-être plier cette règle est lorsque le contrôle est coûteux en calcul. Dans ce cas, vous pouvez l'affirmer.

Si vous pensez qu'une violation de cette condition préalable est probable, conservez-la à titre d'exception ou modifiez la condition préalable.

Vous devriez utiliser les deux. Les assertions sont pour votre commodité en tant que développeur. Les exceptions interceptent des choses que vous avez manquées ou auxquelles vous ne vous attendiez pas pendant l'exécution.

Je me suis attaché à les fonctions de rapport d'erreur de glib au lieu des assertions les plus anciennes. Ils se comportent comme des affirmations, mais au lieu d’arrêter le programme, ils renvoient une valeur et laissent le programme se poursuivre. Cela fonctionne étonnamment bien et, en prime, vous voyez ce qu'il advient du reste de votre programme lorsqu'une fonction ne renvoie pas "ce qu'elle est supposée". En cas de blocage, vous savez que la vérification de vos erreurs est laxiste ailleurs.

Dans mon dernier projet, j’utilisais ce style de fonctions pour implémenter la vérification préalable, et si l’une d’entre elles échouait, j’imprimais une trace de pile dans le fichier journal, mais je continuais à courir. M'a fait gagner beaucoup de temps de débogage lorsque d'autres personnes rencontraient un problème lors de l'exécution de ma version de débogage.

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

Si j'avais besoin de vérifier les arguments à l'exécution, je le ferais:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}

J'ai essayé de synthétiser plusieurs des autres réponses ici avec mes propres vues.

Utilisez des assertions pour les cas où vous souhaitez le désactiver en production, préférant le laisser entrer. La seule vraie raison de désactiver en production, mais pas en développement, est d’accélérer le programme. Dans la plupart des cas, cette accélération ne sera pas significative, mais parfois le code est critique ou le test est coûteux en calcul. Si le code est essentiel à la mission, les exceptions peuvent être meilleures malgré le ralentissement.

S'il existe un risque réel de récupération, utilisez une exception, car les assertions ne sont pas conçues pour être récupérées. Par exemple, le code est rarement conçu pour remédier aux erreurs de programmation, mais aux facteurs tels que les défaillances du réseau ou les fichiers verrouillés. Les erreurs ne doivent pas être traitées comme des exceptions simplement parce qu'elles échappent au contrôle du programmeur. Au contraire, la prévisibilité de ces erreurs, comparée aux erreurs de codage, les rend plus aimables pour la récupération.

Argument qu'il est plus facile de déboguer des assertions: la trace de pile d'une exception correctement nommée est aussi facile à lire qu'une assertion. Un bon code ne devrait capturer que des types spécifiques d'exceptions, les exceptions ne doivent donc pas passer inaperçues après avoir été interceptées. Cependant, je pense que Java vous oblige parfois à intercepter toutes les exceptions.

Voir aussi cette question :

  

Dans certains cas, les assertions sont désactivées lors de la compilation. Tu peux   ne pas avoir le contrôle sur cela (sinon, vous pourriez construire avec des assertions   sur), donc il pourrait être une bonne idée de le faire comme ça.

     

Le problème avec "correction" les valeurs d'entrée est que l'appelant   ne pas obtenir ce qu’ils attendent, ce qui peut poser des problèmes voire   se bloque dans des parties totalement différentes du programme, faisant du débogage une   cauchemar.

     

Je lance généralement une exception dans l'instruction if pour prendre en charge le rôle.   de l'affirmation au cas où ils sont désactivés

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff

Pour moi, la règle générale consiste à utiliser des expressions d'assertion pour rechercher des erreurs internes et des exceptions pour des erreurs externes. La discussion suivante de Greg sur ici .

  

Les expressions d'assertion sont utilisées pour rechercher les erreurs de programmation: soit des erreurs dans la logique même du programme, soit des erreurs dans son implémentation correspondante. Une condition d'assertion vérifie que le programme reste dans un état défini. Un " état défini " est fondamentalement celui qui est en accord avec les hypothèses du programme. Notez qu'un " état défini " pour un programme ne doit pas nécessairement être un "état idéal" ou même "un état habituel", ou même un "état utile" mais plus sur ce point important plus tard.

     

Pour comprendre l’intégration des assertions dans un programme, considérez une routine dans   un programme C ++ sur le point de déréférencer un pointeur. Maintenant, le   test de routine si le pointeur est NULL avant le déréférencement, ou   devrait-il affirmer que le pointeur n'est pas NULL et ensuite aller de l'avant et   déréférenciez-vous quand même?

     

J'imagine que la plupart des développeurs voudraient faire les deux, ajoutez l'assertion,   mais aussi vérifier le pointeur pour une valeur NULL, afin de ne pas planter   si la condition affirmée échoue. En surface, effectuer à la fois la   test et le contrôle peut sembler la décision la plus sage

     

Contrairement à ses conditions affirmées, le traitement des erreurs d'un programme (exceptions) ne renvoie pas   aux erreurs dans le programme, mais aux entrées que le programme obtient de son   environnement. Ce sont souvent des "erreurs". de la part de quelqu'un, comme un utilisateur   tenter de vous connecter à un compte sans entrer de mot de passe. Et   même si l'erreur peut empêcher la réussite du programme   tâche, il n'y a pas d'échec du programme. Le programme ne parvient pas à se connecter à l'utilisateur   sans mot de passe en raison d'une erreur externe - une erreur de l'utilisateur   partie. Si les circonstances étaient différentes et que l'utilisateur avait saisi le mot   mot de passe correct et le programme n'a pas réussi à le reconnaître; alors bien   le résultat serait toujours le même, l'échec appartiendrait maintenant à   le programme.

     

Le traitement des erreurs (exceptions) a un double objectif. Le premier est de communiquer   à l'utilisateur (ou à un autre client) qu'une erreur dans l'entrée du programme a   été détecté et ce que cela signifie. Le second objectif est de restaurer le   application après la détection de l’erreur, dans un état bien défini. Remarque   que le programme lui-même n'est pas en erreur dans cette situation. Certes, le   programme peut être dans un état non idéal, ou même un état dans lequel peut faire   rien d’utile, mais il n’ya pas d’erreur de programmation. Au contraire,   puisque l'état de récupération d'erreur est celui anticipé par le programme   conception, c’est celle que le programme peut gérer.

PS: vous pouvez consulter la question similaire: Assertion Exception Vs .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top