# Débogage A : ce qu'est un bug, les types d'erreurs et la lecture d'une stack trace<no value>

## Qu'allons-nous apprendre ?

- Ce qu'est un **bug**.
- Les trois types d'erreurs : **Syntax Error**, **Runtime Error**, **Logical Error**.
- Comment lire une **Stack Trace** (Traceback), le message d'erreur de Python.
- Les erreurs les plus courantes, et ce que dit chacune.

## Pourquoi en avons-nous besoin ?

Les développeurs passent une part énorme de leur temps à trouver et corriger des bugs. La différence entre un débutant et un professionnel n'est pas que le professionnel n'a pas de bugs, mais qu'il **lit le message d'erreur** et sait en quelques secondes où regarder. Les débutants voient beaucoup de texte rouge et paniquent. À la fin de la leçon, le texte rouge deviendra une carte.

## Explication à partir des bases

### Qu'est-ce qu'un bug ?

Un **bug** est **un écart entre ce que le programme est censé faire et ce qu'il fait réellement.** Parfois le programme plante, et parfois il « fonctionne » mais renvoie un résultat faux.

{{< box "tip" "Pourquoi appelle-t-on cela un bug ?" >}}
En 1947, les ingénieurs de l'ordinateur Mark II ont trouvé un papillon de nuit (un insecte, « bug » en anglais) coincé dans l'un des relais, et l'ont collé dans le journal de travail avec la légende « first actual case of bug being found ». Le mot servait déjà pour les pannes techniques avant cela, mais cette histoire l'a rendu célèbre.
{{< /box >}}

### Les trois types d'erreurs

| Type | Quand est-elle découverte | Python la signale-t-il ? | Exemple |
|---|---|---|---|
| **Syntax Error** | **Avant** l'exécution (lors de la vérification de la syntaxe) | Oui, et aucune ligne ne s'exécute | Un `:` ou une parenthèse manquante |
| **Runtime Error** (Exception) | **Pendant** l'exécution, sur la ligne problématique | Oui, et les lignes précédentes se sont déjà exécutées | Division par 0, une variable qui n'existe pas |
| **Logical Error** | **Jamais** automatiquement | **Non !** Le code s'exécute « avec succès » | `+` au lieu de `-`, une condition inversée |

Vous vous souvenez du thème sur l'ordinateur, le système d'exploitation et le réseau ? Une Syntax Error est détectée quand Python traduit en bytecode, avant l'exécution. Une Runtime Error se produit quand la PVM exécute une ligne précise et échoue.

**Une Logical Error est la plus dangereuse :** pas de message, pas de couleur rouge, juste un résultat faux. On ne la trouve que si l'on **vérifie** que le résultat est correct.

### La Stack Trace — la carte de l'erreur

Quand une Runtime Error se produit, Python affiche un **Traceback** (aussi appelé Stack Trace) : le chemin que le code a suivi jusqu'à l'erreur.

```text
Traceback (most recent call last):
  File "shop.py", line 9, in <module>
    total = checkout(cart)
  File "shop.py", line 5, in checkout
    return subtotal / count
ZeroDivisionError: division by zero
```

**Lisez-la de bas en haut :**

1. **La dernière ligne** — le **type** de l'erreur (`ZeroDivisionError`) et le **message** (`division by zero`). Commencez toujours ici.
2. **La ligne au-dessus** — **où** exactement : le fichier `shop.py`, ligne 5, dans la fonction `checkout`, et le code lui-même.
3. **Encore au-dessus** — **qui a appelé** cette fonction : la ligne 9, au niveau supérieur du fichier (`<module>`).

Pourquoi « Stack » ? Parce que chaque appel de fonction est empilé sur la **pile** des appels (la Call Stack). Le Traceback montre la pile au moment du plantage, du premier appel (en haut) au dernier (en bas).

### Les erreurs courantes en Python

| Erreur | Signification | Cause fréquente |
|---|---|---|
| `SyntaxError` | Le code n'a pas une syntaxe valide | Un `:`, des parenthèses ou des guillemets manquants |
| `IndentationError` | Mauvaise indentation | Mélange d'espaces, indentation incohérente |
| `NameError` | Un nom n'est pas défini | Une faute de frappe, une utilisation avant la définition |
| `TypeError` | Une opération sur un type inadapté | `"5" + 3`, appeler une fonction avec le mauvais nombre d'arguments |
| `ValueError` | Le bon type, une valeur invalide | `int("hello")` |
| `ZeroDivisionError` | Division par zéro | Un dénominateur qui n'a pas été vérifié |
| `IndexError` | Un indice en dehors de la liste | `items[5]` quand il y a 3 éléments |
| `KeyError` | Une clé qui n'existe pas dans un dict | (nous l'apprendrons dans le thème structures de données et fonctions) |
| `AttributeError` | L'objet n'a pas cet attribut/cette méthode | `"abc".push()`, une faute de frappe dans un nom de méthode |
| `FileNotFoundError` | Un fichier n'existe pas | Un mauvais chemin, le Working Directory |

