Aller au contenu

Paradigmes de programmation et mise au point

Terminale · NSI · Langages et programmation

Langages et programmation

Trois façons de programmer : impératif, objet, fonctionnel

Un même problème peut être résolu de plusieurs manières, non pas parce qu'on change de langage, mais parce qu'on change de façon de penser le programme. C'est ce qu'on appelle un paradigme de programmation. Python a la particularité d'en accepter plusieurs : c'est un langage multiparadigme. Savoir lequel tu utilises, et pourquoi, fait partie des attendus du programme de Terminale.

Le paradigme impératif

C'est celui que tu pratiques depuis la Seconde. Le programme est une suite d'instructions exécutées dans l'ordre, qui modifient progressivement l'état de la machine. Ses outils sont l'affectation de variables, les conditions et les boucles. On décrit comment obtenir le résultat, étape par étape.

somme = 0
for k in range(1, 11):
    somme = somme + k
print(somme)

Ici, la variable somme change de valeur dix fois : l'état évolue au fil de l'exécution. C'est la signature du style impératif.

Le paradigme objet. Le programme est organisé autour d'objets qui regroupent des données (les attributs) et les opérations qui agissent sur elles (les méthodes). Une classe est le moule, l'instance est l'objet fabriqué à partir de ce moule. La méthode __init__, appelée automatiquement à la création, initialise les attributs, et le premier paramètre self désigne l'instance elle-même.

class CompteBancaire:
    def __init__(self, solde):
        self.solde = solde

    def deposer(self, montant):
        self.solde = self.solde + montant

c = CompteBancaire(100)
c.deposer(50)

L'intérêt majeur est l'encapsulation : le reste du programme manipule le compte par ses méthodes, sans avoir à connaître la façon dont le solde est stocké. On peut donc changer l'intérieur de la classe sans casser le code qui l'utilise.

Le paradigme fonctionnel. Le programme est enfin vu comme une composition de fonctions, au sens mathématique du terme. On privilégie les fonctions pures, on évite les variables mutables et les effets de bord, et on remplace volontiers les boucles par la récursivité. On décrit quoi calculer plutôt que comment le calculer pas à pas.

ImpératifObjetFonctionnel
Unité de baseL'instructionL'objet (attributs + méthodes)La fonction
Gestion de l'étatVariables modifiées au fil du tempsÉtat interne encapsulé dans l'objetÉtat évité : on préfère l'immuabilité
RépétitionBoucles for et whileBoucles, souvent dans les méthodesRécursivité
Effets de bordFréquents et assumésContrôlés, limités à l'objetBannis autant que possible
Point fortSimple, proche du fonctionnement machineModélise bien les entités d'un problèmeSe teste et se raisonne facilement
Exemple d'usageAlgorithme de tri, parcours de tableauInterface graphique, simulationTransformation de données, calcul mathématique

Langages et programmation

Mettre au point un programme : spécification, assertions et tests

Écrire un programme qui fonctionne du premier coup n'arrive à personne. La mise au point n'est donc pas une punition infligée aux mauvais programmeurs : c'est une étape normale du travail, avec ses méthodes et ses outils. L'objectif n'est pas seulement de faire disparaître les erreurs, mais de gagner de bonnes raisons de croire que le programme est correct.

Trois familles d'erreurs se distinguent, et elles ne se repèrent pas du tout de la même façon.

Type d'erreurQuand elle se manifesteExempleDifficulté
Erreur de syntaxeAvant même l'exécution : le code n'est pas un texte Python valideOubli des deux-points après un if, parenthèse non ferméeFacile : signalée avec le numéro de ligne
Erreur d'exécutionPendant l'exécution : le programme s'interrompt brutalementDivision par zéro, indice hors des bornes d'une listeMoyenne : le message d'erreur oriente la recherche
Erreur de logiqueJamais signalée : le programme tourne, mais le résultat est fauxUne boucle qui s'arrête un tour trop tôt, un signe inverséDifficile : rien ne prévient, seuls les tests la révèlent

