Retour à la page d'accueilNSI · Terminale (maths expertes) · Changer de matière
Commencer gratuitement
Terminale (maths expertes) · NSI
Structures de données, bases de données et algorithmique · Algorithmique avancée et programmation

Programmation, tests et calculabilité

Tu rends un devoir maison de NSI : ton programme marche sur ton exemple, mais le professeur le teste avec d'autres données et il plante. Comment être sûr qu'une fonction est correcte ? Et un logiciel pourrait-il, à lui seul, vérifier que n'importe quel programme ne plante jamais ? Cette page répond à ces deux questions.

Spécifier, tester, documenter

Une fonction a un contrat : ce qu'elle attend (précondition), ce qu'elle renvoie (postcondition). On l'écrit dans une docstring, juste sous la ligne def. Elle sert à celui qui utilise la fonction sans lire son code.

Une précondition se vérifie avec assert : si la condition est fausse, le programme s'arrête avec une erreur claire, au lieu de produire un résultat faux en silence.

Un jeu de tests est une série de assert qui comparent le résultat obtenu au résultat attendu. On teste :

  • un cas général ;

  • les cas limites : liste à un seul élément, valeur nulle, valeurs négatives, liste vide ;

  • des cas où l'on connaît la réponse à l'avance.

Trouver un bogue

Causes fréquentes : mauvaises bornes d'un range, variable mal initialisée, division par zéro, condition non exhaustive, comparaison de flottants avec ==.

Pour les flottants, 0.1 + 0.2 == 0.3 est faux à cause des arrondis : on compare avec une tolérance, par exemple abs(a - b) < 1e-9.

Entraîne-toi★☆☆Pas commencé
On considère la fonction : a) Écrire trois tests sous forme d'assert : un cas général, un cas à un seul élément et un cas avec des valeurs négatives. b) Écrire une…

On considère la fonction :

def maximum(liste):

    """Renvoie le plus grand élément de liste (non vide)."""

    m = liste[0]

    for x in liste:

        if x > m:

            m = x

    return m
  1. a) Écrire trois tests sous forme d'assert : un cas général, un cas à un seul élément et un cas avec des valeurs négatives.
  1. b) Écrire une précondition avec assert sur la liste.
  1. c) Expliquer le rôle de la docstring.
Résoudre
Entraîne-toi★★☆Pas commencé
La fonction suivante doit renvoyer la moyenne d'une liste de nombres : a) Trouver le bogue et donner un jeu de test qui le met en évidence. b) Corriger la fonction. c)…

La fonction suivante doit renvoyer la moyenne d'une liste de nombres :

def moyenne(liste):

    s = 0

    for i in range(1, len(liste)):

        s = s + liste[i]

    return s / len(liste)
  1. a) Trouver le bogue et donner un jeu de test qui le met en évidence.
  1. b) Corriger la fonction.
  1. c) Indiquer le comportement pour une liste vide et proposer une protection.
Résoudre

Programmation objet et modularité

Une classe décrit un type d'objets : ses attributs (les données) et ses méthodes (les fonctions qui agissent sur l'objet). Un objet est une instance de la classe. self désigne l'objet courant et __init__ est le constructeur.

class Compte:

    def __init__(self, titulaire, solde):

        self.titulaire = titulaire

        self.solde = solde

    def deposer(self, montant):

        self.solde = self.solde + montant

c = Compte("Lina", 80)

c.deposer(20)

print(c.solde)   # 100

Ici : classe Compte, attributs titulaire et solde, méthodes __init__ et deposer. On accède à un attribut par objet.attribut et on appelle une méthode par objet.methode(...).

Interface et implémentation

L'interface d'une structure est la liste de ses opérations (leurs noms, ce qu'elles font). L'implémentation est la façon dont elles sont programmées. Une pile a pour interface empiler, depiler, est_vide ; on peut l'implémenter avec une liste Python ou avec une liste chaînée : le code utilisateur ne change pas.

Modules et API

Un module est un fichier Python réutilisable. On l'importe : from math import sqrt. Son interface (API) est l'ensemble des fonctions documentées qu'il propose : on s'en sert sans connaître leur code, en lisant leur documentation.

Avantages de la modularité : on découpe un gros programme en morceaux, on réutilise le code, on teste chaque module séparément, et on travaille à plusieurs.

Entraîne-toi★☆☆Pas commencé
On définit une classe : a) Identifier la classe, les attributs et les méthodes. b) Écrire les instructions qui créent un objet c pour « Camille » avec 100 €, puis…

On définit une classe :

class Compte:

    def __init__(self, titulaire, solde):

        self.titulaire = titulaire

        self.solde = solde

    def deposer(self, montant):

        self.solde = self.solde + montant
  1. a) Identifier la classe, les attributs et les méthodes.
  1. b) Écrire les instructions qui créent un objet c pour « Camille » avec 100 €, puis déposent 50 €.
  1. c) Donner la valeur de c.solde ensuite.
Résoudre
Entraîne-toi★☆☆Pas commencé
a) Expliquer ce qu'est un module Python et donner l'instruction pour importer la fonction sqrt du module math. b) Expliquer la notion d'interface (API) d'une…
  1. a) Expliquer ce qu'est un module Python et donner l'instruction pour importer la fonction sqrt du module math.
  1. b) Expliquer la notion d'interface (API) d'une bibliothèque en prenant cet exemple.
  1. c) Citer deux avantages de la modularité pour un grand programme.
Résoudre
Entraîne-toi★★☆Pas commencé
On implémente une pile avec une classe Pile contenant la liste contenu. a) Écrire les méthodes empiler, depiler et est_vide. b) Écrire une docstring et une précondition…

