Débogage B : le processus de débogage et le débogueur de VS Code

Objectifs de la leçon
  • Appliquer les cinq étapes du débogage — Reproduce, Isolate, Root Cause, Fix, Verify
  • Placer un Breakpoint et exécuter du code en mode Debug dans VS Code
  • Utiliser Step Over, Step Into et Continue, et suivre les variables dans la fenêtre Variables
  • Trouver et corriger une Logical Error dans du vrai code à l’aide du débogueur

Qu’allons-nous apprendre ?

Dans la leçon précédente, nous avons appris à lire les erreurs. Maintenant, nous allons apprendre à traquer les bugs, y compris ceux qui n’ont aucun message d’erreur :

  1. Un processus — cinq étapes fixes pour chaque bug.
  2. Un outil — le débogueur (Debugger) de VS Code : arrêter le code au milieu et vérifier ce qui s’y passe.
  3. Une vraie pratique — ensemble, nous résoudrons un bug étape par étape.

Pourquoi en avons-nous besoin ?

La méthode des débutants est « deviner et changer » : on change quelque chose, on exécute, et si ça n’a pas marché, on change autre chose. C’est lent, frustrant, et cela crée souvent un nouveau bug. Les professionnels travaillent avec une méthode, et le débogueur permet de voir le code « au ralenti », au lieu de deviner ce qui s’y passe.

Explication à partir des bases

Les cinq étapes du débogage

ÉtapeNomLa question à laquelle elle répond
1Reproduce (reproduire)Comment faire apparaître le bug à chaque fois ? Avec quelle entrée ?
2Isolate (isoler)Où dans le code se trouve le problème ? Le réduire à une ligne ou une fonction.
3Root Cause (cause racine)Pourquoi cela arrive-t-il ? Pas le symptôme, mais la cause.
4Fix (corriger)Corriger la cause, et ne pas cacher le symptôme.
5Verify (vérifier)Le bug a-t-il disparu ? Et rien d’autre n’a-t-il cassé ?
Analogie : un médecin

Un bon médecin ne donne pas un antidouleur à chaque patient. Il reproduit (« quand est-ce que ça fait mal ? »), isole (« où exactement ? »), trouve la cause (des examens), traite la cause, et vérifie lors d’une visite de contrôle que le patient est guéri. Un antidouleur seul, c’est « corriger le symptôme ». La douleur revient, et le problème continue.

Qu’est-ce qu’un débogueur ?

Un débogueur est un outil qui exécute le programme sous votre contrôle : il l’arrête à une ligne que vous avez choisie, et vous permet d’avancer ligne par ligne et de voir la valeur de chaque variable à chaque instant.

Les termes :

TermeCe que c’est
BreakpointUn « point d’arrêt » : une marque sur une ligne. Quand l’exécution l’atteint, le programme s’arrête avant d’exécuter la ligne.
Continue (F5)Continuer l’exécution normalement, jusqu’au Breakpoint suivant (ou jusqu’à la fin).
Step Over (F10)Exécuter la ligne actuelle en entier et s’arrêter à la ligne suivante. Si elle contient un appel de fonction, la fonction s’exécute « d’un coup ».
Step Into (F11)Si la ligne contient un appel de fonction, entrer dedans et s’arrêter à sa première ligne.
Step Out (Shift+F11)Terminer la fonction actuelle et revenir à celui qui l’a appelée.
VariablesUne fenêtre qui montre toutes les variables et leurs valeurs à cet instant.
WatchDes expressions que vous avez choisi de suivre, par exemple total / count.
Call StackLa chaîne des appels : quelle fonction a appelé laquelle. C’est le Traceback, mais « en direct ».
Stop (Shift+F5)Arrêter le débogage.
Step Over face à Step Into

Vous lisez une recette, et elle contient une ligne « préparer la pâte (voir page 40) ». Step Over = vous préparez la pâte sans ouvrir la page 40, et passez à la ligne suivante de la recette. Step Into = vous ouvrez la page 40 et lisez la recette de la pâte ligne par ligne.

Syntaxe

Comment lancer le débogueur dans VS Code :

  1. Assurez-vous que l’extension Python de Microsoft est installée. Elle inclut le débogueur.
  2. Ouvrez un fichier .py.
  3. Placer un Breakpoint : cliquez dans la marge de gauche, à gauche du numéro de ligne. Un point rouge 🔴 apparaît (ou F9 sur la ligne).
  4. Démarrer : F5, ou la petite flèche à côté du bouton ▷ → Python Debugger: Debug Python File. Si on vous demande quelle configuration, choisissez Python File.
  5. Le programme s’arrête au Breakpoint. La ligne jaune est la prochaine ligne qui sera exécutée.
  6. Sur le côté gauche apparaissent Variables, Watch et Call Stack. En haut apparaît la barre de boutons : Continue, Step Over, Step Into, Step Out, Restart, Stop.

Astuce : pendant l’arrêt, vous pouvez aussi survoler une variable du code avec la souris pour voir sa valeur.

Premier exemple de code

Nous commençons par un programme correct, juste pour découvrir l’outil. week-2/debug_intro.py :

