Introduction à la programmation C# · Module 5, leçon 2 sur 3
Chercher et corriger méthodiquement
Remplacer les essais au hasard par une méthode : reproduire, localiser, comprendre, corriger, vérifier et documenter chaque erreur.
À la fin de la leçon, vous corrigez un programme qui contient plusieurs erreurs, une à la fois, et vous remettez un journal de correction qui prouve chaque correction.
- L'essentiel
- Exercices
- Solutions
- Je fais le point
Ce que vous allez faire
- Reconnaître les trois familles d'erreurs.
- Lire un message de compilation et un message d'exception.
- Appliquer la méthode en six étapes.
- Tenir un journal de correction.
Vous aurez réussi si…
- chaque correction répond à un cas qui échouait ;
- vous ne changez qu'une chose à la fois ;
- vos tests réussissent tous après la dernière correction.
L'essentiel
Devant un programme qui ne fonctionne pas, la tentation est de modifier une ligne « pour voir », puis une autre. On finit souvent avec plus d'erreurs qu'au départ, sans savoir laquelle des modifications a aidé. Un informaticien procède comme un enquêteur : il établit les faits, formule une hypothèse, la vérifie, puis consigne ce qu'il a trouvé.
Trois familles d'erreurs
| Famille | Symptôme | Premier réflexe |
|---|---|---|
| Compilation | Le programme ne démarre pas ; liste de messages CS | Lire le premier message : code, ligne, colonne. Les suivants en découlent souvent. |
| Exécution | « Unhandled exception » ; ou le programme ne rend plus la main | Lire le type d'exception et la ligne indiquée ; ou arrêter avec le débogueur (pause) pour voir où il tourne. |
| Logique | Résultat faux, sans aucun message | Trouver le cas le plus simple qui échoue, puis suivre les variables au débogueur. |
Lire un message d'exception
Une exception s'affiche en anglais, en quelques lignes. Trois informations suffisent : le type, le message et la ligne.
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at Program.<Main>$(String[] args) in C:\Cours\Pas\Program.cs:line 14
Ici : une division entière par zéro, à la ligne 14 de Program.cs. Les exceptions les plus fréquentes à ce stade sont FormatException (un Parse sur un texte qui n'est pas un nombre) et DivideByZeroException (une division entière par zéro).
La méthode en six étapes
- Reproduire : trouver des entrées précises qui font échouer le programme à tout coup. « Ça marche pas » n'est pas un cas ; « 2 trajets, 120 km et 80 km, donnent 1 571,43 au lieu de 7,70 » en est un.
- Localiser : repérer la première valeur qui s'écarte de l'attendu. Pour un long programme, on divise pour régner : on vérifie une valeur au milieu du traitement ; si elle est juste, l'erreur est après, sinon elle est avant.
- Comprendre : formuler la cause en une phrase et la prouver par une valeur observée. Expliquer le code à voix haute, ligne par ligne, même à un objet inanimé (la méthode du canard en caoutchouc), révèle souvent l'erreur.
- Corriger : faire une seule modification, la plus petite possible.
- Vérifier : rejouer le cas qui échouait, puis tous les autres tests (non-régression).
- Documenter : consigner l'erreur dans le journal de correction.
Exemple complet : le covoiturage
Le programme doit calculer le coût de l'essence partagé entre les passagers d'un covoiturage, pour plusieurs trajets. La voiture consomme 7 L aux 100 km, et l'essence coûte 1,65 le litre. Cas de test : 2 trajets de 120 km et 80 km, 3 personnes. Résultat attendu : 14,0 L, 23,10 , soit 7,70 par personne.
const double Consommation = 7; // litres aux 100 km const double PrixLitre = 1.65; Console.Write("Nombre de trajets : "); int nbTrajets = int.Parse(Console.ReadLine() ?? ""); Console.Write("Nombre de personnes : "); int nbPersonnes = int.Parse(Console.ReadLine() ?? ""); double totalKm = 0; int trajet = 1; while (trajet <= nbTrajets) { Console.Write($"Distance du trajet {trajet} (km) : "); double km = double.Parse(Console.ReadLine() ?? "") totalKm += km; } double litres = totalKm * 100 / Consommation; double cout = litres * PrixLitre; double parPersonne = cout / nbPersonnes; Console.WriteLine($"Essence : {litres:F1} L, {cout:C}"); Console.WriteLine($"Par personne : {parPersonne:C}");
Le programme contient trois erreurs, une de chaque famille. On les corrige dans l'ordre où elles se présentent :
- Compilation : le premier message est
CS1002 : ; attendu, ligne 14. Il manque le point-virgule à la fin de la lecture dekm. - Exécution : une fois le programme compilé, il redemande sans fin « Distance du trajet 1 ». Au débogueur,
trajetvaut toujours 1 : la progressiontrajet++;manque dans la boucle. - Logique : le programme se termine, mais affiche 2857,1 L et 1 571,43 par personne. Un point d'arrêt après le calcul de
litresmontre 2857,14 alors qu'on attend 14 : la formule est inversée. Les litres valenttotalKm * Consommation / 100.
| No | Symptôme | Cas | Cause | Correction | Vérification |
|---|---|---|---|---|---|
| 1 | CS1002, ligne 14 | Compilation | Point-virgule manquant | Ajout du ; | Compile sans erreur |
| 2 | Demande sans fin « trajet 1 » | 2 trajets | trajet reste à 1 : pas de progression | Ajout de trajet++; à la fin du bloc | Demande les trajets 1 et 2, puis s'arrête |
| 3 | 2857,1 L au lieu de 14,0 L | 120 et 80 km, 3 personnes | Formule des litres inversée | totalKm * Consommation / 100 | 14,0 L ; 23,10 ; 7,70 ; et 1 trajet de 50 km pour 2 personnes : 2,89 |
Si on change trois lignes d'un coup et que le résultat devient juste, on ne sait pas lesquelles étaient nécessaires ; si le résultat reste faux, on ne sait pas si une modification a empiré les choses. Une modification, puis un test : chaque pas est une preuve.
- Corriger le deuxième message de compilation en premier : il est souvent une conséquence du premier. Corrigez le premier, puis recompilez.
- Modifier le test pour qu'il passe : si le résultat attendu est juste, c'est le programme qu'on corrige.
- S'arrêter au premier cas réussi : une correction peut en briser une autre partie. Rejouez tous les tests.
- Laisser des traces : les
Console.WriteLineajoutés pour enquêter doivent être retirés à la fin.
Exercices
GuidéExercice 1 : Classer les symptômes
Pour chaque symptôme, donnez la famille d'erreur (compilation, exécution ou logique) et votre premier réflexe.
CS0103 : Le nom 'totl' n'existe pas dans le contexte actuel, ligne 12.System.FormatException: The input string 'douze' was not in a correct format.- La facture affiche un total dix fois trop grand.
- Le programme n'affiche plus rien et ne rend jamais la main.
CS0165 : Utilisation d'une variable locale non assignée 'moyenne'.- Le maximum affiché est 0 pour une semaine de températures négatives.
Vous avez réussi si vous classez deux erreurs dans chaque famille, et si chaque réflexe nomme un outil précis (message, ligne, débogueur, cas de test).
GuidéExercice 2 : Lire une exception
Ce programme calcule la moyenne de pas par jour. Avec la seule saisie 0, il affiche le message d'exception présenté plus haut.
int total = 0; int nbValeurs = 0; int valeur = -1; while (valeur != 0) { Console.Write("Nombre de pas (0 pour terminer) : "); valeur = int.Parse(Console.ReadLine() ?? ""); if (valeur != 0) { total += valeur; nbValeurs++; } } int moyenne = total / nbValeurs; Console.WriteLine($"Moyenne : {moyenne} pas par jour");
- Nommez le type d'exception et la ligne en cause.
- Expliquez la cause en une phrase.
- Corrigez le programme pour qu'il affiche « Aucune donnée. » quand aucune valeur n'est saisie.
Vous avez réussi si la saisie 0 affiche « Aucune donnée. », et si 8000, 6000, 0 affiche toujours « Moyenne : 7000 pas par jour ».
Semi-guidéExercice 3 : Le remboursement d'un prêt
Ce programme doit compter les versements mensuels nécessaires pour rembourser un prêt sans intérêt, et afficher le dernier versement, qui peut être plus petit. Il contient deux erreurs.
Console.Write("Montant emprunté : "); double montant = double.Parse(Console.ReadLine() ?? ""); Console.Write("Versement mensuel : "); double versement = double.Parse(Console.ReadLine() ?? ""); double solde = montant; int mois = 1; double dernier = 0; while (solde >= 0) { if (solde < versement) { dernier = solde; } else { dernier = versement; } solde -= dernier; mois++; } Console.WriteLine($"Nombre de versements : {mois}"); Console.WriteLine($"Dernier versement : {dernier:C}");
Appliquez la méthode et tenez le journal de correction (six colonnes). Utilisez ces cas : 1000 et 150 ; 900 et 150 ; 100 et 150 .
Vous avez réussi si votre journal compte deux lignes, et si le programme corrigé donne 7 versements (dernier : 100,00 ), 6 versements (dernier : 150,00 ) et 1 versement (dernier : 100,00 ).
AutonomeExercice 4 : Diviser pour régner
Cette facture applique un rabais de 10 % à partir de 100 d'achat, puis les taxes. Pour 4 articles à 30 , on attend 124,17 , mais elle affiche 13,80 . Avec 2 articles à 25 , elle donne le bon total, 57,49 .
const double Rabais = 0.10; const double SeuilRabais = 100; const double TauxTps = 0.05; const double TauxTvq = 0.09975; Console.Write("Prix unitaire : "); double prix = double.Parse(Console.ReadLine() ?? ""); Console.Write("Quantité : "); int quantite = int.Parse(Console.ReadLine() ?? ""); double sousTotal = prix * quantite; if (sousTotal >= SeuilRabais) { sousTotal = sousTotal * Rabais; } double tps = sousTotal * TauxTps; double tvq = sousTotal * TauxTvq; double total = sousTotal + tps + tvq; Console.WriteLine($"Total : {total:C}");
Localisez l'erreur en vérifiant d'abord une seule valeur, au milieu du traitement : le sous-total après le rabais. Rédigez la ligne du journal.
Vous avez réussi si vous expliquez pourquoi le cas à 50 ne révélait pas l'erreur, et si 4 × 30 donne 124,17 et 1 × 100 donne 103,48 .
DéfiExercice 5 : L'erreur qui se cache
Ce programme réussit les tests 2, 7, 10 et 97 : il dit que 2, 7 et 97 sont premiers, et que 10 ne l'est pas. Pourtant, il contient une erreur.
Console.Write("Nombre entier (2 ou plus) : "); int n = int.Parse(Console.ReadLine() ?? ""); bool estPremier = true; for (int diviseur = 2; diviseur * diviseur < n; diviseur++) { if (n % diviseur == 0) { estPremier = false; } } if (estPremier) { Console.WriteLine($"{n} est premier."); } else { Console.WriteLine($"{n} n'est pas premier."); }
Aucun symptôme n'est fourni : c'est à vous de trouver un cas qui échoue. Raisonnez sur la condition de la boucle pour choisir vos tests, puis corrigez et documentez.
Vous avez réussi si vous trouvez au moins deux nombres mal classés, si vous expliquez ce qu'ils ont en commun, et si la version corrigée les classe bien tout en réussissant les quatre tests d'origine.
Solutions
Exercice 1 : Classer les symptômes
- Compilation : aller à la ligne 12 ; une faute de frappe dans un nom de variable.
- Exécution : un
Parsea reçu « douze » ; chercher la lecture en cause et la protéger avecTryParse. - Logique : trouver le plus petit cas qui échoue, puis vérifier les valeurs intermédiaires au débogueur (un facteur 10, souvent un taux mal écrit).
- Exécution : une boucle infinie probable ; mettre le débogueur en pause pour voir la ligne où il tourne et la variable qui ne progresse pas.
- Compilation : une branche n'affecte pas
moyenne; vérifier que chaque chemin lui donne une valeur. - Logique : le maximum est initialisé à 0 ; il doit partir de la première valeur (leçon 4.3).
Exercice 2 : Lire une exception
DivideByZeroException, ligne 14 :int moyenne = total / nbValeurs;.- Avec 0 comme seule saisie,
nbValeursvaut 0, et une division entière par zéro est impossible. - Ajouter un test avant la division :
if (nbValeurs == 0) { Console.WriteLine("Aucune donnée."); } else { int moyenne = total / nbValeurs; Console.WriteLine($"Moyenne : {moyenne} pas par jour"); }
Avec des double, 0 / 0 ne plante pas, mais donne NaN : l'erreur deviendrait une erreur de logique, plus discrète. Le test est donc nécessaire dans les deux cas.
Exercice 3 : Le remboursement d'un prêt
| No | Symptôme | Cas | Cause | Correction | Vérification |
|---|---|---|---|---|---|
| 1 | Le programme se fige après les saisies | 1000 et 150 | Quand solde atteint 0, solde >= 0 reste vrai et dernier vaut 0 : le solde ne bouge plus | while (solde > 0) | Le programme se termine, mais affiche 8 versements |
| 2 | 8 versements au lieu de 7 | 1000 et 150 | mois part de 1 : un versement de trop compté | int mois = 0; | 7 (100,00 ) ; 6 (150,00 ) ; 1 (100,00 ) |
La deuxième erreur n'était visible qu'une fois la première corrigée : c'est typique. Le cas 900 et 150 est la limite où le dernier versement est complet.
Exercice 4 : Diviser pour régner
Un point d'arrêt sur la ligne double tps = … montre sousTotal = 12 au lieu de 108 : l'erreur est avant, et la seule ligne qui modifie le sous-total est celle du rabais. sousTotal * Rabais calcule le montant du rabais (10 %), pas le prix après rabais (90 %). Il faut sousTotal * (1 - Rabais).
Le cas à 50 ne passe pas dans le if : il ne peut pas révéler une erreur qui se trouve dans le if. Un bon plan de tests exécute chaque branche au moins une fois.
Ligne du journal : sous-total de 12 au lieu de 108 ; cas 4 × 30 ; rabais calculé comme un montant ; sousTotal * (1 - Rabais) ; vérifié avec 124,17 , 103,48 et 57,49 .
Exercice 5 : L'erreur qui se cache
La condition diviseur * diviseur < n exclut le cas où le diviseur au carré vaut exactement n. Les carrés de nombres premiers sont donc mal classés : 4, 9, 25, 49 et 121 sont déclarés premiers. Pour 9, la boucle s'arrête avant de tester 3, puisque 3 × 3 < 9 est faux.
Correction : diviseur * diviseur <= n. Vérification : 4, 9, 25 et 49 ne sont plus premiers ; 2, 7 et 97 le sont toujours ; 10 ne l'est toujours pas.
Les tests d'origine avaient tous des nombres « faciles ». Chercher les valeurs à la frontière d'une condition, ici l'égalité, est la méthode des valeurs limites de la leçon 2.3.
Je fais le point
- Je sais reconnaître une erreur de compilation, d'exécution ou de logique.
- Je sais lire le premier message du compilateur et un message d'exception.
- Je sais reproduire une erreur avec un cas précis avant de la corriger.
- Je sais localiser une erreur en divisant le traitement en deux.
- Je sais ne faire qu'une correction à la fois, puis rejouer tous les tests.
- Je sais tenir un journal de correction.
Rich Text
This is my rich text.
I am a subheading
This is more rich text.
I am a list
Lists are cool
Breakdance is flexible, powerful, and easy-to-use. It's everything I need to build a website.
