Aller au contenu principal

La modularité : découper un algorithme

Ce que ce chapitre apporte 5 points
  • Définir un module, et distinguer un bon découpage d'un mauvais.
  • Organiser un programme en fonctions cohérentes, reliées par leurs seuls paramètres et valeurs de retour.
  • Décomposer un problème réel du haut vers le bas : le programme principal d'abord, les fonctions ensuite.
  • Concevoir une décomposition hiérarchique, et nommer ce qu'elle interdit.
  • Écrire ce qu'un module suppose de ses entrées.

Un programme qui tient en une page se lit. Un programme qui tient en dix ne se lit plus, sauf s'il est découpé. Celui qui surveille la chambre froide d'un entrepôt doit lire les relevés d'un capteur, écarter ceux qu'il a perdus, calculer une moyenne et compter les dépassements de seuil : écrit d'un seul bloc, il mêle tout, et une correction apportée à la moyenne peut casser le comptage sans que rien ne le signale. La modularité consiste à répartir le travail entre des morceaux nommés, les modules, qui font chacun une seule chose et se vérifient chacun à part, puis à écrire un programme principal qui se contente de les enchaîner. Où couper, et à quoi reconnaître un bon découpage ? C'est la différence entre un tas d'instructions et un programme qu'on peut reprendre six mois plus tard.

Le chapitre suppose acquis les fonctions et leurs paramètres : un module n'est rien d'autre qu'une fonction, qui ne communique que par ses paramètres et sa valeur de retour. Les relevés circulent d'un module à l'autre dans des tableaux.

Pourquoi décomposer un programme ?

Définition

La décomposition consiste à séparer un problème complexe en sous-problèmes plus simples, et à confier chacun à un module. Chaque module peut ensuite être écrit, testé et corrigé indépendamment des autres.

Un programme complet peut être vu comme une machine composée de plusieurs engrenages : chaque engrenage joue un rôle précis, et l'ensemble ne fonctionne que si chaque partie fait bien son travail. Pour un programme de contrôle qualité en sortie d'une ligne d'ensachage, par exemple :

  • un module lit la masse de chaque sachet ;
  • un autre calcule la masse moyenne ;
  • un troisième affiche le résultat.

Cette approche permet de raisonner par étapes, et de chercher une erreur dans un module de dix lignes plutôt que dans un programme de cent.

La question à se poser avant de découper

« Puis-je expliquer mon algorithme en trois ou quatre grandes étapes claires ? » Si oui, chacune est un module en puissance. Si non, le problème n'est pas encore compris.

Vérification rapideon peut se reprendre

Pourquoi découper un programme en modules ?

Qu'est-ce qu'un module ?

Définition

Dans ce cours, un module est une fonction qui remplit une tâche précise, et une seule : lire les mesures, calculer une moyenne, afficher un résultat. Qu'elle rende une valeur ou se contente d'agir ne change rien à ce rôle. En Python, le mot désigne un fichier qui regroupe plusieurs fonctions, comme math : c'est la même idée, à une autre échelle.

Voici le programme de contrôle qualité, découpé en trois modules :

Algorithme
pas 1 / 33
séparées par des virgules, décimales au point
Fonction LireMesures()
Lire n
Pour i de 1 à n
Lire t[i]
FinPour
Retourner t
FinFonction
Fonction Moyenne(t)
somme ← 0
Pour i de 1 à longueur(t)
somme ← somme + t[i]
FinPour
Retourner somme / longueur(t)
FinFonction
Fonction AfficherResultat(m)
Écrire "Masse moyenne : ", m, " g"
FinFonction
Début
masses ← LireMesures()
m ← Moyenne(masses)
AfficherResultat(m)
Fin

programme principal

aucune variable

Le programme principal se limite à enchaîner des modules : la lecture, le calcul et l'affichage. Chaque fonction fait une seule chose, et son nom suffit à le dire : un module qu'on ne peut nommer sans « et » en fait deux.

Diagramme de séquence
Diagramme de séquence entre Principal, LireMesures, Moyenne, AfficherResultat, en 5 messages :
  1. Principal vers LireMesures : appel sans argument.
  2. LireMesures vers Principal, en réponse : le tableau des masses.
  3. Principal vers Moyenne : le tableau.
  4. Moyenne vers Principal, en réponse : la moyenne.
  5. Principal vers AfficherResultat : la moyenne. Note : aucune flèche ne relie deux modules entre eux.

Toutes les flèches partent du programme principal ou y reviennent. Aucun module n'en appelle un autre, et aucune donnée ne circule autrement que par un argument ou une valeur retournée. C'est cette forme en étoile qui rend chaque module utilisable ailleurs, tel quel.

Le tableau circule par les deux portes, jamais autrement

