L'affirmation est-elle mauvaise? [fermé]


199

Les Gocréateurs du langage écrivent :

Go ne fournit pas d'assertions. Ils sont indéniablement pratiques, mais notre expérience a montré que les programmeurs les utilisent comme une béquille pour éviter de penser à une gestion et à un rapport d'erreur appropriés. Une gestion correcte des erreurs signifie que les serveurs continuent de fonctionner après des erreurs non fatales au lieu de planter. Un rapport d'erreurs approprié signifie que les erreurs sont directes et directes, ce qui évite au programmeur d'interpréter une grande trace de plantage. Les erreurs précises sont particulièrement importantes lorsque le programmeur qui voit les erreurs n'est pas familier avec le code.

Quelle est votre opinion à ce sujet?


4
tangente: Go est un langage inhabituel. Ce n'est pas nécessairement une mauvaise chose. Cependant, cela signifie que vous devriez prendre ses opinions avec un plus gros grain de sel. Cela signifie également que si vous n'êtes pas d'accord, vous allez grincer des dents en utilisant la langue. Comme preuve de la façon dont Go s'accroche à ses opinions malgré la réalité, considérez que vous devez recourir à la magie de la réflexion pour déterminer si deux collections sont égales.
allyourcode

@allyourcode Si vous faites référence à reflect.DeepEqual, vous n'en avez certainement pas besoin . C'est pratique, mais au détriment des performances (les tests unitaires sont un bon cas d'utilisation). Sinon, vous pouvez implémenter sans problème la vérification d'égalité appropriée pour votre "collection".
Igor Dubinskiy

1
Non, ce n'est pas de ça que je parle. Il n'y a rien de tel que slice1 == slice2 sans réflexion. Toutes les autres langues ont un équivalent à cette opération super basique. La seule raison pour laquelle Go ne le fait pas est le préjudice.
allyourcode

Vous pouvez comparer deux tranches sans réflexion en utilisant une forboucle dans Go (tout comme C). Ce serait vraiment bien d'avoir des opérations génériques de tranche, bien que la comparaison se complique lorsque des pointeurs et des structures sont impliqués.
kbolino

Réponses:


321

Non, il n'y a rien de mal asserttant que vous l'utilisez comme prévu.

Autrement dit, il est censé être utilisé pour intercepter les cas qui «ne peuvent pas se produire», lors du débogage, par opposition à la gestion normale des erreurs.

  • Assert: Un échec dans la logique du programme lui-même.
  • Gestion des erreurs: une entrée ou un état système erroné non dû à un bogue dans le programme.

109

Non, ni gotone assertsont mauvais. Mais les deux peuvent être mal utilisés.

Assert est pour les vérifications de santé mentale. Les choses qui devraient tuer le programme si elles ne sont pas correctes. Pas pour la validation ou pour remplacer la gestion des erreurs.


comment l'utiliser à gotobon escient?
ar2015

1
@ ar2015 Trouvez l'un des modèles ridiculement artificiels que certaines personnes recommandent d'éviter gotopour des raisons purement religieuses, puis utilisez gotoplutôt que de masquer ce que vous faites. En d'autres termes: si vous pouvez prouver que vous avez vraiment besoin goto, et la seule alternative est de décréter une charge d'échafaudages inutiles qui fait finalement la même chose mais sans altérer la police Goto ... alors utilisez-le goto. Bien sûr, une condition préalable à cela est le bit "si vous pouvez prouver que vous avez vraiment besoin goto". Souvent, les gens ne le font pas. Cela ne signifie toujours pas que c'est intrinsèquement une mauvaise chose.
underscore_d

2
gotoest utilisé dans le noyau linux pour le nettoyage du code
malat

61

Selon cette logique, les points d'arrêt sont également mauvais.

Les assertions doivent être utilisées comme aide au débogage, et rien d'autre. «Mal», c'est lorsque vous essayez de les utiliser au lieu de gérer les erreurs.

Les assertions sont là pour vous aider, le programmeur, à détecter et résoudre les problèmes qui ne doivent pas exister et vérifier que vos hypothèses restent vraies.

Ils n'ont rien à voir avec la gestion des erreurs, mais malheureusement, certains programmeurs les abusent en tant que tels, puis les déclarent "mauvais".


40

J'aime beaucoup utiliser l'affirmation. Je trouve cela très utile lorsque je crée des applications pour la première fois (peut-être pour un nouveau domaine). Au lieu de faire une vérification d'erreur très sophistiquée (que je considérerais comme une optimisation prématurée), je code rapidement et j'ajoute beaucoup d'assertions. Après en savoir plus sur le fonctionnement des choses, je fais une réécriture et je supprime certaines des assertions et les modifie pour une meilleure gestion des erreurs.

À cause des assertions, je passe beaucoup moins de temps à coder / déboguer les programmes.

J'ai également remarqué que les asserts m'aident à penser à beaucoup de choses qui pourraient casser mes programmes.


31

Comme information supplémentaire, go fournit une fonction intégrée panic. Cela peut être utilisé à la place de assert. Par exemple

if x < 0 {
    panic("x is less than 0");
}

panicimprimera la trace de la pile, donc d'une certaine manière, elle a pour but de assert.


30

Ils doivent être utilisés pour détecter les bogues dans le programme. Pas mauvaise entrée utilisateur.

S'ils sont utilisés correctement, ils ne sont pas mauvais.


13

Cela revient souvent et je pense qu'un problème qui rend les défenses d'assertions déroutantes est qu'elles sont souvent basées sur la vérification des arguments. Considérez donc cet exemple différent de cas où vous pourriez utiliser une assertion:

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

Vous utilisez une exception pour l'entrée, car vous vous attendez parfois à une mauvaise entrée. Vous affirmez que la liste est triée pour vous aider à trouver un bogue dans votre algorithme, ce que vous ne vous attendez pas par définition. L'assertion est uniquement dans la version de débogage, donc même si la vérification est coûteuse, cela ne vous dérange pas de le faire à chaque appel unique de la routine.

Vous devez toujours tester à l'unité votre code de production, mais c'est une manière différente et complémentaire de vous assurer que votre code est correct. Les tests unitaires garantissent que votre routine est à la hauteur de son interface, tandis que les assertions sont un moyen plus précis de vous assurer que votre implémentation fait exactement ce que vous attendez d'elle.


8

Les affirmations ne sont pas mauvaises, mais elles peuvent être facilement utilisées à mauvais escient. Je suis d'accord avec l'affirmation selon laquelle "les assertions sont souvent utilisées comme une béquille pour éviter de penser à une gestion et à un rapport appropriés des erreurs". Je l'ai vu assez souvent.

Personnellement, j'aime utiliser les assertions car elles documentent les hypothèses que j'ai pu faire en écrivant mon code. Si ces hypothèses sont brisées lors de la maintenance du code, le problème peut être détecté pendant le test. Cependant, je fais le point de supprimer toutes les assertions de mon code lors d'une génération de production (c'est-à-dire en utilisant #ifdefs). En supprimant les assertions dans la version de production, j'élimine le risque que quelqu'un les utilise à mauvais escient comme béquille.

Il y a aussi un autre problème avec les assertions. Les assertions ne sont vérifiées qu'au moment de l'exécution. Mais il arrive souvent que la vérification que vous souhaitez effectuer ait pu être effectuée au moment de la compilation. Il est préférable de détecter un problème au moment de la compilation. Pour les programmeurs C ++, boost fournit BOOST_STATIC_ASSERT qui vous permet de le faire. Pour les programmeurs C, cet article ( texte du lien ) décrit une technique qui peut être utilisée pour effectuer des assertions au moment de la compilation.

En résumé, la règle générale que je respecte est la suivante: n'utilisez pas d'assertions dans une génération de production et, si possible, n'utilisez des assertions que pour des éléments qui ne peuvent pas être vérifiés au moment de la compilation (c'est-à-dire qui doivent être vérifiés au moment de l'exécution).


5

J'avoue avoir utilisé des assertions sans envisager de rapport d'erreur approprié. Cependant, cela n'enlève pas qu'ils sont très utiles lorsqu'ils sont utilisés correctement.

Ils sont particulièrement utiles si vous souhaitez suivre le principe "Crash Early". Par exemple, supposons que vous implémentez un mécanisme de comptage de références. À certains endroits de votre code, vous savez que le décompte doit être zéro ou un. Et supposez également que si le refcount est erroné, le programme ne se bloquera pas immédiatement, mais au cours de la prochaine boucle de message, il sera difficile de savoir pourquoi les choses ont mal tourné. Une assertion aurait été utile pour détecter l'erreur plus près de son origine.


5

Je préfère éviter le code qui fait différentes choses dans le débogage et la publication.

Rompre le débogueur à une condition et avoir toutes les informations sur le fichier / la ligne est cependant utile, aussi l'expression exacte et la valeur exacte.

Avoir une affirmation qui "évaluerait la condition uniquement dans le débogage" peut être une optimisation des performances, et en tant que telle, utile uniquement dans 0,0001% des programmes - où les gens savent ce qu'ils font. Dans tous les autres cas, cela est nocif, car l'expression peut en fait changer l'état du programme:

assert(2 == ShroedingersCat.GetNumEars()); ferait le programme faire des choses différentes dans le débogage et la libération.

Nous avons développé un ensemble de macros d'assertion qui lèveraient une exception, et le font dans les versions de débogage et de version. Par exemple, THROW_UNLESS_EQ(a, 20);lève une exception avec le message what () contenant à la fois le fichier, la ligne et les valeurs réelles de a, etc. Seule une macro aurait le pouvoir pour cela. Le débogueur peut être configuré pour s'arrêter au «lancer» du type d'exception spécifique.


4
90% des statistiques utilisées dans les arguments sont fausses.
João Portela

5

Je n'aime pas les affirmations intensément. Mais je n'irais pas jusqu'à dire qu'ils sont mauvais.

Fondamentalement, une assertion fera la même chose qu'une exception non vérifiée, la seule exception est que l'assertion ne doit pas (normalement) être conservée pour le produit final.

Si vous créez un filet de sécurité pour vous-même pendant le débogage et la construction du système, pourquoi refuseriez-vous ce filet de sécurité à votre client, à votre service d'assistance ou à toute personne qui pourra utiliser le logiciel que vous construisez actuellement. Utilisez des exceptions exclusivement pour les assertions et les situations exceptionnelles. En créant une hiérarchie d'exceptions appropriée, vous pourrez discerner très rapidement les uns des autres. Sauf que cette fois, l'assertion reste en place et peut fournir des informations précieuses en cas de défaillance qui serait autrement perdue.

Je comprends donc parfaitement les créateurs de Go en supprimant complètement les assertions et en forçant les programmeurs à utiliser des exceptions pour gérer la situation. Il y a une explication simple à cela, les exceptions sont juste un meilleur mécanisme pour le travail pourquoi s'en tenir aux assertions archaïques?


Go n'a pas d'exceptions. La raison habituelle d'utiliser une assertion plutôt qu'une exception est que vous souhaitez qu'elle soit élidée dans le déploiement pour des raisons de performances. Les assertions ne sont pas archaïques. Je suis désolé de paraître dur, mais cette réponse n'est pas à distance utile à la question d'origine, ni correcte.
Nir Friedman


3

J'ai récemment commencé à ajouter des assertions à mon code, et voici comment je l'ai fait:

Je divise mentalement mon code en code limite et code interne. Le code de limite est un code qui gère les entrées utilisateur, lit les fichiers et obtient les données du réseau. Dans ce code, je demande une entrée dans une boucle qui ne se termine que lorsque l'entrée est valide (dans le cas d'une entrée utilisateur interactive), ou lève des exceptions dans le cas de données de fichier / réseau corrompues irrécupérables.

