Planification, tests (TDD) et refactoring
Le jeu de la planification et la vélocité, le développement piloté par les tests, et le refactoring permanent qui garde le code propre.
Avant de plonger dans SOLID, Martin pose les pratiques quotidiennes qui rendent une conception agile possible : on ne peut pas faire émerger une bonne architecture si l'on ne sait ni planifier honnêtement, ni écrire des tests qui guident le code, ni nettoyer ce code en continu. Ces trois chapitres forment un triptyque indissociable. La planification donne le rythme et le feedback ; les tests donnent la confiance et pilotent la conception ; le refactoring entretient la propreté qui permet de continuer. Les exemples du livre sont en Java ; nous les transposons en TypeScript idiomatique.
Le jeu de la planification
En méthode agile, on ne rédige pas un cahier des charges figé en début de projet. On démarre par une exploration initiale : développeurs et clients identifient ensemble les histoires utilisateur (user stories) réellement significatives — sans chercher à toutes les recenser. De nouvelles histoires continueront d'arriver tant que le projet vivra ; le flux ne se tarit qu'à la fin.
Une histoire utilisateur (user story) décrit une fonctionnalité du point de vue de l'utilisateur, en une phrase courte écrite sur une fiche. Les développeurs l'estiment ensemble, mais l'estimation est relative, pas absolue : on inscrit un nombre de points (points) sur la fiche pour représenter le coût relatif de l'histoire. On ignore peut-être combien de temps vaut un point, mais on sait qu'une histoire à huit points prendra deux fois plus longtemps qu'une histoire à quatre.
Note
L'estimation relative est libératrice : il est bien plus facile de dire « cette histoire est deux fois plus grosse que celle-là » que « cette histoire prendra 6,5 jours ». Les humains comparent mieux qu'ils ne mesurent dans l'absolu.
Découper, fusionner, sonder
Les histoires trop grosses ou trop petites sont difficiles à estimer : on tend à sous-estimer les grosses et à surestimer les petites. Toute histoire trop grosse doit être découpée en morceaux plus raisonnables ; toute histoire trop petite doit être fusionnée avec d'autres. Prenons l'histoire « L'utilisateur peut transférer de l'argent vers, depuis et entre ses comptes en toute sécurité ». C'est énorme, et l'estimation sera hasardeuse. On la découpe :
- L'utilisateur peut se connecter.
- L'utilisateur peut se déconnecter.
- L'utilisateur peut déposer de l'argent sur son compte.
- L'utilisateur peut retirer de l'argent de son compte.
- L'utilisateur peut transférer de l'argent vers un autre compte.
Quand une histoire est découpée ou fusionnée, on la réestime — on n'additionne ni ne soustrait les estimations existantes. Il n'est pas surprenant qu'une histoire estimée à cinq points se décompose en histoires totalisant dix : dix est l'estimation la plus juste. La raison d'être du découpage est précisément d'atteindre une taille où l'estimation devient fiable.
La vélocité
Les estimations relatives ne disent rien de la taille absolue des histoires. Pour connaître le temps réel, il faut un facteur que l'on appelle la vélocité (velocity). Avec une vélocité juste, on multiplie l'estimation d'une histoire par la vélocité pour obtenir une durée. Si la vélocité est de « 2 jours par point » et qu'une histoire vaut quatre points, elle devrait prendre huit jours.
// La vélocité convertit des points relatifs en durée concrète.
type Histoire = { titre: string; points: number };
function dureeEstimee(histoire: Histoire, joursParPoint: number): number {
return histoire.points * joursParPoint;
}
// vélocité = 2 jours/point, histoire à 4 points => 8 jours.
dureeEstimee({ titre: "Retirer de l'argent", points: 4 }, 2); // 8 La vélocité gagne en précision à mesure que le projet avance, car on mesure les points livrés par itération. Au démarrage, l'équipe n'en a qu'une idée vague : elle pose une première estimation par les moyens qu'elle juge bons. La précision n'est pas cruciale à ce stade — quelques jours à prototyper une ou deux histoires suffisent souvent à jauger la vélocité. Une telle session de prototypage s'appelle un sondage (spike).
Planification de release et d'itération
Une fois la vélocité connue, les clients connaissent le coût de chaque histoire. Comme ils en connaissent aussi la valeur métier et la priorité, ils choisissent l'ordre. Ce choix n'est pas pure priorité : une histoire importante mais coûteuse peut être différée au profit d'une histoire moins importante mais bien moins chère. Ce sont des décisions métier — les gens du métier décident où se trouve le meilleur rapport valeur/coût.
La planification de release fixe une date de première livraison, généralement à deux à quatre mois. Les clients choisissent les histoires qu'ils veulent dans cette release et l'ordre approximatif — sans pouvoir en choisir plus que la vélocité courante ne le permet. La sélection est grossière au début, mais elle s'ajustera à mesure que la vélocité se précise.
La planification d'itération opère à plus petite échelle. On choisit une taille d'itération — typiquement deux semaines. Les clients choisissent les histoires de la première itération, toujours dans la limite de la vélocité. L'ordre des histoires dans l'itération est, lui, une décision technique : les développeurs les implémentent dans l'ordre qui a le plus de sens, en série ou en parallèle, à leur guise.
À retenir
Une fois l'itération commencée, les clients ne peuvent plus changer ses histoires. Ils restent libres de modifier ou réordonner toutes les autres histoires du projet, mais pas celles sur lesquelles les développeurs travaillent. C'est ce contrat de stabilité courte qui rend le rythme tenable.
L'itération se termine à la date prévue, même si toutes les histoires ne sont pas finies. On totalise les estimations des histoires achevées : c'est la vélocité mesurée de l'itération. La règle est d'une simplicité désarmante : la vélocité planifiée de chaque itération est la vélocité mesurée de la précédente. Si l'équipe a livré 31 points la dernière fois, elle en planifie 31 pour la suivante. Ce bouclage de feedback maintient la planification synchronisée avec la réalité : si l'équipe gagne en compétence, la vélocité monte ; si quelqu'un quitte l'équipe, elle baisse ; si une bonne architecture émerge, elle remonte.
La planification des tâches et le point médian
Au début d'une itération, développeurs et clients se réunissent pour découper les histoires en tâches de développement. Une tâche est ce qu'un développeur peut implémenter en 4 à 16 heures. Les tâches sont listées sur un tableau, puis les développeurs s'inscrivent un par un sur celles qu'ils veulent prendre, en les estimant en points de tâche arbitraires (souvent des « heures de programmation parfaites »).
Chacun connaît le nombre de points de tâche qu'il a réalisés à l'itération précédente : c'est son budget personnel, qu'il ne dépasse pas. Fait notable : les développeurs peuvent s'inscrire sur n'importe quelle tâche, même hors de leur spécialité — un développeur d'interface peut prendre une tâche de base de données. Cela paraît inefficace, mais c'est voulu : on veut que la connaissance du projet se diffuse dans toute l'équipe.
À mi-parcours de l'itération, l'équipe tient une réunion : à ce point, la moitié des histoires (pas seulement des tâches) devrait être terminée. C'est crucial.
Piège courant
Le scénario cauchemar : arriver en fin d'itération avec 90 % des tâches faites mais aucune histoire complète. L'objectif est de livrer des histoires, pas des tâches. Une histoire à moitié faite n'a aucune valeur métier. Sur huit histoires totalisant 24 points découpées en 42 tâches, on vise au point médian 21 tâches et 12 points d'histoires intégralement bouclées.
À la fin de chaque itération, l'exécutable courant est démontré au client, qui évalue l'aspect, le ressenti et les performances, et fournit son retour sous forme de nouvelles histoires. La loi des grands nombres joue en faveur de ce processus : les erreurs d'estimation, nombreuses et petites, tendent à se compenser sur la durée, si bien que la vélocité moyenne reste un prédicteur fiable. Le projet entre alors dans un rythme prévisible et confortable : les parties prenantes voient du logiciel qui marche, pas des classeurs de diagrammes.
Les tests : le développement piloté par les tests
Écrire un test unitaire est davantage un acte de conception que de vérification. C'est aussi davantage un acte de documentation que de vérification.
Le développement piloté par les tests (Test-Driven Development, TDD) renverse l'ordre habituel : et si l'on concevait nos tests avant nos programmes ? Et si l'on refusait d'implémenter une fonction tant qu'un test n'échoue pas faute de cette fonction ? Et si l'on refusait d'ajouter ne serait-ce qu'une seule ligne de code en l'absence d'un test qui échoue à cause de son absence ? Quel effet cela aurait-il sur la conception ?
Le premier effet, le plus évident, est que chaque fonction du programme possède un test qui en vérifie le fonctionnement. Cette suite agit comme un filet de sécurité : elle nous prévient dès que nous cassons par inadvertance une fonctionnalité existante. On peut ajouter des fonctions ou changer la structure du programme sans craindre de tout briser.
L'effet le plus important est moins évident : écrire le test d'abord nous force à adopter le point de vue de l'appelant de notre code. On se préoccupe immédiatement de l'interface autant que de la fonction. On conçoit donc le logiciel pour qu'il soit commodément appelable. Mieux encore, on le conçoit pour être testable — et pour être testable, le code doit être découplé de son environnement. Ainsi, écrire les tests d'abord nous force à découpler le logiciel. Enfin, les tests constituent une documentation compilable, exécutable et qui ne ment jamais : qui veut savoir comment appeler une fonction trouve un test qui le montre.
Astuce
Écrivez le test d'abord. Rédigez-le tel que vous aimeriez qu'il se lise, comme une déclaration d'intention (programmation par intention) : énoncez ce que vous voulez de la façon la plus simple et lisible possible, puis faites confiance à cette clarté pour guider une bonne structure. Vous concevez l'interface du côté de celui qui l'utilise — c'est exactement là qu'il faut se placer.
Un exemple de conception pilotée par les tests
Martin illustre par un jeu d'aventure, « Hunt the Wumpus », où le joueur se déplace de salle en salle. L'un des tout premiers tests écrits — avant la moindre ligne du jeu — relie la salle 4 à la salle 5 par un passage est, place le joueur en 4, le fait aller à l'est, puis affirme qu'il est en 5 :
test("le joueur se déplace vers l'est", () => {
const jeu = new JeuWumpus();
jeu.connecter(4, 5, "E");
jeu.placerJoueur(4);
jeu.allerEst();
expect(jeu.salleDuJoueur()).toBe(5);
}); La leçon est subtile. Ce test n'utilise aucune classe Salle : connecter deux entiers suffit à exprimer l'intention. On pourrait croire qu'un jeu « de salles » exige une classe Salle, mais la notion de connexion se révèle plus centrale que celle de salle. Le point n'est pas que ce design soit le bon : c'est que le test a éclairé une décision de conception majeure à un stade très précoce. Écrire les tests d'abord, c'est trancher entre des options de conception avant de s'engager.
L'isolation des tests et les objets simulacres
Écrire les tests avant le code de production expose les zones qui devraient être découplées. Considérons une application de paie : la classe Paie utilise une base de données pour récupérer un Employe, lui demande de calculer sa rémunération, passe ce montant à un EmetteurDeCheque, puis enregistre le paiement et réécrit l'employé en base.
EmetteurDeCheque Paie Employe
+ emettreCheque() | + calculerPaie()
| + enregistrerPaiement()
v
BaseEmployes
+ lireEmploye()
+ ecrireEmploye() Pour tester Paie, deux problèmes : quelle base utiliser ? Et comment vérifier qu'un chèque correct a été imprimé — on ne va pas lire le papier dans l'imprimante ! La solution est le patron objet simulacre (Mock Object) : on insère des interfaces entre Paie et tous ses collaborateurs, puis on crée des simulacres qui les implémentent. Le test interroge ces simulacres pour vérifier que Paie les a correctement pilotés.
Mock PaieTest Mock
EmetteurDeCheque | Employe
^ | ^
| v |
«interface» Paie «interface»
EmetteurDeCheque Employe
| | |
| v |
| «interface» |
| BaseEmployes |
| ^ |
+------------ Mock BaseEmployes -------+ Voici l'intention du test : créer les simulacres, les injecter dans Paie, lui demander de payer, puis vérifier auprès des simulacres que les chèques ont été écrits et les paiements enregistrés correctement.
// AVANT (couplé) : Paie crée elle-même sa base et son émetteur,
// impossible à tester en isolation.
class Paie {
private base = new BaseEmployesSql(); // dépendance en dur
private emetteur = new ImprimanteCheque();
payerEmployes(): void { /* ... */ }
}
// APRÈS (découplé) : les dépendances sont des interfaces injectées.
interface BaseEmployes {
lireEmploye(id: number): Employe;
enregistrerPaiement(id: number, montant: Money): void;
}
interface EmetteurDeCheque {
emettreCheque(id: number, montant: Money): void;
}
class Paie {
constructor(
private base: BaseEmployes,
private emetteur: EmetteurDeCheque,
) {}
payerEmployes(): void {
for (const id of this.base.tousLesIds()) {
const employe = this.base.lireEmploye(id);
const montant = employe.calculerPaie();
this.emetteur.emettreCheque(id, montant);
this.base.enregistrerPaiement(id, montant);
}
}
}
// Le test pilote des simulacres et vérifie le comportement.
test("Paie paie tous les employés correctement", () => {
const base = new MockBaseEmployes();
const emetteur = new MockEmetteurDeCheque();
const paie = new Paie(base, emetteur);
paie.payerEmployes();
expect(emetteur.chequesEcritsCorrectement()).toBe(true);
expect(base.paiementsEnregistresCorrectement()).toBe(true);
}); Ce test ne vérifie pas qu'un vrai chèque a été imprimé ni qu'une vraie base a été mise à jour : il vérifie que Paie se comporte correctement en isolation, en appelant les bonnes fonctions avec les bonnes données.
À retenir
Le découplage de Paie est ici un effet secondaire heureux (serendipitous decoupling) du besoin de tester. Le besoin d'isoler le module sous test force à découpler de façons qui profitent à toute la structure du programme. Écrire les tests avant le code améliore la conception — et ce sont les tests unitaires qui fournissent l'essentiel de l'élan et de la direction du découplage.
Les tests d'acceptation
Les tests unitaires sont nécessaires mais insuffisants : ce sont des tests boîte blanche (white-box), qui connaissent et vérifient les mécanismes internes du système. Ils ne disent pas si le système fonctionne dans son ensemble. Les tests d'acceptation (acceptance tests) sont des tests boîte noire (black-box) qui vérifient que les exigences du client sont satisfaites.
Ils sont écrits par des gens qui ignorent les mécanismes internes : le client lui-même, ou des techniciens à son service (la QA, par exemple). Ce sont des programmes exécutables, mais rédigés dans un langage de script conçu pour les clients. Ils constituent la documentation ultime d'une fonctionnalité : une fois que le client a écrit les tests qui vérifient qu'une fonctionnalité est correcte, les programmeurs peuvent les lire pour la comprendre vraiment.
Comme pour les tests unitaires, l'acte d'écrire les tests d'acceptation d'abord a un effet profond — mais cette fois sur l'architecture. Pour rendre le système testable de bout en bout, il faut le découpler au plus haut niveau : l'interface utilisateur doit être séparée des règles métier, de sorte que les tests accèdent aux règles sans passer par l'UI. Faire ces tests manuellement aux premières itérations est une fausse économie : on se prive de la pression de découplage qu'exerce le besoin de les automatiser.
Reprenons la paie. À la première itération, on doit pouvoir ajouter et supprimer des employés, et générer des fiches de paie (salariés uniquement). Aucun code écrit, aucune conception investie : c'est le meilleur moment pour penser aux tests d'acceptation, en programmation par intention. On écrit le test comme on aimerait le voir, dans de simples fichiers texte versionnés :
AddEmp 1429 "Robert Martin" 3215.88
Payday
Verify Paycheck EmpId 1429 GrossPay 3215.88 On ajoute l'employé 1429, on déclenche la paie, puis on vérifie que sa fiche affiche un brut de 3215,88. Ce script est trivial à écrire pour un client — mais réfléchissez à ce qu'il implique. Les deux premières lignes sont des transactions de l'application de paie ; la ligne Verify est une directive propre au test. Le framework devra donc parser le fichier, séparer les transactions des directives, envoyer les transactions à l'application, puis l'interroger pour vérifier.
Cela met aussitôt l'architecture sous tension : l'application doit accepter des entrées venant à la fois des utilisateurs et du framework de test. On veut réunir ces deux sources tôt, par une forme commune — typiquement du XML — que l'UI comme le framework savent produire :
// La transaction comme structure commune aux deux sources.
type Transaction = {
type: "AddEmp" | "Payday";
payload: Record<string, string | number>;
};
// Aussi bien l'UI que le framework de test produisent ce format ;
// l'application de paie ne connaît qu'une seule entrée.
function depuisXml(xml: string): Transaction { /* ... */ } La sortie suit le même chemin : l'application produit ses fiches de paie en XML, que le framework intercepte et interroge pour exécuter la directive Verify. Au début, un simple fichier suffit pour l'entrée comme la sortie ; on migrera vers une API ou une socket bien plus tard, le changement étant trivial.
Note
Là encore, c'est une architecture heureuse (serendipitous architecture). Le seul fait de considérer les tests en premier nous a menés rapidement à l'entrée/sortie XML, qui découple les sources de transactions et le mécanisme d'impression du cœur de l'application. De bonnes décisions architecturales, induites par le besoin de testabilité.
Plus une suite de tests est simple à lancer, plus elle sera lancée souvent ; plus elle est lancée, plus vite toute déviation est détectée. Si l'on peut exécuter tous les tests plusieurs fois par jour, le système n'est jamais cassé plus de quelques minutes : une fois qu'il atteint un certain niveau, il ne régresse jamais en dessous.
Le refactoring
Martin Fowler définit le refactoring (refactoring) comme « le processus consistant à modifier un système logiciel de telle façon qu'il n'altère pas le comportement externe du code, tout en améliorant sa structure interne ». Mais pourquoi améliorer du code qui marche — « si ce n'est pas cassé, ne le répare pas » ?
Parce que tout module a trois fonctions. D'abord, ce qu'il fait à l'exécution : sa raison d'être. Ensuite, permettre le changement : presque tous les modules évolueront, et c'est la responsabilité des développeurs de rendre ces changements aussi simples que possible. Un module difficile à changer est cassé, même s'il fonctionne. Enfin, communiquer avec ses lecteurs : un module incompréhensible est lui aussi cassé. Tout cela exige plus que des principes et des patterns — il faut de l'attention, de la discipline, et la passion de créer quelque chose de beau.
Générer des nombres premiers : un avant/après
L'exemple canonique est un générateur de nombres premiers par le crible d'Ératosthène. La version 1 est une grosse fonction unique, truffée de variables à une lettre et de commentaires censés nous aider à lire :
// AVANT : une seule fonction qui fait tout, variables cryptiques.
function generatePrimes(maxValue: number): number[] {
if (maxValue >= 2) {
const s = maxValue + 1; // taille du tableau
const f: boolean[] = new Array(s);
let i: number;
for (i = 0; i < s; i++) f[i] = true;
f[0] = f[1] = false; // ni premiers ni multiples
let j: number;
for (i = 2; i < Math.sqrt(s) + 1; i++) {
if (f[i]) {
for (j = 2 * i; j < s; j += i) f[j] = false;
}
}
let count = 0;
for (i = 0; i < s; i++) if (f[i]) count++;
const primes: number[] = new Array(count);
for (i = 0, j = 0; i < s; i++) if (f[i]) primes[j++] = i;
return primes;
} else {
return [];
}
} Avant de toucher à quoi que ce soit, il faut un test — l'approche statistique : aucun premier jusqu'à 0, un seul (2) jusqu'à 2, deux (2 et 3) jusqu'à 3, et 25 premiers jusqu'à 100, le dernier étant 97.
test("génère correctement les nombres premiers", () => {
expect(generatePrimes(0)).toEqual([]);
expect(generatePrimes(2)).toEqual([2]);
expect(generatePrimes(3)).toEqual([2, 3]);
const cent = generatePrimes(100);
expect(cent).toHaveLength(25);
expect(cent[24]).toBe(97);
}); Le refactoring procède alors par petits pas, en relançant les tests après chaque changement. La fonction veut visiblement être trois fonctions : initialiser le crible, l'exécuter, puis charger les résultats. On extrait ces trois étapes. On remplace les usages de s par f.length. On renomme les fonctions pour les rendre expressives. On constate que le tableau de booléens représente mal son intention : f devient crossedOut (« rayé »), et l'on en inverse le sens pour éviter les doubles négations. On extrait des fonctions au nom parlant pour chaque sous-étape. Les tests passent toujours.
// APRÈS : généraliste qui se lit comme un récit, du général au détail.
class GenerateurDePremiers {
private crossedOut: boolean[] = [];
private result: number[] = [];
generer(maxValue: number): number[] {
if (maxValue < 2) return [];
this.decocherEntiersJusqua(maxValue);
this.rayerLesMultiples();
this.collecterEntiersNonRayes();
return this.result;
}
private decocherEntiersJusqua(maxValue: number): void {
this.crossedOut = new Array(maxValue + 1).fill(false);
}
private rayerLesMultiples(): void {
const limite = this.determinerLimiteIteration();
for (let i = 2; i <= limite; i++) {
if (this.nonRaye(i)) this.rayerLesMultiplesDe(i);
}
}
private determinerLimiteIteration(): number {
// Tout multiple a un facteur premier <= racine de la taille
// du tableau ; inutile de rayer au-delà de cette racine.
return Math.floor(Math.sqrt(this.crossedOut.length));
}
private rayerLesMultiplesDe(i: number): void {
for (let m = 2 * i; m < this.crossedOut.length; m += i) {
this.crossedOut[m] = true;
}
}
private nonRaye(i: number): boolean {
return this.crossedOut[i] === false;
}
private collecterEntiersNonRayes(): void {
this.result = [];
for (let i = 2; i < this.crossedOut.length; i++) {
if (this.nonRaye(i)) this.result.push(i);
}
}
} La relecture finale est une étape à part entière : on lit le programme du début à la fin, comme une démonstration de géométrie, pour vérifier qu'il tient ensemble. C'est ainsi que Martin débusque ses propres erreurs — un commentaire faux sur le « facteur premier maximal », un +1 superstitieux de trop. Chaque correction est validée par les tests ; quand un changement le rend nerveux (la suppression du +1), il ajoute un test exhaustif vérifiant qu'aucun premier listé entre 2 et 500 n'a de diviseur, et ses craintes sont apaisées.
Astuce
Ces renommages incessants ne sont pas frivoles : avec un outil de refactoring, ils ne coûtent presque rien, et les tests écartent tout risque de casser quelque chose à son insu. Le cycle est rouge–vert–refactor : écrire un test qui échoue (rouge), le faire passer au plus simple (vert), puis nettoyer (refactor) — sans relâche.
Nettoyer la cuisine
Le refactoring se pratique sans pitié, en continu. On pourrait craindre que d'extraire des fonctions appelées une seule fois nuise aux performances : la lisibilité accrue vaut bien quelques nanosecondes dans l'immense majorité des cas. Martin file une métaphore : le refactoring, c'est nettoyer la cuisine après le dîner. La première fois qu'on saute le nettoyage, on a fini plus vite — mais la vaisselle sale rend le repas suivant plus pénible à préparer, ce qui pousse à sauter le nettoyage à nouveau. Le désordre s'accumule jusqu'à ce que retrouver un ustensile propre prenne un temps fou. Sauter le nettoyage ne fait pas gagner de temps.
Attention
Tous les principes et patterns du livre ne valent rien si le code qui les emploie est un dépotoir. Avant d'investir dans les principes et les patterns, investissez dans du code propre. Le nettoyage quotidien est l'habilitateur le plus important de votre capacité à étendre et modifier le système avec un minimum d'effort.
À retenir
- Le jeu de la planification estime les histoires en points relatifs ; la vélocité (points livrés par itération) les convertit en durées et règle la planification : la vélocité planifiée d'une itération est la vélocité mesurée de la précédente.
- Découpez les grosses histoires, fusionnez les petites, réestimez après coup, et utilisez des sondages (spikes) pour jauger une vélocité initiale. La loi des grands nombres lisse les erreurs.
- L'objectif d'une itération est de livrer des histoires complètes, jamais 90 % de tâches sans une seule histoire bouclée ; à mi-parcours, la moitié des points doit être finie.
- Le TDD consiste à écrire le test d'abord : il documente, sécurise, et surtout pilote la conception en forçant le code à être appelable, testable, donc découplé (injection de dépendances, objets simulacres).
- Les tests d'acceptation (boîte noire, écrits par/pour le client) exercent la même pression bénéfique au niveau architectural : ils séparent l'UI des règles métier et font émerger de bonnes décisions (entrée/sortie XML).
- Le refactoring améliore la structure sans changer le comportement, par petits pas validés par les tests (rouge–vert–refactor), jusqu'à une relecture finale d'ensemble.
- Un module difficile à changer ou à lire est cassé, même s'il fonctionne. Nettoyez le code chaque jour : c'est le préalable indispensable à tous les principes qui suivent.