def apply_tax(amount):
    tax = amount * 0.17
    return amount + tax

prices = [100, 250, 40]
total = 0

for price in prices:
    total = total + apply_tax(price)

print("Total with tax:", total)

L’exercice :

  1. Placez un Breakpoint sur la ligne total = total + apply_tax(price).
  2. F5. Le programme s’arrête. Dans la fenêtre Variables : price = 100, total = 0.
  3. F10 (Step Over). Maintenant total = 117.0, et la ligne jaune est revenue au for.
  4. F5 (Continue). On s’arrête de nouveau sur la même ligne : price = 250.
  5. Cette fois F11 (Step Into). Nous sommes dans apply_tax, et amount = 250. Dans la Call Stack, on voit : apply_tax → <module>.
  6. F10 deux fois : on voit tax = 42.5, puis return.
  7. F5 jusqu’à la fin.

Sortie :

Total with tax: 456.3

Analyse du code ligne par ligne

Ce que l’on voit à chaque arrêt, et c’est ainsi que l’on comprend le déroulement :

ArrêtLigne jauneVariablesExplication
1total = total + apply_tax(price)price=100, total=0L’arrêt se fait avant l’exécution de la ligne, donc total vaut encore 0.
Après F10for price in prices:total=117.0La ligne a été exécutée en entier, y compris la fonction.
2total = total + apply_tax(price)price=250, total=117.0Deuxième tour.
Après F11tax = amount * 0.17amount=250Nous sommes entrés dans la fonction. On ne voit que ses variables (Local).
Après F10return amount + taxamount=250, tax=42.5

Un autre exemple

Un vrai bug — du début à la fin. Un programme qui calcule une moyenne de notes sans la note la plus basse (comme c’est courant dans les cours : « la note la plus basse ne compte pas »). week-2/drop_lowest.py :

def find_lowest(grades):
    lowest = 0
    for grade in grades:
        if grade < lowest:
            lowest = grade
    return lowest

def average_without_lowest(grades):
    lowest = find_lowest(grades)
    total = sum(grades) - lowest
    return total / (len(grades) - 1)

my_grades = [80, 90, 70, 100]
print("Average without lowest:", average_without_lowest(my_grades))

Attendu : la plus basse est 70. Il reste 80, 90, 100, et la moyenne est 90.0.

Sortie réelle :

Average without lowest: 113.33333333333333

Une moyenne au-dessus de 100 ! C’est une Logical Error : pas de message d’erreur, juste un résultat impossible.

Étape 1 — Reproduce : l’entrée [80, 90, 70, 100] produit le bug à chaque exécution. Parfait, nous avons un cas stable.

Étape 2 — Isolate : il y a deux fonctions. Laquelle est coupable ? Placez un Breakpoint sur la ligne total = sum(grades) - lowest et lancez avec F5. Dans la fenêtre Variables : lowest = 0. Mais la plus basse est 70 ! Le problème se trouve déjà avant cette ligne, dans find_lowest. Nous l’avons isolé.

Étape 3 — Root Cause : arrêtez, placez un Breakpoint sur lowest = find_lowest(grades), F5, puis F11 (Step Into) dans find_lowest, et F10 ligne par ligne :

Tourgradegrade < lowest ?lowest
Départ——0
18080 < 0 → False0
29090 < 0 → False0
37070 < 0 → False0
4100100 < 0 → False0

La cause racine : lowest commence à 0. Aucune note positive n’est inférieure à 0, elle n’est donc jamais mise à jour. L’hypothèse « 0 est une bonne valeur de départ » est fausse.

Étape 4 — Fix : partir de la première note de la liste. C’est toujours une vraie candidate :

def find_lowest(grades):
    lowest = grades[0]
    for grade in grades:
        if grade < lowest:
            lowest = grade
    return lowest

(Python a aussi une fonction intégrée min(grades), et dans du vrai code il vaut mieux l’utiliser. Ici, nous l’avons écrite à la main pour nous entraîner.)

Étape 5 — Verify :

Average without lowest: 90.0

Et vérifiez d’autres cas, pas seulement celui d’origine :

  • [50, 50, 50] → 50.0 ✓
  • [100, 0] → 100.0 ✓ (la plus basse, 0, est retirée)
  • [70] → plante ! ZeroDivisionError (division par len - 1 = 0). Nous avons trouvé un autre bug grâce à l’étape de vérification. Que faire d’une liste d’une seule note ? C’est une décision métier. Par exemple : renvoyer la note elle-même.

Erreur courante

Erreur 1 : sauter l’étape Reproduce. « L’utilisateur a dit que parfois ça ne marche pas », alors on change du code sans un cas qui reproduit le bug. Sans le reproduire, il n’y a aucun moyen de savoir que vous l’avez corrigé.

Erreur 2 : déboguer uniquement avec print, sans nettoyer. Les affichages temporaires (print("HERE", x)) sont un outil légitime et rapide, mais on les oublie dans le code et ils encombrent la sortie. Le débogueur montre toutes les variables sans modifier le code. (Et dans le thème exceptions et journalisation, nous apprendrons le Logging, la façon professionnelle de laisser des informations dans le code.)