LireMesures construit le tableau et le retourne ; le programme principal le range dans masses et le passe à Moyenne. À aucun moment les deux fonctions ne partagent une variable.

On pourrait croire plus simple de ranger le tableau dans une variable que les deux fonctions liraient directement, ce que certains langages permettent. On gagnerait quelques caractères, et l'on perdrait la propriété qui fait tout l'intérêt du découpage : Moyenne se relit, se teste et se remplace sans savoir qui l'appelle ni d'où vient le tableau. Parce qu'elle parcourt longueur(t) cases, elle calcule la moyenne de n'importe quel tableau de nombres, de cinq sachets comme de cinq cents, et rien d'autre.

Vérification rapideon peut se reprendre

1.Deux modules doivent échanger une information. Comment ?

2.Qu'est-ce qui distingue un bon découpage d'un mauvais ?

Communication entre modules

Les modules échangent des données à l'aide de paramètres (les entrées) et de valeurs de retour (les sorties). C'est ce qui permet à chaque fonction de travailler indépendamment tout en participant à la résolution du problème. Un module peut aussi en appeler un autre, pourvu que tout passe par ces deux portes :

Algorithme
pas 1 / 6
séparées par des virgules, décimales au point
Fonction Montant(prix, quantite)
Retourner prix * quantite
FinFonction
Fonction AfficherLigne(prix, quantite)
Écrire quantite, " × ", prix, " euros = ", Montant(prix, quantite), " euros"
FinFonction
Début
Lire prix, quantite
AfficherLigne(prix, quantite)
Fin

programme principal

prix12,5quantitesaisi4

Le programme principal lit les données, AfficherLigne met en forme une ligne de facture, et Montant effectue le calcul, sans rien lire ni rien afficher. Chacune se teste seule, et Montant resservira pour le total d'une commande entière.

Aucune variable n'est commune à deux modules

L'erreur consiste à chercher à faire communiquer deux modules par une variable du programme principal. Une variable créée dans une fonction n'existe que dans cette fonction et disparaît quand elle se termine ; une variable du programme principal n'est pas visible non plus depuis une fonction. Dans la convention de ce cours, aucune variable n'est donc commune à deux modules. D'autres langages en admettent, sous le nom de variables globales : Python laisse une fonction lire une variable du programme principal. C'est précisément la dépendance invisible que ce chapitre demande d'éviter.

Le chapitre précédent en donne la démonstration déroulable, dans les deux sens : un algorithme qui réclame, après l'appel, une variable ayant appartenu à la fonction, et une fonction qui réclame une variable du programme principal. Les deux s'arrêtent net à la ligne fautive ; le chapitre Les fonctions et leurs paramètres les contient, et il n'y a pas lieu de les refaire ici.

Ce qui traverse la frontière, c'est ce que Retourner rend, et rien d'autre. Une variable de travail n'a pas à survivre : c'est même ce qui permet à deux modules d'employer le nom i ou somme sans jamais se gêner.

Vérification rapideon peut se reprendre

Un même traitement apparaît à trois endroits. Que faire ?

La décomposition hiérarchique

La figure en étoile n'est pas la seule forme possible, et ce n'est pas la plus fréquente. Dès qu'un problème dépasse trois ou quatre étapes, un module se découpe à son tour, et l'on obtient un arbre, ou presque : le programme principal appelle des modules, qui en appellent de plus petits, et une brique commune peut servir à deux parents.

ControlerLotle programme principal
LireMassespèse chaque sachet du lot
EtablirBilanproduit la moyenne et le verdict
Moyennemoyenne d'un tableau
CompterConformessachets dans la tolérance
EstConformeune masse est-elle entre 495 et 505 g
AfficherRapportmet en forme et écrit
FormaterLigneune ligne du rapport
La décomposition d'un contrôle qualité en sortie de ligne. Chaque niveau ne connaît que celui juste en dessous, et jamais celui du dessus.

Ce qui est permis et ce qui ne l'est pas tient en deux règles.

Un module appelle ce qui est en dessous de lui, jamais au-dessus. CompterConformes peut appeler EstConforme ; EstConforme ne doit rien savoir de CompterConformes, ni du lot, ni du rapport. C'est ce qui permet de réutiliser EstConforme dans un tout autre programme.

Aucun cycle. Si A appelle B et que B finit par rappeler A, le découpage est faux : les deux modules n'en forment qu'un, mal coupé, et aucun des deux ne se teste seul. La seule exception courante est la récursivité, où une fonction s'appelle elle-même volontairement, sujet du chapitre suivant.

La profondeur se lit comme un sommaire : chaque niveau répond à la question « comment ? » posée au niveau du dessus, et chaque niveau du dessus répond au « pourquoi ? » du dessous.

