cursus.

Cours 4 · Qualité du codeLeçon 2 sur 2

Principes de conception

5 h de lecture8 sections Version PDF

À la fin de cette leçon, vous saurez

Séparation des responsabilités ; couplage et cohésion ; éviter la duplication ; dette technique ; refactoring d'un code volontairement mal écrit.

La lisibilité (chapitre 6) agit à l'échelle d'une ligne, d'une fonction. La conception monte d'un cran : comment organiser le code en un tout qui résiste au changement ? Car un logiciel qui dure sera modifié des dizaines de fois (chapitre 1), et une bonne conception est précisément celle qui rend le changement facile plutôt que douloureux.

Ce chapitre présente les principes fondamentaux — séparation des responsabilités, couplage, cohésion, non-duplication — introduit la notion de dette technique, et met en pratique le refactoring sur un code volontairement mal écrit. Ces principes s'appuient naturellement sur les classes de la POO : si votre maquette la propose avant cette UE, vous êtes en terrain connu.

Séparer les responsabilités

Le principe le plus fondamental de la conception : chaque module (fonction, classe) ne devrait avoir qu'une seule responsabilité — une seule raison de changer.

Une fonction qui, à elle seule, lit les données, les calcule, les met en forme et les affiche a quatre raisons de changer : un nouveau format d'entrée, une nouvelle règle de calcul, un nouvel affichage, une nouvelle sortie. Chaque évolution risque de casser les autres. En séparant ces responsabilités — une fonction par tâche —, chacune devient modifiable, testable et réutilisable indépendamment.

C'est le prolongement, à l'échelle de la conception, du « une fonction fait une chose » du chapitre 6. La question à se poser devant tout module : « combien de raisons différentes aurais-je de le modifier ? » Si la réponse est « plusieurs », il faut probablement le découper.

Couplage et cohésion

Deux notions jumelles mesurent la qualité d'un découpage. On les vise en sens opposés.

Le couplage mesure à quel point les modules dépendent les uns des autres. On le veut faible : un module qu'on peut comprendre, modifier ou tester sans toucher aux autres. Un couplage fort — un module qui connaît les détails internes d'un autre — propage chaque changement en cascade : toucher l'un casse l'autre.

La cohésion mesure à quel point les éléments d'un même module vont ensemble. On la veut forte : une classe Facture qui ne s'occupe que de facturation est cohérente ; une classe Utilitaires qui rassemble des fonctions sans rapport (dates, réseau, chaînes) ne l'est pas.

Bonne conception = couplage faible entre modules, cohésion forte à l'intérieur de chacun.

L'image : des modules bien séparés (faiblement couplés), chacun rassemblant ce qui va ensemble (fortement cohésif). C'est cette structure qui permet de modifier une partie sans faire trembler tout l'édifice — exactement ce dont un logiciel qui évolue a besoin.

Quiz · vérifiez votre compréhension Sans réponse

Que vise-t-on pour le couplage et la cohésion d'un logiciel bien conçu ?

Éviter la duplication : DRY

La duplication de code est l'un des pires défauts de conception, et le plus fréquent. Le principe qui la combat s'appelle DRYDon't Repeat Yourself : chaque connaissance ne doit exister qu'à un seul endroit dans le code.

Copier-coller un bloc est tentant — c'est rapide. Mais le jour où la logique dupliquée doit changer (une remise qui passe de 10 % à 15 %), il faut la corriger partout — et l'on en oublie toujours une, créant des comportements incohérents et un bug silencieux. Le remède est l'extraction : sortir la logique commune dans une fonction (ou classe) que tout le monde appelle.

Une nuance de bon sens : DRY concerne la duplication de connaissance, pas la ressemblance superficielle. Deux bouts de code qui se ressemblent aujourd'hui par hasard mais évolueront pour des raisons différentes n'ont pas à être fusionnés — les unir créerait un couplage artificiel. On factorise ce qui est vraiment la même règle, destinée à changer ensemble. L'exercice vous fait pratiquer cette extraction.

La dette technique

Voici une métaphore qui éclaire tous les compromis de qualité : la dette technique. Quand on prend un raccourci — copier au lieu de factoriser, bâcler au lieu de concevoir, sauter les tests — on emprunte du temps : on va plus vite maintenant.

Mais comme une dette financière, ce raccourci porte des intérêts : chaque modification future du code mal conçu coûte plus cher, plus longtemps. Tant qu'on ne rembourse pas — en refactorant —, les intérêts s'accumulent, jusqu'à ce que la moindre évolution devienne un cauchemar et que le projet s'enlise.