Erreur 3 : Step Into sur tout. F11 sur print(...) ou sum(...) vous emmène dans le code interne de Python, et il est facile de s’y perdre. Utilisez Step Into sur vos propres fonctions, et Step Over sur les fonctions intégrées. (Si vous y êtes entré par erreur, Shift + F11 vous en fait sortir.)

Erreur 4 : corriger sans vérifier d’autres cas. Comme nous l’avons vu, l’étape Verify a révélé un second bug. Vérifiez toujours aussi les cas limites : une liste vide, un seul élément, des valeurs négatives, zéro.

Exercices

Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.

Exercice 1: Les cinq étapes

Remettez les étapes du débogage dans l’ordre, et expliquez en une phrase ce que vous faites à chaque étape :

Fix · Reproduce · Verify · Understand · Isolate

Afficher la solution
  1. Reproduce — faire réapparaître le bug, volontairement, avec une entrée connue.
  2. Isolate — réduire le champ : dans quelle fonction, à quelle ligne, avec quelle valeur le problème commence.
  3. Understand — comprendre pourquoi cela arrive, avant de modifier le moindre code.
  4. Fix — corriger la cause racine, pas le symptôme.
  5. Verify — relancer le cas qui a échoué, ainsi que des cas qui fonctionnaient avant.

Exercice 2: Le maximum qui sortait à 0

def find_max(numbers):
    largest = 0
    for number in numbers:
        if number > largest:
            largest = number
    return largest

print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))

Le second appel renvoie 0, alors que 0 n’est même pas dans la liste.

  1. Où placeriez-vous un Breakpoint, et que verriez-vous dans la fenêtre Variables ?
  2. Corrigez le bug.

Résultat attendu :

9
-3
Afficher la solution
  1. Un Breakpoint sur la ligne if number > largest:. Dans la fenêtre Variables, on voit que largest reste à 0 pendant toute la boucle — aucun nombre négatif n’est supérieur à 0.
  2. Partir du premier élément de la liste, et non de 0 :
def find_max(numbers):
    largest = numbers[0]
    for number in numbers:
        if number > largest:
            largest = number
    return largest


print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))
9
-3

Exercice 3: return à l’intérieur de la boucle

def count_vowels(word):
    count = 0
    for letter in word:
        if letter in "aeiou":
            count = count + 1
        return count

print(count_vowels("banana"))

La fonction renvoie 0 au lieu de 3. Utilisez Step Over pour suivre la boucle — que se passe-t-il après la première lettre ? Corrigez.

Résultat attendu :

3
Afficher la solution

Avec Step Over, on voit qu’après la première lettre (b, qui n’est pas une voyelle), l’exécution passe directement à return count — à l’intérieur de la boucle — et la fonction se termine avec 0. Le return est indenté d’un niveau de trop.

def count_vowels(word):
    count = 0
    for letter in word:
        if letter in "aeiou":
            count = count + 1
    return count


print(count_vowels("banana"))
3

Exercice 4: Les outils du débogueur

Expliquez chaque terme en une phrase, et quand vous l’utilisez :

  1. Breakpoint
  2. Step Over
  3. Step Into
  4. La fenêtre Variables
Afficher la solution
  1. Breakpoint — un point où l’exécution s’arrête, pour vérifier l’état à ce moment-là. On le place sur la ligne qui précède l’endroit suspect.
  2. Step Over — exécute la ligne actuelle et passe à la suivante, sans entrer dans les fonctions qui y sont appelées.
  3. Step Into — entre dans la fonction appelée sur la ligne actuelle, pour vérifier ce qui s’y passe.
  4. La fenêtre Variables — montre les valeurs de toutes les variables à l’instant présent. C’est là que l’on voit où une valeur diffère de ce que l’on attendait.

Exercice 5: Off-by-one

def sum_to(n):
    total = 0
    for i in range(1, n):
        total = total + i
    return total

print(sum_to(5))

La somme 1+2+3+4+5 devrait valoir 15, mais 10 sort. Trouvez le bug et corrigez-le.

Résultat attendu :

15
Afficher la solution

range(1, n) n’inclut pas n — la boucle ne tourne que sur 1, 2, 3, 4. C’est un bug classique « Off-by-one » (décalage de un).

def sum_to(n):
    total = 0
    for i in range(1, n + 1):
        total = total + i
    return total


print(sum_to(5))
15

Résumé

  • Les cinq étapes : Reproduce → Isolate → Root Cause → Fix → Verify. Sans deviner.
  • Un Breakpoint arrête avant l’exécution de la ligne. F5 = Continue, F10 = Step Over, F11 = Step Into, Shift+F11 = Step Out.
  • La fenêtre Variables montre l’état du programme au moment de l’arrêt. La Call Stack montre qui a appelé qui.
  • On trouve les Logical Errors en comparant les valeurs réelles des variables avec les valeurs attendues, ligne par ligne.
  • Après une correction, vérifiez aussi les cas limites : vide, un seul élément, zéro, négatif.

Documentation officielle