Algorithme
pas 1 / 24
Fonction EstConforme(masse)
Retourner masse >= 495 ET masse <= 505
FinFonction
Fonction CompterConformes(t)
c ← 0
Pour i de 1 à longueur(t)
Si EstConforme(t[i]) Alors
c ← c + 1
FinSi
FinPour
Retourner c
FinFonction
Début
masses ← [502, 491, 499, 507]
Écrire "Sachets conformes : ", CompterConformes(masses)
Fin

programme principal

masses[502, 491, 499, 507]
À manipuler
Dérouler et regarder les encadrés placés sous le listing, un par appel en cours : le chapitre suivant les appellera des cadres, et leur empilement la pile des appels. Ils sont jusqu'à trois : le programme principal qui attend, CompterConformes qui attend à son tour, et EstConforme qui travaille. Le troisième naît et meurt quatre fois, une par sachet examiné. Remarquer surtout que EstConforme ne voit ni t, ni c, ni i : elle reçoit un nombre et rend un booléen, ce qui est tout ce qu'il faut savoir d'elle pour l'employer ailleurs.
Vérification rapideon peut se reprendre

Dans une décomposition hiérarchique, qu'est-ce qui est interdit ?

Décomposer un problème réel, du haut vers le bas

Dans la convention de ce cours, un programme découpé s'écrit dans un ordre fixe : d'abord les fonctions, chacune entre Fonction et FinFonction, puis le programme principal entre Début et Fin, qui se contente de les appeler. Il n'y a pas de partie réservée aux déclarations : une variable naît à sa première affectation. L'ordre de lecture est l'inverse : on lit d'abord le programme principal, comme une table des matières, puis on ne descend que dans les fonctions dont on a besoin, comme on ouvre un chapitre.

Un programme se conçoit lui aussi dans l'ordre inverse de celui où il s'écrit. Le découpage ne se trouve pas en écrivant les fonctions : il se trouve en écrivant d'abord ce qu'on voudrait pouvoir appeler.

Soit une chambre froide dont un capteur relève la température toutes les heures. Quand il perd la mesure, il écrit -99. On veut la température moyenne de la journée et le nombre de relevés au-dessus de 5 degrés, seuil au-delà duquel la marchandise se dégrade.

Le programme principal d'abord, comme si les fonctions existaient. Il tient en quelques lignes et se lit comme l'énoncé : afficher la moyenne des relevés, afficher le nombre de dépassements.

Algorithme
Début
Écrire "Température moyenne : ", MoyenneValides(releves)
Écrire "Relevés au-dessus du seuil : ", CompterDepassements(releves, seuil)
Fin

Cette esquisse ne s'exécute pas encore, puisque rien n'y est défini : elle fixe seulement ce qu'il faudra écrire.

Les en-têtes ensuite, avec leurs portes. L'esquisse fait apparaître deux fonctions : MoyenneValides reçoit un tableau et rend un nombre, CompterDepassements reçoit un tableau et un seuil et rend un entier.

Les corps enfin, et ce qu'ils ont en commun. En les écrivant, on s'aperçoit que les deux fonctions doivent écarter les -99 : ce besoin commun devient une troisième fonction, EstValide, que les deux appellent. Chaque fonction s'écrit alors seule, et se vérifie seule, avant l'assemblage.

Algorithme
pas 1 / 72
Fonction EstValide(mesure)
Retourner mesure > -40 ET mesure < 40
FinFonction
Fonction MoyenneValides(t)
// suppose : au moins un relevé valide dans t
somme ← 0
n ← 0
Pour i de 1 à longueur(t)
Si EstValide(t[i]) Alors
somme ← somme + t[i]
n ← n + 1
FinSi
FinPour
Retourner somme / n
FinFonction
Fonction CompterDepassements(t, seuil)
c ← 0
Pour i de 1 à longueur(t)
Si EstValide(t[i]) ET t[i] > seuil Alors
c ← c + 1
FinSi
FinPour
Retourner c
FinFonction
Début
releves ← [3.5, 4.1, -99, 5.2, 6.8, 4.4]
Écrire "Température moyenne : ", MoyenneValides(releves)
Écrire "Relevés au-dessus de 5 degrés : ", CompterDepassements(releves, 5)
Fin

programme principal

releves[3,5, 4,1, -99, 5,2, 6,8, 4,4]

Déroulée, la figure affiche une moyenne de 4,8 degrés et deux dépassements, et trois encadrés sont ouverts à la fois chaque fois qu'EstValide travaille pour l'une ou l'autre fonction. EstValide a deux parents : ce n'est pas un défaut, c'est la réutilisation que le découpage promettait.