On implémente une pile avec une classe Pile contenant la liste contenu.

  1. a) Écrire les méthodes empiler, depiler et est_vide.
  1. b) Écrire une docstring et une précondition (assert) pour depiler.
  1. c) Expliquer la différence entre l'interface d'une pile et son implémentation, en proposant une autre implémentation possible.
Résoudre
Entraîne-toi★★★Pas commencé
On souhaite écrire un module geometrie.py qui gère des points du plan et des segments. a) Proposer une classe Point (attributs x, y) avec une méthode distance(autre),…

On souhaite écrire un module geometrie.py qui gère des points du plan et des segments.

  1. a) Proposer une classe Point (attributs x, y) avec une méthode distance(autre), puis une classe Segment construite à partir de deux points avec une méthode longueur.
  1. b) Écrire une batterie de tests avec assert : le segment de (0 ; 0) à (3 ; 4) mesure 5 ; un segment réduit à un point mesure 0 ; la distance est symétrique.
  1. c) Expliquer pourquoi on teste aussi les cas limites et discuter l'usage de la comparaison de flottants.
  1. d) Rédiger la documentation de l'API publique du module à l'intention d'un autre développeur.
Résoudre

Récursivité et paradigmes

Une fonction est récursive si elle s'appelle elle-même. Il faut toujours :

  • un cas de base qui s'arrête sans appel récursif ;

  • un appel récursif sur un problème plus petit, qui se rapproche du cas de base.

La version itérative (paradigme impératif) utilise une boucle et une variable modifiée. La version récursive (style plus fonctionnel) exprime directement la définition. Le paradigme objet organise le code en classes.

Terminaison et correction

  • Terminaison : on exhibe une quantité entière positive qui diminue strictement à chaque appel (ici $n$, puis $n // 10$).

  • Correction : on la prouve par récurrence, la propriété pour les petites valeurs servant à la prouver pour $n$ (récurrence forte).

Entraîne-toi★★☆Pas commencé
a) Écrire une fonction récursive somme_chiffres(n) qui renvoie la somme des chiffres d'un entier $n \ge 0$. b) Dérouler somme_chiffres(4 276). c) Écrire une version…
  1. a) Écrire une fonction récursive somme_chiffres(n) qui renvoie la somme des chiffres d'un entier $n \ge 0$.
  1. b) Dérouler somme_chiffres(4 276).
  1. c) Écrire une version itérative et comparer les deux approches (paradigmes récursif et impératif).
Résoudre
Entraîne-toi★★★Pas commencé
On considère la fonction récursive : a) Dérouler puissance(2, 13) et compter les multiplications. b) Montrer par récurrence forte la correction : puissance(x, n) renvoie…

On considère la fonction récursive :

def puissance(x, n):

    if n == 0:

        return 1

    if n % 2 == 0:

        y = puissance(x, n // 2)

        return y * y

    return x * puissance(x, n - 1)
  1. a) Dérouler puissance(2, 13) et compter les multiplications.
  1. b) Montrer par récurrence forte la correction : puissance(x, n) renvoie $x^n$.
  1. c) Justifier la terminaison à l'aide d'une quantité strictement décroissante.
  1. d) Montrer que le nombre d'appels est en $O(\log_2 n)$ et comparer avec la méthode naïve.
Résoudre

Calculabilité : le problème de l'arrêt

Un programme est écrit dans un fichier texte : c'est donc aussi une donnée. Un compilateur, un antivirus ou un éditeur de code prennent des programmes en entrée. On peut passer un programme en argument d'une autre fonction, voire à lui-même.

La calculabilité étudie ce qu'un algorithme peut ou non résoudre, indépendamment du langage de programmation. Un problème est décidable s'il existe un algorithme qui répond toujours (oui ou non) en temps fini.

Problème de l'arrêt : existe-t-il une fonction arret(prog, donnee) qui renvoie True si prog(donnee) s'arrête et False s'il boucle indéfiniment ?

Conséquence : aucun outil automatique ne peut vérifier, pour tout programme, qu'il se termine (ni, plus généralement, qu'il est correct). Les outils de vérification fonctionnent donc sur des familles restreintes de programmes, et les tests restent indispensables.

Entraîne-toi★★★Pas commencé
On veut démontrer que le problème de l'arrêt est indécidable. Supposons qu'il existe une fonction arret(prog, donnee) qui renvoie True si le programme prog appliqué à…

On veut démontrer que le problème de l'arrêt est indécidable. Supposons qu'il existe une fonction arret(prog, donnee) qui renvoie True si le programme prog appliqué à donnee s'arrête, False sinon.

  1. a) Expliquer pourquoi un programme peut être traité comme une donnée.
  1. b) On définit paradoxe(p) : si arret(p, p) renvoie True, la fonction boucle indéfiniment ; sinon elle renvoie 0. Que se passe-t-il pour paradoxe(paradoxe) ?
  1. c) En déduire la conclusion et expliquer ce que cela implique pour un outil de vérification automatique de programmes.
Résoudre

Vers le début : les tests ne font que révéler la présence de bogues, et aucun logiciel ne peut tout vérifier à notre place.

L'essentiel

  • Une fonction se spécifie par une docstring et des préconditions (assert), et se teste par un jeu de tests incluant les cas limites. Un test réussi ne prouve pas la correction.

  • Une classe regroupe attributs et méthodes ; l'interface (opérations) est distincte de l'implémentation.

  • Un module s'utilise par son API documentée ; la modularité facilite réutilisation, tests et travail en équipe.

  • Une fonction récursive a un cas de base et un appel sur un problème plus petit ; on justifie la terminaison par une quantité strictement décroissante.

  • Un programme est une donnée ; le problème de l'arrêt est indécidable.

Voir tous les exercices de cette partie