Mise au point des programmes. Gestion des bugs

Numérique et Sciences Informatiques

donnees

La programmation est une démarche très complexe, et comme c'est le cas dans toute activité humaine, on y commet de nombreuses erreurs. Pour des raisons anecdotiques, les erreurs de programmation s'appellent des « bugs » (ou « bogues », en France), et l'ensemble des techniques que l'on met en œuvre pour les détecter et les corriger s'appelle « debug » (ou « débogage »).
En fait, il peut exister dans un programme trois types d'erreurs assez différentes, et il convient que vous appreniez à bien les distinguer :

Exemples d'erreurs de syntaxe :
  • Erreurs de syntaxe: SyntaxError erreur de parenthèse, : manquant avant un bloc d'instruction.... ex: len([1,2,3))
  • Erreurs d'indentation: IndentationError indentation oubliée, ou trop grande.
l'erreur sémantique :
  • Le programme fonctionne parfaitement, aucun message d'erreur, mais le résultat n'est pas celui que vous attendiez : vous obtenez autre chose. La sémantique (la logique) est incorrecte.
Exemples d'erreurs à l'exécution :
  • Erreur de type TypeError Opération impossible entre deux types. ex: "3" * "5"
  • Erreur d'indexation: IndexError accés à un index non présent dans une liste. ex: [12,15,14][14],;
  • Erreurs de nom: NameError nom de fonction ou de variable mal orthographié. ex:help(est_pair)

Pour bien comprendre cette notion, voici une petite introduction de Gérard Berry du Collège de France sur le sujet

L'une des compétences les plus importantes à acquérir au cours de votre apprentissage est celle qui consiste à « déboguer » efficacement un programme. Il s'agit d'une activité intellectuelle parfois énervante mais toujours très riche, dans laquelle il faut faire montre de beaucoup de perspicacité.

Ce travail ressemble par bien des aspects à une enquête policière ou au travail expérimental en sciences. Vous examinez un ensemble de faits, et vous devez émettre des hypothèses explicatives pour reconstituer les processus et les événements qui ont logiquement entraîné les résultats que vous constatez.

Éviter les bugs

Pour éviter les bugs et rendre vos programmes plus sûrs, il y a des habitudes à développer :

  • Faire une analyse de votre problème assez détaillée. Trouver des cas limites qui pourront alimenter votre jeu de tests. Penser aux préconditions et post conditions pour trouver des cas à tester.
  • Décomposer votre programme en plusieurs sous-programmes : approche fonctionnelle, POO, modularité
  • Tester vos différents modules (fonctions, modules,etc)
  • Commenter vos modules avec des docstrings
  • Utiliser vos préconditions et postconditions pour écrire des assertions dans vos modules

Le typage des données

Python en apparence ne se soucie pas des types , cela allège la syntaxe du langage, mais la source de très nombreuses erreurs.

Il est possible de réaliser en python des annotations de type. Ces annotations constituent une indication pour le développeur. L'Environnement de Développement Intégré pourra les exploiter pour avertir en cas d'erreurs ou faire des suggestions pertinentes ce qui limite le risque d'erreurs.

def (x:int):
    return 2*x

def entier_double(x:int) -> int:
    return 2*x

La documentation