Le code interne est tout le reste. Par exemple, une fonction qui définit une variable dans ma classe peut être définie comme

void Class::f (int value) {
    assert (value < end);
    member = value;
}

et une fonction qui obtient une entrée d'un réseau peut se lire comme telle:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

Cela me donne deux niveaux de contrôles. Tout ce qui détermine les données au moment de l'exécution reçoit toujours une exception ou une gestion immédiate des erreurs. Cependant, cette vérification supplémentaire Class::favec la assertdéclaration signifie que si un code interne appelle Class::f, j'ai toujours un contrôle de santé mentale. Mon code interne peut ne pas passer d'argument valide (car j'ai peut-être calculé à valuepartir d'une série complexe de fonctions), j'aime donc avoir l'assertion dans la fonction de réglage pour documenter que, quelle que soit la personne qui appelle la fonction, elle valuene doit pas être supérieure ou égal à end.

Cela semble correspondre à ce que je lis à quelques endroits, à savoir que les affirmations devraient être impossibles à violer dans un programme qui fonctionne bien, tandis que les exceptions devraient être pour les cas exceptionnels et erronés qui sont encore possibles. Parce qu'en théorie je valide toutes les entrées, il ne devrait pas être possible de déclencher mon assertion. Si c'est le cas, mon programme est erroné.


