Aller au contenu

Partie 4 : Mise au point des programmes et gestion des bugs

Programme officiel (B.O.)

B.O. spécial n° 8 du 25 juillet 2019 - NSI Terminale

Contenus Capacités attendues Commentaires
Mise au point des programmes. Gestion des bugs. Dans la pratique de la programmation, savoir répondre aux causes typiques de bugs : problèmes liés au typage, effets de bord non désirés, débordements dans les tableaux, instruction conditionnelle non exhaustive, choix des inégalités, comparaisons et calculs entre flottants, mauvais nommage des variables, etc. On prolonge le travail entrepris en classe de première sur l'utilisation de la spécification, des assertions, de la documentation des programmes et de la construction de jeux de tests. Les élèves apprennent progressivement à anticiper leurs erreurs.

1. Les types de bugs

1.1 Erreurs de syntaxe

Détectées avant l'exécution par l'interpréteur Python. Le programme ne démarre pas.

# Parenthèse manquante
print("bonjour"
# SyntaxError: unexpected EOF while parsing

# Indentation incorrecte
def f():
print("test")
# IndentationError: expected an indented block

1.2 Erreurs d'exécution (exceptions)

Détectées pendant l'exécution. Le programme s'arrête brutalement.

# Division par zéro
print(10 / 0)
# ZeroDivisionError

# Index hors limites
tab = [1, 2, 3]
print(tab[5])
# IndexError: list index out of range

# Type incompatible
print("age : " + 17)
# TypeError: can only concatenate str to str

1.3 Erreurs logiques (sémantiques)

Les plus difficiles à détecter : le programme s'exécute sans erreur mais produit un résultat faux.

# Bug : calcul de la moyenne
def moyenne(notes):
    return sum(notes) / len(notes) + 1    # le + 1 est une erreur

2. Causes typiques de bugs

2.1 Problèmes liés au typage

# Concaténation au lieu d'addition
age = input("Âge : ")    # renvoie une chaîne
print(age + 1)            # TypeError !
# Correction : int(input("Âge : "))

# Confusion entier / flottant
print(7 / 2)     # 3.5 (division réelle)
print(7 // 2)    # 3   (division entière)

2.2 Effets de bord non désirés

Un effet de bord se produit quand une fonction modifie quelque chose en dehors d'elle (variable globale, argument mutable).

# Bug classique : modification d'une liste passée en argument
def ajouter_element(liste, element):
    liste.append(element)    # modifie la liste originale !
    return liste

ma_liste = [1, 2, 3]
nouvelle = ajouter_element(ma_liste, 4)
print(ma_liste)    # [1, 2, 3, 4] - modifiée involontairement !
# Correction : travailler sur une copie
def ajouter_element(liste, element):
    copie = liste.copy()
    copie.append(element)
    return copie
# Bug classique : argument par défaut mutable
def ajouter(element, liste=[]):    # DANGER !
    liste.append(element)
    return liste

print(ajouter(1))    # [1]
print(ajouter(2))    # [1, 2] - surprise !
# Correction : utiliser None comme valeur par défaut
def ajouter(element, liste=None):
    if liste is None:
        liste = []
    liste.append(element)
    return liste

2.3 Débordements dans les tableaux

tab = [10, 20, 30]

# Bug : index hors limites
for i in range(len(tab) + 1):    # range(4) → 0, 1, 2, 3
    print(tab[i])                 # IndexError quand i = 3

# Correction
for i in range(len(tab)):        # range(3) → 0, 1, 2
    print(tab[i])

2.4 Instruction conditionnelle non exhaustive

# Bug : cas non traité
def categorie(age):
    if age < 12:
        return "enfant"
    elif age < 18:
        return "adolescent"
    # Que se passe-t-il si age >= 18 ? → renvoie None

# Correction
def categorie(age):
    if age < 12:
        return "enfant"
    elif age < 18:
        return "adolescent"
    else:
        return "adulte"

2.5 Choix des inégalités (erreur « off-by-one »)

# Bug : < au lieu de <=
def est_dans_intervalle(x, a, b):
    return a < x < b    # exclut a et b

# Si on veut inclure les bornes :
def est_dans_intervalle(x, a, b):
    return a <= x <= b
# Bug : boucle qui itère une fois de trop ou de moins
for i in range(1, 10):     # 1, 2, ..., 9 (pas 10 !)
for i in range(1, 11):     # 1, 2, ..., 10

2.6 Comparaisons entre flottants

# Bug : comparaison directe de flottants
print(0.1 + 0.2 == 0.3)    # False !
print(0.1 + 0.2)            # 0.30000000000000004

# Correction : comparer avec une tolérance
def presque_egal(a, b, epsilon=1e-9):
    return abs(a - b) < epsilon

print(presque_egal(0.1 + 0.2, 0.3))    # True

2.7 Mauvais nommage des variables

# Bug : confusion entre variables aux noms proches
longueur = 10
largeur = 5
aire = longueur * longueur    # devrait être longueur * largeur

# Bug : variable locale masquant une variable de portée supérieure
total = 100
def ajouter(n):
    total = 0        # crée une variable locale, ne modifie pas la globale
    total += n
    return total

3. Techniques de débogage

3.1 Affichage avec print

La méthode la plus simple : insérer des print() pour observer les valeurs des variables.

def recherche(tab, val):
    for i in range(len(tab)):
        print(f"  i={i}, tab[i]={tab[i]}, val={val}")    # debug
        if tab[i] == val:
            return i
    return -1

3.2 Assertions

Les assertions vérifient des conditions qui doivent être vraies. Si la condition est fausse, le programme s'arrête avec un message explicite.

def moyenne(notes):
    assert len(notes) > 0, "La liste de notes ne doit pas être vide"
    assert all(0 <= n <= 20 for n in notes), "Notes entre 0 et 20"
    return sum(notes) / len(notes)

3.3 Documentation et spécification

def recherche_dicho(tab, val):
    """Recherche val dans tab (trié) par dichotomie.

    Args:
        tab: liste triée par ordre croissant
        val: valeur à rechercher

    Returns:
        L'indice de val si trouvé, -1 sinon.

    Raises:
        AssertionError: si tab n'est pas trié.
    """
    assert all(tab[i] <= tab[i+1] for i in range(len(tab)-1)), "tab doit être trié"
    # ...

3.4 Jeux de tests

Tester systématiquement avec des cas variés :

def est_premier(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % 2 == 0:
            return False
    return True

# Jeu de tests
assert est_premier(2) == True
assert est_premier(3) == True
assert est_premier(4) == False
assert est_premier(17) == True
assert est_premier(1) == False
assert est_premier(0) == False
assert est_premier(-5) == False
# Bug détecté ! est_premier(9) renvoie True au lieu de False
# Cause : n % 2 au lieu de n % i dans la boucle

3.5 Le débogueur

Un débogueur (Thonny, VS Code, etc.) permet :

  • l'exécution pas à pas ;
  • l'inspection des variables à chaque étape ;
  • la pose de points d'arrêt (breakpoints) ;
  • la navigation dans la pile d'appels.

4. Bonnes pratiques pour éviter les bugs

Pratique Description
Nommer clairement indice_max plutôt que x ou temp
Tester au fur et à mesure Tester chaque fonction dès qu'elle est écrite
Écrire les tests d'abord Définir les cas de test avant de coder (TDD)
Limiter la portée Éviter les variables globales
Documenter Docstrings pour chaque fonction
Utiliser des assertions Vérifier les préconditions
Copier les mutables Ne pas modifier involontairement les listes passées en argument
Éviter la comparaison de flottants Utiliser une tolérance (epsilon)

À retenir

Type de bug Exemple Détection
Syntaxe Parenthèse manquante Avant l'exécution
Exécution Division par zéro, index hors limites Pendant l'exécution (exception)
Logique Résultat faux, condition non exhaustive Par les tests
Typage "5" + 3 TypeError à l'exécution
Effet de bord Modification involontaire d'une liste Par les tests
Off-by-one < au lieu de <=, range(n) vs range(n+1) Par les tests
Flottants 0.1 + 0.2 != 0.3 Comparaison avec epsilon