# Débogage B : le processus de débogage et le débogueur de VS Code<no value>

## 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

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

{{< box "analogy" "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.
{{< /box >}}

### 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 :**

| Terme | Ce que c'est |
|---|---|
| **Breakpoint** | Un « 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. |
| **Variables** | Une fenêtre qui montre toutes les variables et leurs valeurs **à cet instant**. |
| **Watch** | Des expressions que vous avez choisi de suivre, par exemple `total / count`. |
| **Call Stack** | La chaîne des appels : quelle fonction a appelé laquelle. C'est le Traceback, mais « en direct ». |
| **Stop** (`Shift+F5`) | Arrêter le débogage. |

{{< box "analogy" "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.
{{< /box >}}

## 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` :

```python
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 :**

```text
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êt | Ligne jaune | Variables | Explication |
|---|---|---|---|
| 1 | `total = total + apply_tax(price)` | `price=100, total=0` | L'arrêt se fait **avant** l'exécution de la ligne, donc total vaut encore 0. |
| Après `F10` | `for price in prices:` | `total=117.0` | La ligne a été exécutée en entier, y compris la fonction. |
| 2 | `total = total + apply_tax(price)` | `price=250, total=117.0` | Deuxième tour. |
| Après `F11` | `tax = amount * 0.17` | `amount=250` | Nous sommes entrés dans la fonction. On ne voit que **ses** variables (Local). |
| Après `F10` | `return amount + tax` | `amount=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` :

```python
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 :**

```text
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 :

| Tour | `grade` | `grade < lowest` ? | `lowest` |
|---|---|---|---|
| Départ | — | — | 0 |
| 1 | 80 | `80 < 0` → False | 0 |
| 2 | 90 | `90 < 0` → False | 0 |
| 3 | 70 | `70 < 0` → False | 0 |
| 4 | 100 | `100 < 0` → False | 0 |

**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 :

```python
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 :**

```text
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.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## 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

{{< 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.
{{< /solution >}}

### Exercice 2: Le maximum qui sortait à 0

```python
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 :**

```text
9
-3
```

{{< 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 :

```python
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]))
```

```text
9
-3
```
{{< /solution >}}

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

```python
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 :**

```text
3
```

{{< 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.

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


print(count_vowels("banana"))
```

```text
3
```
{{< /solution >}}

### 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

{{< 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.
{{< /solution >}}

### Exercice 5: Off-by-one

```python
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 :**

```text
15
```

{{< 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).

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


print(sum_to(5))
```

```text
15
```
{{< /solution >}}

<!-- exercises:end -->

## 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.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Documentation officielle

- [VS Code — Python debugging](https://code.visualstudio.com/docs/python/debugging)
- [VS Code — Debugging: Breakpoints](https://code.visualstudio.com/docs/debugtest/debugging#_breakpoints)

<!-- docs:end -->