2

Si les affirmations dont vous parlez signifient que le programme vomit puis existe, les affirmations peuvent être très mauvaises. Cela ne veut pas dire qu'ils sont toujours la mauvaise chose à utiliser, ils sont une construction qui est très facilement mal utilisée. Ils ont également de bien meilleures alternatives. Des choses comme ça sont de bons candidats pour être appelées mal.

Par exemple, un module tiers (ou n'importe quel module vraiment) ne devrait presque jamais quitter le programme appelant. Cela ne donne au programmeur appelant aucun contrôle sur le risque que le programme devrait prendre à ce moment. Dans de nombreux cas, les données sont si importantes que même la sauvegarde de données corrompues est préférable à la perte de ces données. Les assertions peuvent vous obliger à perdre des données.

Quelques alternatives aux assertions:

  • À l'aide d'un débogueur,
  • Console / base de données / autre journalisation
  • Des exceptions
  • Autres types de gestion des erreurs

Quelques références:

Même les personnes qui plaident pour l'affirmation pensent qu'elles ne devraient être utilisées que dans le développement et non dans la production:

Cette personne dit que les assertions doivent être utilisées lorsque le module contient des données potentiellement corrompues qui persistent après qu'une exception est levée: http://www.advogato.org/article/949.html . C'est certainement un point raisonnable, cependant, un module externe ne devrait jamais prescrire l'importance des données corrompues pour le programme appelant (en les quittant "pour"). La bonne façon de gérer cela est de lever une exception qui indique clairement que le programme peut maintenant être dans un état incohérent. Et comme les bons programmes sont principalement constitués de modules (avec un peu de code glue dans l'exécutable principal), les assertions sont presque toujours la mauvaise chose à faire.


