Résumé
Cette leçon ouvre la huitième et dernière unité du cours, consacrée aux programmes plus grands. Elle en explique le but : organiser, vérifier et déboguer des programmes qui ne tiennent plus dans un fichier ni dans la tête ; elle présente ses trois leçons et le fil qui les unit, qui ferme le cercle ouvert dans la première leçon du cours (spécifier, construire, vérifier) ; elle rappelle ce que l’unité tient pour acquis, c’est-à-dire le cours entier ; elle formule le problème que l’unité résout, celui d’un programme d’un seul fichier qui fonctionne avec les données avec lesquelles on l’a testé et tait un défaut à la frontière de sa spécification ; elle anticipe la différence entre tester et démontrer ; et elle montre où elle mène, dans le projet final et dans Programmation en C++ II.
Objectifs d’apprentissage
- Décrire la séquence des contenus de l’unité et expliquer ce que chaque leçon apporte à la suivante.
- Rappeler, avec un exemple, la différence entre déclarer et définir une fonction, le contrat qui la spécifie et les étapes (compilation et édition de liens) qui transforment plusieurs fichiers en un programme.
- Anticiper, avec un exemple, la différence entre tester un programme sur quelques cas et démontrer qu’il est correct pour tous les cas admissibles.
- Reconnaître ce que l’on attend de savoir faire à la fin de l’unité et du cours, et le distinguer de ce que l’on apprendra dans Programmation en C++ II.
Évaluation d’entrée
Avant de parcourir l’unité, il convient de mesurer le point de départ. Cette évaluation n’est pas notée : elle oriente l’étude. Les sept premières questions vérifient ce que l’unité tient pour acquis (déclarer et définir, la précondition, le contrat d’une fonction de l’unité 6, les messages de l’éditeur de liens, les cas d’une recherche dichotomique, le tableau de suivi et la lecture hors d’un vecteur) ; les trois suivantes touchent, dans l’ordre des leçons, aux idées centrales de l’unité, et pour elles « je ne le sais pas encore » est une réponse honnête. Chaque réponse explique le point et nomme la leçon qui le traite ; les mêmes compétences sont mesurées de nouveau dans la leçon de synthèse.
Avant de commencer l’unité, mesurez votre point de départ. C’est une autoévaluation brève : elle n’est pas notée et son résultat n’est conservé que dans votre navigateur.
Question 1Des unités précédentesQue fait la ligne
int gcd(int a, int b);écrite avantmain?Elle confond déclaration et définition : sans corps, il n’y a rien à exécuter, et aucune fonction ne s’exécute parce qu’elle est écrite, mais quand elle est appelée.Elle confond déclarer et appeler : un appel ne porte pas les types des paramètres.Elle ignore que la signature suffit au compilateur pour vérifier un appel ; l’absence du corps, si personne ne l’écrit, est découverte par l’éditeur de liens, non par le compilateur.Une déclaration suffit pour que le compilateur vérifie chaque appel ; la définition, avec le corps, peut se trouver plus bas ou dans un autre fichier. Toute la leçon Plusieurs fichiers et bibliothèques personnelles repose sur cette séparation : l’en-tête déclare, le fichier d’implémentation définit.À revoir : Fonctions
Question 2Des unités précédentesLa fonction
toBase(n, base)a pour précondition «n >= 0et2 <= base <= 10». Qui doit la garantir ?Elle confond la précondition avec la validation des entrées : valider est la tâche de la frontière du programme, non de chaque fonction.Elle croit que les commentaires sont vérifiés ; le compilateur les ignore.Elle ne voit pas que le contrat répartit les obligations ; sans lui, personne ne sait qui s’est trompé quand quelque chose échoue.Question 3Des unités précédentesLa fonction
binarySearch(values, target)de la leçon 6.4 renvoie une position detarget, ouvalues.size()s’il n’y est pas. Quelle est sa précondition ?Avec le vecteur vide, la boucle ne fait aucune itération et la fonction renvoie 0, qui estvalues.size(): le cas est couvert par la postcondition.La fonction signale l’absence ; exiger la présence la rendrait inutile.Avec des répétitions, la fonction renvoie l’une des positions de la valeur, ce que la postcondition admet.Chaque itération écarte la moitié de l’intervalle en comparant avec l’élément central, et cela n’est valable que si le vecteur est trié. Avec des données non triées, la fonction répond, sans avertissement, quelque chose de faux. La précondition s’écrit dans le contrat, où l’appelant la voit.À revoir : Rechercher et trier
Question 4Des unités précédentesUn programme compile sans erreurs et, à la construction de l’exécutable, apparaît
undefined reference to `tetrahedral(int)'. Quelle étape a échoué ?Elle ne distingue pas les messages du compilateur, qui citent un fichier et une ligne, de ceux de l’éditeur de liens, qui citent des objets et des noms.Le programme n’est jamais venu à l’existence : sans exécutable, il n’y a pas d’exécution.Un#includeabsent produitwas not declared in this scopeà la compilation, non ce message.Le compilateur a accepté chaque appel parce qu’il a vu une déclaration ; l’éditeur de liens a cherché la définition dans tous les objets et dans les bibliothèques, et ne l’a pas trouvée. La leçon Plusieurs fichiers et bibliothèques personnelles en explique les causes habituelles : un fichier qui n’a pas été compilé, ou une déclaration copiée avec un autre type.À revoir : Programmes, compilateurs et C++
Question 5Des unités précédentesLequel de ces ensembles de cas met le mieux à l’épreuve une recherche dichotomique ?
Elle confond quantité et couverture : elle ne teste jamais l’absence ni les frontières, où se trouvent d’ordinaire les défauts.Un cas qui a déjà réussi est celui qui a le moins de chances de découvrir quelque chose de nouveau.Ils sont en nombre infini, ou astronomique dans les limites du type : tester exige de choisir.Chaque cas exerce une frontière différente de l’intervalle : l’intervalle vide dès le début, celui qui se réduit à un élément, les extrémités et les trois manières de ne pas y être. Choisir les cas à partir de la spécification, par régions et frontières, est le sujet de la leçon Tester.À revoir : Rechercher et trier
Question 6Des unités précédentesQue valent
aetbaprèsint a{0}; int b{1}; for (int i{0}; i < 4; ++i) { const int t{a + b}; a = b; b = t; }?Elle compte une itération de trop : aveci < 4, le corps s’exécute quatre fois, non cinq.Elle compte une itération de moins, l’erreur de décalage d’une unité (off-by-one) symétrique.Elle oublie quetgarde la somme calculée avant queane change : à la dernière itération,areçoit le 3 deb, maisbreçoit cette somme, 5, et non la nouvelle valeur dea.Question 7Des unités précédentesPourquoi
std::vector<int> v{1, 2}; std::println("{}", v[2]);s’arrête-t-il avec les options du cours et, compilé seulement avec-O2, sans les détecteurs (sanitizers), peut-il écrire un nombre quelconque ?Elle croit que l’optimisation répare les erreurs ; elle ne fait que retirer des vérifications et suppose que le programme ne viole pas la norme.Elle inverse l’effet : la vérification qui arrête le programme est celle de la bibliothèque sans optimisation.C’est l’erreur de décalage d’une unité des indices : ceux d’un vecteur de deux éléments sont 0 et 1.Question 8Un en-tête muni de gardes d’inclusion définit (et ne fait pas que déclarer)
double scale(double x), sansinline, etmain.cppetreport.cppl’incluent. Que se passe-t-il à la construction du programme ?Elle croit que les gardes agissent entre fichiers ; elles agissent à l’intérieur de chaque unité de traduction.Elle confond le fichier que l’on compile avec le programme entier : dans chaque fichier, il n’y a qu’une définition.C’est une réponse honnête : la leçon 8.1 le montre avec le message réel de l’éditeur de liens.Les gardes empêchent qu’un en-tête entre deux fois dans un même fichier, mais chaque.cppse compile séparément et reçoit sa copie de la définition : l’éditeur de liens en trouve deux. C’est la règle de la définition unique, de la leçon Plusieurs fichiers et bibliothèques personnelles.Question 9Une fonction réussit cinq cents tests automatisés choisis avec soin. Que peut-on affirmer ?
Elle confond abondance et démonstration ; un défaut à une frontière qu’aucun cas n’a touchée survit à n’importe quel nombre de cas intérieurs.Les cas ne sont pas un échantillon aléatoire des défauts possibles, et la fréquence des réussites ne mesure pas la probabilité que le programme soit juste.C’est une réponse honnête : la leçon 8.2 commence par cette question.Un test peut montrer la présence d’un défaut, jamais son absence : affirmer la correction pour tous les cas exige un argument, comme l’invariant de la leçon 4.3, non une liste de cas. Des tests bien choisis rendent improbable qu’un défaut courant passe, et c’est pourquoi ils valent tant. La leçon Tester développe cette distinction.À revoir : Tester
Question 10Un point d’arrêt se trouve sur la ligne
total += k;, dans une boucle, et le programme s’y arrête pour la troisième fois. Qu’indique la ligne mise en évidence ?Elle confond l’arrêt avec le pas déjà fait.Elle confond l’endroit où le programme s’arrête, que choisit qui débogue, avec la cause de la défaillance.C’est une réponse honnête : la leçon 8.3 traite des points d’arrêt et des pas.
Chargement du contenu…