## Syntaxe

Il n'y a pas de nouvelle syntaxe. L'outil, c'est la **lecture**. Le modèle de tout Traceback :

```text
Traceback (most recent call last):
  File "<file>", line <N>, in <function or <module>>
    <the code on that line>
  ...                                  ← more frames, each a function call
<ErrorType>: <message>                 ← START READING HERE
```

Et celui d'une Syntax Error (sans Traceback, car rien ne s'est exécuté) :

```text
  File "<file>", line <N>
    <the code>
        ^                              ← where Python noticed the problem
SyntaxError: <message>
```

## Premier exemple de code

`week-2/bug_report.py` :

```python
def average(numbers):
    total = sum(numbers)
    return total / len(numbers)

scores = []
print("Average score:", average(scores))
```

**Sortie :**

```text
Traceback (most recent call last):
  File "C:\dev\kodkod6\week-2\bug_report.py", line 7, in <module>
    print("Average score:", average(scores))
                            ^^^^^^^^^^^^^^^
  File "C:\dev\kodkod6\week-2\bug_report.py", line 3, in average
    return total / len(numbers)
           ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
```

## Analyse du code ligne par ligne

**Lecture du Traceback de bas en haut :**

| Ligne de la sortie | Ce qu'elle nous dit |
|---|---|
| `ZeroDivisionError: division by zero` | **Ce qui** s'est passé : une division par zéro. |
| `line 3, in average` + `return total / len(numbers)` | **Où** : ligne 3, dans `average`. Le `~~~^~~~` marque exactement l'opération de division. |
| `line 7, in <module>` + `average(scores)` | **Qui a appelé** : ligne 7, au niveau supérieur. `<module>` = « pas dans une fonction ». |

**La conclusion :** `len(numbers)` vaut 0, la liste est donc vide. Et qui a passé une liste vide ? La ligne 7 a passé `scores`, qui vaut `[]`. **Le vrai bug** est que nous n'avons pas traité le cas d'une liste vide. La ligne qui a planté (3) est le **symptôme**, et décider quoi faire d'une liste vide est la **correction** :

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)
```

## Un autre exemple

**Les trois types d'erreurs sur la même fonction.** La fonction doit calculer un prix après une remise en pourcentage.

**Syntax Error :**

```python
def price_after_discount(price, percent)
    return price - price * percent / 100
```

```text
  File "discount.py", line 1
    def price_after_discount(price, percent)
                                            ^
SyntaxError: expected ':'
```

Il manque les deux-points. Python n'a exécuté **aucune ligne**.

**Runtime Error :**

```python
def price_after_discount(price, percent):
    return price - price * percent / 100

print(price_after_discount("100", 10))
```

```text
Traceback (most recent call last):
  File "discount.py", line 4, in <module>
    print(price_after_discount("100", 10))
  File "discount.py", line 2, in price_after_discount
    return price - price * percent / 100
                   ~~~~~~~~~~~~~~~~^~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'
```

La syntaxe est valide, mais à l'exécution `price` est du texte. `"100" * 10` « fonctionne » (cela répète le texte 10 fois !), mais ensuite la division par 100 échoue. Le `^` pointe vers le `/`.

**Logical Error :**

```python
def price_after_discount(price, percent):
    return price - percent / 100