1

assert est très utile et peut vous faire économiser beaucoup de recul lorsque des erreurs inattendues se produisent en arrêtant le programme dès les premiers signes de problèmes.

D'un autre côté, il est très facile d'en abuser assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

La version correcte et correcte serait quelque chose comme:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

Donc ... à long terme ... dans l'ensemble ... je dois convenir que cela assertpeut être abusé. Je le fais tout le temps.


1

assert est utilisé abusivement pour la gestion des erreurs, car il est moins de frappe.

Ainsi, en tant que concepteurs de langage, ils devraient plutôt voir qu'une gestion correcte des erreurs peut être effectuée avec une saisie encore moins importante. Exclure assert car votre mécanisme d'exception est détaillé n'est pas la solution. Oh attendez, Go n'a pas d'exceptions non plus. Dommage :)


1
Pas trop mal :-) Exceptions ou pas, assertions ou pas, les fans de Go parlent encore de la longueur des codes.
Moshe Revah

1

J'avais envie de donner un coup de pied à l'auteur dans la tête quand j'ai vu ça.

J'utilise des assertions tout le temps dans le code et éventuellement les remplace toutes lorsque j'écris plus de code. Je les utilise lorsque je n'ai pas écrit la logique requise et je veux être alerté lorsque je rencontre le code au lieu d'écrire une exception qui sera supprimée à mesure que le projet se rapprochera.

Les exceptions se fondent également plus facilement dans le code de production que je n'aime pas. Une assertion est plus facile à remarquer quethrow new Exception("Some generic msg or 'pretend i am an assert'");


1

Mon problème avec ces réponses défendant l'assertion est que personne ne spécifie clairement ce qui la rend différente d'une erreur fatale régulière , et pourquoi une assertion ne peut pas être un sous-ensemble d'une exception . Maintenant, cela étant dit, que se passe-t-il si l'exception n'est jamais interceptée? Cela en fait-il une affirmation par nomenclature? Et, pourquoi voudriez-vous jamais imposer une restriction dans le langage qu'une exception peut être levée que / rien / peut gérer?


Si vous regardez ma réponse. Mon utilisation est de différencier les «exceptions» (affirmations) dont je veux me débarrasser utilisées pour le débogage par rapport aux exceptions que je garde. Pourquoi voudrais-je m'en débarrasser? car sans eux, ce ne serait pas complet. Par exemple, si je gère 3 cas et le 4ème est à faire. Je peux facilement rechercher assert pour les trouver dans le code et connaître son incomplet plutôt que d'utiliser une exception qui pourrait être accidentellement interceptée (par un autre programmeur) ou difficile à dire si c'est une exception ou une vérification logique que je devrais résoudre dans le code.