Il est important de bien distinguer commenter et documenter.

  • les commentaires sont destinés au programmeur, pour expliquer ce que l'on a fait, pourquoi on l'a fait.
  • la documentation est destinée aux utilisateurs, elle leur permet d'expliquer comment on utilise le programme du point de vue de l'utilisateur et non du programmeur.
  • Les docstrings sont des chaînes de commentaires qui doivent être placées juste en dessous des définitions de fonction ou de classe, ou bien tout en haut d'un module. L'intérêt, c'est que les docstrings sont récupérables dynamiquement via l'attribut __doc__, ou grâce à la fonction primitive help(ma_fonction). Cela permet notamment de faire de la documentation pour aider vos utilisateurs et vous-même avec la documentation de votre projet.

    def addition(a, b):
      """
      Cette fonction est une fonction de test
      Elle sert a calculer a + b
      :param a : Valeur 1 de type int
      :param b : Valeur 2 de type int
      :return : Somme des Valeur 1 et Valeur 2 de type int
      :CU : a et b entiers.
      """
      return a + b

    On peut accéder à la Docstring de la fonction addition(a,b) écrite précédemment comme ceci.

    help(addition)
    Help on function addition in module __main__:
    
    addition(a, b)
        Cette fonction est une fonction de test
        Elle sert a calculer a + b
        :param a : Valeur 1 de type int
        :param b : Valeur 2 de type int
        :return : Somme des Valeur 1 et Valeur 2 de type int
        :CU : a et b entiers.

    Les commentaires

    De bons commentaires vous feront gagner beaucoup de temps sur les projets car

    • ils facilitent l'examen du code par une personne tiers (dans le cadre d'un travail collaboratif par exemple)
    • ils vous seront utiles si vous replongez dans votre code après plusieurs mois d'interruption

    Voici un exemple de ce qu'on peut proposer comme amélioration. Il est éclairé par des commentaires qui mettent en relief l'algorithme utilisé.

    def tri_selection(tableau: list) -> list:
        tableau_trie = tableau[:]
        longueur = len(tableau_trie)
        for position in range(0, longueur):
            #### invariant de boucle ####
            # tableau_trie est trié des indices 0 à position (exclu)
            # à l'indice position se trouvera le minimum de la fin du tableau
    
            # recherche du min à partir de position+1
            for j in range(position+1, longueur):
                if tableau_trie[j] < tableau_trie[position]:
                    # on a trouvé en j une valeur inférieure, on échange avec position
                    tableau_trie[position], tableau_trie[j] = tableau_trie[j], tableau_trie[position]
    
        return tableau_trie

    Les tests

    Une bonne pratique est d'écrire des procédures automatisées de tests avant d'écrire la fonction ou le programme que l'on souhaite produire.
    Écrire un bon jeu de tests n'est pas si simple. Cela prend du temps mais au final, ce temps est largement récupéré car les tests étant automatisés, ils sont rapides à réaliser, y compris après chaque modification du code de votre programme. Détecter un bug et le résoudre prend souvent plus de temps que de se poser et réfléchir à des tests pertinents, surtout quand votre programme est composé de multiples fonctions susceptibles de poser problèmes. Si vous avez des tests unitaires pour chacune de vos fonctions, les tests vous indiqueront où se trouve le problème.

    Le mécanisme d'assertion est là pour empêcher des erreurs qui ne devraient pas se produire, en arrêtant prématurément le programme, avant d'exécuter le code qui aurait produit une erreur.

    assert test_booleen,"texte à afficher si test_booleen est faux"

Un exemple :

def est_pair(nombre) :
    """ Teste si un nombre entier est pair par le reste de la division entière entre le nombre et 2.
    :paramètre: nombre de type int
    :return :un booléen de type bool, True si nombre est pair, False sinon
    :CU : nombre >= 0

    Exemples
    >>> est_pair(6)
    True
    >>> est_pair(0)
    True
    >>> est_pair(1)
    False
    >>> est_pair(-4)
    Traceback (most recent call last):
    ...
    AssertionError: l'argument doit être un entier positif
    """
    assert(a>=0 and type(a)==int), "l'argument doit être un entier positif"
    if nombre % 2 == 0 :
    	test = True
    else :
    	test = False
    return test

Autre solution

Le module doctest permet d’intégrer des tests dans la docstring des fonctions. Les doctests sont repérées par la chaîne >>>. Écrire des doctests permet à la fois de réaliser des tests unitaires, mais aussi de documenter efficacement la fonction.

import doctest
doctest.testmod(verbose=True) # Pour avoir le compte rendu

Le débogage d'un programme

Il existe différentes façons de déboguer un programme :

  • Utiliser un débugueur
  • Utiliser un outil du type pythontutor pour analyser une exécution pas à pas lorsque cela est possible.
  • Utiliser des print() à des endroits bien placés dans le code que vous pourrez mettre en commentaire par la suite.

Savoir faire

  • 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.
  • Prototyper une fonction
  • Décrire les preconditions sur les arguments et les postconditions sur les résultats
  • Utiliser les jeux de tests
  • Utiliser la documentation d'une bibliothèque

Fiche de cours