La dette n'est pas toujours mauvaise : emprunter délibérément pour livrer à temps une démonstration peut être un bon calcul, à condition de le savoir et de rembourser ensuite. Ce qui tue les projets, c'est la dette subie et ignorée — accumulée sans en avoir conscience. Nommer la dette, c'est se donner les moyens de décider quand l'emprunt en vaut la peine, et quand il faut s'arrêter pour rembourser.

Le refactoring

Le refactoring est l'acte de rembourser la dette : améliorer la structure interne du code sans changer son comportement. C'est la définition exacte, et le mot « sans changer son comportement » est tout : refactorer, ce n'est pas réécrire au hasard ni ajouter des fonctionnalités, c'est transformer la forme en préservant le fond.

Extraire une fonction dupliquée, renommer pour clarifier, découper un module trop gros, réduire un couplage : autant de refactorings. On procède par petits pas, en vérifiant après chacun que rien n'a cassé.

D'où le lien indissociable avec le chapitre suivant : on ne refactore jamais sans tests. Les tests sont le filet qui garantit que la transformation a préservé le comportement. Sans eux, chaque refactoring est un pari — on ne sait pas si on a amélioré le code ou introduit un bug. C'est précisément ce que vérifie l'exercice de ce chapitre : les mêmes tests passent avant et après.

Quiz · vérifiez votre compréhension Sans réponse

Qu'est-ce que le refactoring, et pourquoi ne le pratique-t-on jamais sans tests ?

À vous

L'exercice met en pratique le principe le plus rentable du chapitre : DRY. Vous partez d'un code où la même logique de remise est copiée trois fois, et vous l'extrayez en une seule fonction — sans changer le comportement, comme le vérifient les tests.

Vous toucherez du doigt pourquoi la duplication est une dette (le jour où la remise change, trois corrections au lieu d'une), et pourquoi le refactoring exige des tests : ce sont eux qui prouvent que votre transformation a préservé le fond.

Exercice · JavaScript · à vous de jouer

Refactorez un code où la même logique de remise est copiée trois fois : extrayez-la en une seule fonction (DRY), sans changer le comportement — les tests le vérifient. Reliez cela au couplage/cohésion, à la dette technique, et à la raison pour laquelle on ne refactore jamais sans tests.

En attente
// Un code qui MARCHE mais qui a mal vieilli : la même logique de remise est
// COPIÉE-COLLÉE trois fois, avec une petite variation. Le jour où la remise
// change, il faut la corriger à trois endroits — et on en oublie toujours un.
function prixLivre(prix, quantite) {
  let total = prix * quantite;
  if (total > 100) { total = total * 0.9; }   // -10 % au-dessus de 100
  return total;
}
function prixDVD(prix, quantite) {
  let total = prix * quantite;
  if (total > 100) { total = total * 0.9; }   // -10 % (copié)
  return total;
}
function prixJeu(prix, quantite) {
  let total = prix * quantite;
  if (total > 100) { total = total * 0.9; }   // -10 % (encore copié)
  return total;
}

// ── À VOUS : refactorer sans changer le comportement ────────────────────────
// 1. Extraire la logique commune dans UNE fonction (DRY : Don't Repeat
//    Yourself). Le calcul de remise doit exister à UN seul endroit.
// 2. Réécrire prixLivre/prixDVD/prixJeu pour qu'ils l'utilisent (ou même
//    disparaissent au profit d'un seul « prix »).

function prix(prixUnitaire, quantite) {
  return 0; // à compléter : total, puis remise via une seule fonction
}

// ── Vérification : MÊMES résultats que les versions d'origine ───────────────
const cas = [ [10, 5], [30, 4], [50, 3], [200, 1], [20, 2] ];
let ok = true;
for (const [p, q] of cas) {
  const attendu = prixLivre(p, q);          // les 3 originales donnaient pareil
  const obtenu = prix(p, q);
  const bon = attendu === obtenu;
  ok = ok && bon;
  console.log((bon ? "  ok " : " ✗  ") + "prix(" + p + "," + q + ") = " + attendu + " / " + obtenu);
}
console.log(ok ? "Comportement préservé : c'est un refactoring valide." : "Le résultat a changé !");

Console de sortie
Le résultat s'affiche dans la console

Ce que la suite en fait

Le refactoring de ce chapitre a montré son propre présupposé : il repose sur des tests. C'est l'objet du bloc V, et le cœur pratique de l'UE.

Le chapitre 8 traite les tests — pourquoi « ça marche chez moi » ne suffit pas, et comment écrire des tests qui attrapent les régressions, y compris celles qu'un refactoring pourrait introduire. Le chapitre 9 apprend Git — travailler à plusieurs sans se marcher dessus. Ce sont les deux outils qui transforment les principes des blocs précédents en pratique quotidienne d'équipe.

À retenir

Flashcards · 1 / 4Toucher pour retourner
Fin de la leçon

Vous avez parcouru les 8 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.