La première ligne de MoyenneValides n'est pas une instruction mais une hypothèse, écrite en commentaire : la fonction suppose au moins un relevé valide, faute de quoi n resterait à zéro et elle diviserait par zéro. L'écrire n'est pas une politesse. Un module qui marche seul et échoue une fois assemblé a presque toujours reçu ce qu'il ne prévoyait pas ; l'hypothèse écrite dit à celui qui l'appelle ce qu'il doit garantir.

Vérification rapideon peut se reprendre

Un module fonctionne seul mais échoue une fois assemblé. La cause la plus probable ?

Mettre en pratique

Un découpage se lit aussi en nombres : ce que chaque module rend, combien de fois il est appelé, et combien de cadres s'empilent quand le plus petit travaille.

Ce que les modules rendent, et combien de fois ils sont appelés

  • 1.

    Le programme de la chambre froide reçoit le relevé [4.5, -99, 6.5, 3, 5.5, -99]. Que rend MoyenneValides, en degrés ?

  • 2.

    Pendant tout ce programme, combien de fois la fonction EstValide est-elle appelée ?

  • 3.

    Dans la décomposition du contrôle qualité dessinée en arbre, combien de cadres la pile compte-t-elle au plus, programme principal compris, quand EstConforme travaille ?

  • 4.

    Un lot compte 250 sachets. Combien d'appels de fonction CompterConformes(masses) déclenche-t-il en tout, le sien compris ?

  • 5.

    Avec Montant(prix, quantite), quel est le total en euros d'une commande de trois lignes : 4 pièces à 12,5 euros, 10 à 3,2 euros et 2 à 45 euros ?

Le but n'est pas d'écrire le programme le plus court, mais celui dont chaque morceau fait une seule chose et porte un nom qui le dit.

Écrire un programme modulaire qui lit la masse de plusieurs sachets, calcule leur masse moyenne, puis affiche « Lot conforme » si elle est comprise entre 495 et 505 grammes, « Lot à reprendre » sinon.

Trois responsabilités, donc trois modules, et un programme principal qui se contente de les enchaîner.

Algorithme
pas 1 / 34
séparées par des virgules, décimales au point
Fonction LireMasses()
Lire n
Pour i de 1 à n
Écrire "Masse du sachet ", i, " : "
Lire t[i]
FinPour
Retourner t
FinFonction
Fonction Moyenne(t)
somme ← 0
Pour i de 1 à longueur(t)
somme ← somme + t[i]
FinPour
Retourner somme / longueur(t)
FinFonction
Fonction AfficherVerdict(m)
Écrire "Masse moyenne : ", m, " g"
Si m >= 495 ET m <= 505 Alors
Écrire "Lot conforme"
Sinon
Écrire "Lot à reprendre"
FinSi
FinFonction
Début
masses ← LireMasses()
AfficherVerdict(Moyenne(masses))
Fin

programme principal

aucune variable

Le Début se lit comme une phrase, sans avoir à regarder le détail des fonctions. C'est exactement ce que la modularité achète. Moyenne est celle du programme de la section « Qu'est-ce qu'un module ? », reprise telle quelle : c'est la réutilisation, sur un autre nombre de sachets.

Le test qui valide un découpage

Peut-on nommer chaque module en quelques mots, sans « et » ? Un verbe pour ce qui agit (LireMasses, AfficherVerdict), ce qu'elle rend pour ce qui calcule (Moyenne, EstConforme). Si l'on doit écrire LireEtCalculer, c'est que le module en fait deux et qu'il faut le couper.

Écrire un programme modulaire qui lit la longueur et la largeur d'une plaque à découper, et affiche son périmètre et sa surface, chaque calcul étant effectué dans une fonction distincte.
Algorithme
pas 1 / 10
séparées par des virgules, décimales au point
Fonction Perimetre(a, b)
Retourner 2 * (a + b)
FinFonction
Fonction Surface(a, b)
Retourner a * b
FinFonction
Début
Écrire "Longueur : "
Lire lo
Écrire "Largeur : "
Lire la
Écrire "Périmètre : ", Perimetre(lo, la)
Écrire "Surface : ", Surface(lo, la)
Fin

programme principal

aucune variable

Ce que Écrire a affiché

Longueur : 

Perimetre et Surface ne lisent rien et n'affichent rien : elles calculent, et c'est tout. Ce sont les fonctions les plus faciles à réutiliser, parce qu'elles ne dépendent de rien d'autre que de leurs paramètres.

La lecture enfermée dans le calcul

Mettre le Lire à l'intérieur de Surface. La fonction devient alors inutilisable ailleurs : impossible de calculer la surface de deux dimensions déjà connues sans les ressaisir. Une fonction qui calcule ne doit ni lire ni afficher.

Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?

Dans la bibliothèque d'exercices