print(price_after_discount(100, 10))
```

```text
99.9
```

**Aucune erreur !** Mais une remise de 10 % sur 100 devrait renvoyer `90.0`. Nous avons oublié de multiplier par `price`. Python ne peut pas savoir ce que nous **voulions dire**. Seule une vérification par rapport à un résultat attendu le révélera.

## Erreur courante

**Erreur 1 : lire le Traceback depuis le haut, et paniquer.** Commencez toujours **par la dernière ligne** : le type et le message. Puis la ligne au-dessus. La plupart du temps, c'est tout ce dont vous avez besoin.

**Erreur 2 : corriger la ligne qui a planté, au lieu de la cause.** Dans le premier exemple, une « correction » comme `return total / (len(numbers) + 1)` fait disparaître l'erreur, mais renvoie un résultat **faux** dans tous les cas ! Cela a transformé une Runtime Error en Logical Error. Encore pire.

**Erreur 3 : chercher la Syntax Error uniquement sur la ligne indiquée dans le message.** Parfois le problème est sur la ligne **précédente** :

```python
prices = [10, 20, 30
total = sum(prices)
```

```text
  File "test.py", line 1
    prices = [10, 20, 30
             ^
SyntaxError: '[' was never closed
```

Python est arrivé à la ligne 2, et a découvert que le crochet de la ligne 1 n'avait jamais été fermé. Dans les versions récentes, le message est précis, mais en général : si la ligne marquée semble correcte, vérifiez celle d'avant.

<!-- 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: Quel type d'erreur ?

Pour chaque extrait — s'agit-il d'une SyntaxError, d'une Runtime Error ou d'une Logical Error ? Et quel est le problème ?

```text
# A
if age > 18
    print("adult")

# B
print(total_price)        # total_price was never defined

# C
def area(width, height):
    return width + height   # should be the area of a rectangle
```

{{< solution >}}
- **A — SyntaxError** : il manque les deux-points `:` à la fin de la ligne `if`. Aucune ligne ne s'exécute.
- **B — Runtime Error** (`NameError`) : la syntaxe est valide, mais à l'exécution Python ne trouve pas de variable nommée `total_price`.
- **C — Logical Error** : il n'y a aucune erreur, mais le résultat est faux — l'aire se calcule par une multiplication, `width * height`. On ne trouve cette erreur que si l'on vérifie le résultat.
{{< /solution >}}

### Exercice 2: Lire une Stack Trace

```text
Traceback (most recent call last):
  File "shop.py", line 12, in <module>
    print(checkout([20, 30], 0))
  File "shop.py", line 7, in checkout
    return average_price(prices, count)
  File "shop.py", line 3, in average_price
    return sum(prices) / count
ZeroDivisionError: division by zero
```

1. Quel est le type de l'erreur et quel est le message ?
2. Dans quel fichier, à quelle ligne et dans quelle fonction s'est-elle produite ?
3. D'où venait la valeur problématique ?

{{< solution >}}
1. **`ZeroDivisionError: division by zero`** — une division par zéro. On lit d'abord la **dernière** ligne.
2. Dans le fichier `shop.py`, **ligne 3**, dans la fonction `average_price` — c'est la ligne la plus basse de la liste.
3. On lit vers le haut : `average_price` a été appelée depuis la ligne 7 dans `checkout`, et `checkout` a été appelée depuis la ligne 12 avec `count = 0`. Le problème a donc commencé **dans l'appel de la ligne 12**, qui a passé 0.
{{< /solution >}}

### Exercice 3: Un bug logique dans une conversion de température

```python
def to_celsius(fahrenheit):
    return fahrenheit - 32 * 5 / 9

print(to_celsius(212))
print(to_celsius(32))
```

Les résultats devraient être 100 et 0, mais autre chose sort. Trouvez le bug et corrigez-le.

**Résultat attendu :**

```text
100.0
0.0
```

{{< solution >}}
```python
def to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9


print(to_celsius(212))
print(to_celsius(32))
```

```text
100.0
0.0
```

L'ordre des opérations : la multiplication et la division passent avant la soustraction, donc le code d'origine calculait `fahrenheit - (32 * 5 / 9)`. Les parenthèses obligent la soustraction à se faire d'abord. C'est une **Logical Error** — pas de message d'erreur, juste un résultat faux.
{{< /solution >}}

### Exercice 4: TypeError

```python
age = 25
print("Age: " + age)
```

1. Quelle erreur obtiendrez-vous, et pourquoi ?
2. Corrigez-la de deux façons différentes.

**Résultat attendu :**

```text
Age: 25
```

{{< solution >}}
1. **`TypeError: can only concatenate str (not "int") to str`** — on ne peut pas joindre du texte et un nombre avec `+`.
2. Deux façons :

```python
age = 25
print("Age: " + str(age))
```

```text
Age: 25
```

```python
age = 25
print("Age:", age)
```

```text
Age: 25
```

La première convertit le nombre en texte. La seconde passe deux valeurs à `print`, qui ajoute un espace entre elles.
{{< /solution >}}

### Exercice 5: La moyenne d'une liste vide

```python
def average(numbers):
    return sum(numbers) / len(numbers)

print(average([80, 90]))
print(average([]))
```

1. Que se passera-t-il lors du second appel, et quelle erreur obtiendrez-vous ?
2. Corrigez la fonction pour qu'elle renvoie 0 pour une liste vide.

**Résultat attendu :**

```text
85.0
0
```

{{< solution >}}
1. `len([])` vaut 0, vous obtenez donc **`ZeroDivisionError: division by zero`** — une erreur d'exécution.
2. Vérifiez ce cas **avant** la division :

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)


print(average([80, 90]))
print(average([]))
```

```text
85.0
0
```
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **Bug** = un écart entre le comportement voulu et le comportement réel.
- **Syntax Error** — avant l'exécution, aucune ligne ne s'exécute. **Runtime Error** — au milieu de l'exécution, avec un Traceback. **Logical Error** — pas de message, juste un résultat faux.
- **Lisez un Traceback de bas en haut :** type + message → ligne et fonction → qui a appelé.
- La ligne qui a planté n'est parfois que le **symptôme**. Cherchez la **cause**.
- Les Logical Errors ne se découvrent qu'en comparant un résultat avec une **valeur attendue**.

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

## Documentation officielle

- [Python Tutorial — Syntax Errors](https://docs.python.org/3/tutorial/errors.html#syntax-errors)
- [Python Tutorial — Exceptions](https://docs.python.org/3/tutorial/errors.html#exceptions)
- [Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions)

<!-- docs:end -->