L'erreur de logique est la plus dangereuse, précisément parce qu'elle est silencieuse. Un programme qui plante t'avertit ; un programme qui répond faux te laisse croire que tout va bien.

Spécifier avant de coder. La spécification décrit ce que la fonction doit faire, indépendamment de la manière dont elle le fait : quels arguments elle attend, sous quelles conditions, et ce qu'elle renvoie. On l'écrit en Python dans une docstring, entre triples guillemets, juste après la ligne def. On y précise les préconditions (ce qui doit être vrai à l'entrée) et la postcondition (ce qui est garanti en sortie).

Construire un bon jeu de tests. Un jeu de tests n'est utile que s'il est varié. Tester trois fois le même cas ordinaire ne prouve rien. Un jeu sérieux comporte au minimum : des cas ordinaires, des cas limites (liste vide, valeur nulle, premier et dernier élément, valeur unique), et des cas interdits pour vérifier que les préconditions sont bien contrôlées.

Invariant et variant de boucle. Tester, c'est constater sur des exemples ; prouver, c'est établir la correction pour tous les cas. Deux outils y contribuent. L'invariant de boucle est une propriété vraie avant la boucle, préservée par chaque tour, et qui donne le résultat voulu à la sortie : il démontre la correction. Le variant de boucle est une quantité entière positive qui décroît strictement à chaque tour : comme elle ne peut décroître indéfiniment en restant positive, la boucle s'arrête. Il démontre la terminaison.

Exercices corrigés

  1. Qu'est-ce qui caractérise le paradigme impératif ?

    • A.Le programme est une composition de fonctions sans état modifiable
    • B.Le programme est une suite d'instructions qui modifient l'état des variablesBonne réponse
    • C.Le programme est un ensemble d'objets qui s'échangent des messages
    • D.Le programme décrit uniquement le résultat voulu, sans indiquer comment l'obtenir

    Réponse : B — Le programme est une suite d'instructions qui modifient l'état des variables

    Le style impératif décrit une succession d'étapes qui font évoluer l'état de la machine : affectations, conditions, boucles. C'est celui que tu utilises depuis tes débuts en Python. L'option 1 décrit le paradigme fonctionnel, l'option 3 le paradigme objet, et l'option 4 correspond à un style déclaratif, dont SQL est un bon représentant.

  2. On oublie les deux-points à la fin d'une ligne if. De quel type d'erreur s'agit-il ?

    • A.Une erreur d'exécution
    • B.Une erreur de syntaxeBonne réponse
    • C.Une erreur de logique
    • D.Une erreur de spécification

    Réponse : B — Une erreur de syntaxe

    Le texte n'est pas un programme Python valide : l'erreur est détectée avant même que la moindre instruction ne s'exécute. C'est une erreur de syntaxe, la plus facile à corriger puisque l'interpréteur indique la ligne fautive. Une erreur d'exécution surviendrait pendant le déroulement (division par zéro, indice hors bornes), et une erreur de logique ne serait jamais signalée du tout.

  3. Que doit impérativement contenir un bon jeu de tests, en plus des cas ordinaires ?

    • A.Au moins cent cas différents, pour couvrir toutes les valeurs possibles
    • B.Des cas limites, comme la liste vide, l'élément unique ou les valeurs extrêmesBonne réponse
    • C.Uniquement des cas où la fonction doit renvoyer une erreur
    • D.Des cas tirés au hasard, pour garantir l'impartialité du test

    Réponse : B — Des cas limites, comme la liste vide, l'élément unique ou les valeurs extrêmes

    Ce sont les frontières qui font tomber les programmes : liste vide, tableau à un seul élément, premier et dernier indice, valeur nulle ou négative. Multiplier les cas ordinaires n'apporte presque rien — dix tests bien choisis valent mieux que cent tests redondants. Les cas interdits sont utiles aussi, mais ils ne suffisent évidemment pas à eux seuls.

  4. Dans la définition d'une méthode Python, que représente le paramètre self ?

    • A.L'instance sur laquelle la méthode est appeléeBonne réponse
    • B.La classe elle-même, indépendamment de toute instance
    • C.Un mot-clé réservé du langage Python, obligatoire et non modifiable
    • D.La valeur que la méthode renverra à la fin de son exécution

    Réponse : A — L'instance sur laquelle la méthode est appelée

    Quand tu écris c.deposer(50), Python appelle en réalité la méthode en lui passant c comme premier argument : c'est ce que reçoit self. C'est pourquoi self.solde désigne l'attribut de cet objet-là précisément, et pas d'un autre. Nuance importante : self n'est pas un mot-clé réservé, seulement une convention universelle — un autre nom fonctionnerait, mais personne ne le fait.

  5. Un programme s'exécute sans le moindre message d'erreur, mais renvoie une moyenne manifestement fausse. De quel type d'erreur s'agit-il ?

    • A.Une erreur de syntaxe
    • B.Une erreur d'exécution
    • C.Une erreur de compilation
    • D.Une erreur de logiqueBonne réponse

    Réponse : D — Une erreur de logique

    Rien ne plante, rien n'est signalé, mais le résultat est faux : c'est le portrait exact de l'erreur de logique. Elle vient d'un raisonnement incorrect — une borne de boucle décalée, un opérateur inversé, une division placée au mauvais endroit. C'est la plus redoutable des trois, car seule une comparaison avec un résultat calculé indépendamment peut la révéler. Les erreurs de syntaxe et d'exécution, elles, se signalent bruyamment.

  6. Quelle caractéristique appartient au paradigme fonctionnel ?

    • A.L'usage systématique de variables globales pour partager l'information
    • B.La préférence pour la récursivité et l'absence d'effets de bordBonne réponse
    • C.L'organisation du code en classes et en instances
    • D.L'exécution des instructions strictement dans l'ordre où elles sont écrites

    Réponse : B — La préférence pour la récursivité et l'absence d'effets de bord

    Le style fonctionnel évite l'état modifiable : plutôt que de faire évoluer un accumulateur dans une boucle, on exprime le résultat par une définition récursive ou par composition de fonctions. Les variables globales sont exactement ce qu'il cherche à bannir, puisqu'elles créent des effets de bord. Les classes relèvent du paradigme objet, et l'exécution séquentielle du paradigme impératif.

  7. Une fonction racine(x) exige un argument positif ou nul. Quelle est la meilleure façon de traduire cette précondition dans le code ?

    • A.Ajouter un commentaire signalant que x doit être positif
    • B.Renvoyer 0 lorsque x est négatif, pour éviter tout plantage
    • C.Afficher un message avec print lorsque x est négatif, puis poursuivre le calcul
    • D.Écrire assert x >= 0 en première ligne de la fonctionBonne réponse

    Réponse : D — Écrire assert x >= 0 en première ligne de la fonction

    L'assertion est le seul dispositif qui garantit un arrêt immédiat et lisible en cas de violation. Un commentaire n'est pas exécuté et n'empêche donc rien. Renvoyer 0 est nettement pire : cela transforme une erreur bruyante en erreur silencieuse, qui se propagera dans tout le programme — exactement le contraire de ce qu'on cherche. Afficher un message puis continuer laisse aussi le calcul se poursuivre sur une base fausse.

  8. Pour une boucle while n > 1, avec n divisé par 2 à chaque tour à l'aide de la division entière, quel variant de boucle prouve la terminaison ?

    • A.Le nombre de tours déjà effectués, qui augmente à chaque itération
    • B.La valeur de la condition n > 1, qui finit par devenir fausse
    • C.La valeur de n elle-même : c'est un entier positif qui décroît strictement à chaque tourBonne réponse
    • D.La valeur 2, qui est le diviseur constant utilisé dans la boucle

    Réponse : C — La valeur de n elle-même : c'est un entier positif qui décroît strictement à chaque tour

    Un variant doit être un entier positif qui décroît strictement à chaque itération : comme un entier positif ne peut pas décroître indéfiniment, la boucle s'arrête forcément. Ici, n convient parfaitement, puisque n divisé par 2 est strictement inférieur à n tant que n vaut au moins 2. Le nombre de tours effectués croît au lieu de décroître, donc il ne convient pas. Une constante ne varie pas, et la condition elle-même n'est pas une quantité entière.

  9. Dans une classe Python, quelle méthode est appelée automatiquement lors de la création d'une nouvelle instance ?

    • A.self
    • B.create
    • C.__init__Bonne réponse
    • D.new

    Réponse : C — __init__

    La méthode __init__ est le constructeur : elle est exécutée dès qu'on écrit par exemple c = CompteBancaire(100), et sert à initialiser les attributs de l'instance. Attention, self n'est pas une méthode : c'est le premier paramètre des méthodes, qui reçoit l'instance elle-même. Les noms create et new n'ont aucun statut particulier en Python.

  10. À quoi sert l'instruction assert en Python ?

    • A.À vérifier qu'une condition est vraie et à interrompre le programme si elle ne l'est pasBonne réponse
    • B.À afficher la valeur d'une variable pour aider au débogage
    • C.À corriger automatiquement une erreur détectée dans le code
    • D.À déclarer le type attendu d'un paramètre de fonction

    Réponse : A — À vérifier qu'une condition est vraie et à interrompre le programme si elle ne l'est pas

    assert condition arrête immédiatement l'exécution si la condition est fausse, avec un message explicite si on en a fourni un. L'idée est qu'un arrêt net vaut mieux qu'un résultat faux propagé silencieusement dans tout le programme. Pour afficher une valeur, c'est print. Et aucune instruction Python ne corrige un bug toute seule.

  11. Qu'est-ce qu'une fonction pure ?

    • A.Une fonction qui ne contient aucune boucle
    • B.Une fonction qui n'utilise que des nombres entiers
    • C.Une fonction écrite sans utiliser de bibliothèque extérieure
    • D.Une fonction qui renvoie toujours le même résultat pour les mêmes arguments et ne modifie rien à l'extérieur d'elle-mêmeBonne réponse

    Réponse : D — Une fonction qui renvoie toujours le même résultat pour les mêmes arguments et ne modifie rien à l'extérieur d'elle-même

    Les deux critères sont indissociables : le déterminisme (mêmes entrées, même sortie) et l'absence d'effet de bord. Une fonction pure peut parfaitement contenir une boucle ou manipuler des chaînes de caractères : ce qui compte, c'est qu'elle ne touche à rien d'extérieur — ni variable globale, ni liste reçue en paramètre, ni affichage, ni fichier.

  12. Qu'est-ce qu'un test unitaire ?

    • A.Un test qui vérifie que le programme complet fonctionne du début à la fin
    • B.Un test qui mesure le temps d'exécution d'un algorithme
    • C.Un test qui vérifie le comportement d'une seule fonction sur un cas précis, en comparant le résultat obtenu au résultat attenduBonne réponse
    • D.Un test réalisé par un utilisateur qui essaie le logiciel

    Réponse : C — Un test qui vérifie le comportement d'une seule fonction sur un cas précis, en comparant le résultat obtenu au résultat attendu

    Unitaire signifie qu'on isole une unité de code — en pratique une fonction — pour la vérifier seule. On lui donne des entrées connues et on compare sa sortie à ce qu'on a calculé indépendamment, souvent avec un assert. Tester le programme entier relève des tests d'intégration, et mesurer un temps d'exécution relève de l'analyse de complexité, pas des tests.

S'entraîner sur ce chapitre

Ce chapitre compte 37 exercices corrigés dans l'application, avec le suivi de ta progression.

Commencer les exercices