À mes yeux, c'est une mauvaise idée, au même niveau que les classes "scellées" et pour la même raison. Vous supposez que les exceptions que vous souhaitez conserver sont acceptables pour les utilisations de votre code que vous ne connaissez pas encore. Toutes les exceptions passent par les mêmes canaux, et si l'utilisateur ne veut pas les rattraper, il peut choisir de ne pas le faire. S'il le fait, il devrait également avoir la capacité. Quoi qu'il en soit, vous faites simplement des hypothèses ou repoussez votre pratique avec un concept comme une assertion.
Evan Carroll

J'ai décidé que les scénarios d'exemple sont les meilleurs. Voici un simple. int func (int i) {if (i> = 0) {console.write ("Le nombre est positif {0}", i); } else {assert (false); // to paresseux pour faire des négatifs ATM} return i * 2; <- Comment pourrais-je faire cela sans assertions et est-ce vraiment une exception? et rappelez-vous, ce code sera implémenté avant sa sortie.

Bien sûr, les exceptions sont meilleures, disons que je prends la saisie de l'utilisateur et que j'appelle func()avec un numéro négatif. Maintenant, tout à coup, avec vos affirmations, vous avez retiré le tapis de moi et ne m'avez pas donné une chance de récupérer, plutôt que de me dire poliment ce que je demande ne peut pas être fait. Il n'y a rien de mal à accuser le programme de se comporter mal et à lui donner une citation: le problème est que vous brouillez l'acte d'appliquer une loi et de condamner le criminel.
Evan Carroll du

C'est le point, vous DEVEZ dire ce que l'utilisateur veut faire ne peut pas être fait. L'application doit se terminer avec le message d'erreur et celui qui débogue se souviendra de son quelque chose qui DEVRAIT ÊTRE FAIT mais qui ne l'est pas. Vous ne voulez pas qu'il soit géré. Vous voulez une résiliation et rappelez-vous que vous n'avez pas traité cette affaire. et il est extrêmement facile de voir quels cas il reste car tout ce que vous avez à faire est de rechercher assert dans le code. La gestion de l'erreur serait erronée car le code devrait pouvoir le faire une fois prêt pour la production. Permettre à un programmeur de l'attraper et de faire autre chose est faux.

1

Oui, les affirmations sont mauvaises.

Souvent, ils sont utilisés dans des endroits où une gestion appropriée des erreurs doit être utilisée. Habituez-vous à écrire dès le début une gestion correcte des erreurs de qualité de production!

Habituellement, ils gênent l'écriture de tests unitaires (sauf si vous écrivez une assertion personnalisée qui interagit avec votre faisceau de test). Cela est souvent dû au fait qu'ils sont utilisés là où une gestion des erreurs appropriée doit être utilisée.

Généralement, ils sont compilés à partir des versions de version, ce qui signifie qu'aucun de leurs "tests" n'est disponible lorsque vous exécutez le code que vous publiez réellement; étant donné que dans les situations multi-thread, les pires problèmes n'apparaissent souvent que dans le code de version, cela peut être mauvais.

Parfois, ils sont une béquille pour des conceptions autrement cassées; c'est-à-dire que la conception du code permet à un utilisateur de l'appeler d'une manière qui ne devrait pas être appelée et l'assertion "l'empêche". Réparez le design!

J'ai écrit à ce sujet plus sur mon blog en 2005 ici: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

Pas tant de mal que généralement contre-productif. Il y a une séparation entre la vérification permanente des erreurs et le débogage. Assert incite les gens à penser que tout débogage doit être permanent et provoque d'énormes problèmes de lisibilité lorsqu'ils sont beaucoup utilisés. La gestion des erreurs permanentes devrait être meilleure que celle qui est nécessaire, et puisque assert provoque ses propres erreurs, c'est une pratique assez discutable.


5
assert est bon pour déclarer les conditions préalables en haut d'une fonction et, s'il est clairement écrit, il fait partie de la documentation de la fonction.
Peter Cordes

0

je n'utilise jamais assert (), les exemples montrent généralement quelque chose comme ceci:

int* ptr = new int[10];
assert(ptr);

C'est mauvais, je ne fais jamais ça, et si mon jeu alloue un tas de monstres? pourquoi devrais-je planter le jeu, à la place, vous devriez gérer les erreurs avec élégance, alors faites quelque chose comme:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

14
newne revient jamais nullptr, il jette.
David Stone

Notez que vous pouvez utiliser std :: nothrow pour cela.
Markand
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.