Read in English · Leer en español · Auf Deutsch lesen
Section 3.8 sur 3.8 · Intermédiaire · 25 min de lecture
Simplicité, réversibilité et mesure de la structure
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Cette dernière leçon du chapitre porte sur la façon de garder les systèmes aussi simples que le problème le permet, et de mesurer la structure sans se leurrer.
Certaines choses sont difficiles parce que la tâche est difficile. Une déclaration d’impôts est compliquée parce que le droit fiscal l’est. D’autres sont difficiles à cause de la façon dont nous avons choisi de les faire, comme un système de classement à sept dossiers là où deux suffiraient. Les bons concepteurs acceptent le premier type et combattent le second. Ils construisent ce qui est nécessaire maintenant, consignent chaque fait à un seul endroit, évitent les choix qu’ils ne peuvent pas défaire et vérifient leur travail avec quelques mesures honnêtes.
Fred Brooks, dans son essai No Silver Bullet, a séparé ces deux types. La complexité essentielle appartient au problème : les vraies règles de la TVA, les vraies façons dont une livraison peut échouer. Aucune conception ne peut l’éliminer sans changer ce que fait le système. La complexité accidentelle vient de la manière dont nous construisons la solution : les outils, les contournements, et une structure plus compliquée que ne l’exige le problème. Les architectes placent la complexité essentielle derrière une frontière claire, comme un service fiscal, et éliminent la complexité accidentelle.
Trois maximes aident. La première, appelée KISS, dit : restez simple. Une conception est bonne si les personnes qui doivent la réparer, peut-être à trois heures du matin, peuvent la comprendre. You aren’t gonna need it, « vous n’en aurez pas besoin », ou YAGNI, met en garde contre le fait de construire pour des besoins seulement présumés. Vous payez pour la construire, pour la maintenir à chaque modification ultérieure, et pour la réparer si vous vous êtes trompé. Mais YAGNI s’applique aux fonctionnalités, pas aux pratiques qui rendent le changement peu coûteux, comme les tests et la refactorisation. Et certaines décisions, comme un modèle de données ou une API publique, sont des portes à sens unique. Y réfléchir à l’avance fait partie du travail de l’architecte. Don’t repeat yourself, « ne vous répétez pas », ou DRY, porte sur la connaissance, pas sur le texte. Chaque règle, comme le taux de TVA, a besoin d’un seul emplacement qui fait autorité. Mais fusionner deux fonctions simplement parce qu’elles se ressemblent couple des concepts qui changeront pour des raisons différentes. Enfin, la flexibilité ressemble à une option financière. Payez-la quand le changement est probable, ou très coûteux sans elle, et que l’option est bon marché. Pour le reste, comptez sur une bonne modularité.
Passons maintenant à la mesure. La plus ancienne métrique de code largement utilisée est la complexité cyclomatique, proposée par Thomas McCabe en 1976. Elle traite une fonction comme un graphe : les blocs de code sont des nœuds, et les sauts possibles entre eux sont des arêtes. Celle-ci calcule des frais de livraison. Elle comporte trois décisions : le total du panier atteint-il vingt-cinq livres, la distance dépasse-t-elle cinq, et s’agit-il d’une commande prioritaire ? Il y a huit nœuds et dix arêtes. La complexité cyclomatique vaut les arêtes moins les nœuds plus deux : dix moins huit plus deux, soit quatre. Cela correspond à une vérification rapide : trois décisions, plus un. Quatre signifie quatre chemins indépendants, donc au moins quatre cas de test pour essayer chacun d’eux. McCabe suggérait une dizaine comme limite supérieure raisonnable.
Un an plus tard, la fonction s’est enrichie d’une boucle qui ajoute un supplément pour les articles encombrants, et la vérification de priorité demande aussi si un livreur est disponible. Cela fait les trois décisions d’origine, plus la boucle, la vérification des articles encombrants et le « and » supplémentaire, si votre outil le compte : six. La complexité est donc de sept. C’est sous la limite de McCabe, mais elle a presque doublé. Tester chaque chemin exige désormais au moins sept cas, et non plus quatre. Le code mêle désormais trois préoccupations de tarification, modifiées par des personnes différentes. Extrayez donc chaque supplément dans sa propre petite fonction. La complexité totale est similaire, mais chaque élément peut être testé isolément.
Les architectes mesurent aussi les relations entre modules. Les métriques de couplage et de stabilité de la section trois point quatre révèlent le sens des dépendances et les cycles. Les métriques de cohésion estiment si une classe porte une seule responsabilité ou plusieurs. Une classe client dont les méthodes se répartissent en trois groupes sans aucun champ commun est en réalité trois classes qui partagent un fichier. Le contrôle de version révèle le couplage de changement. Si deux fichiers changent ensemble dans soixante-dix pour cent des commits, ils sont couplés, que l’un importe l’autre ou non. Et les hotspots combinent la fréquence des changements et la complexité, pour repérer le code à la fois difficile à manipuler et souvent modifié.
Mais gare à la loi de Goodhart : lorsqu’une mesure devient un objectif, elle cesse d’être une bonne mesure. Une limite stricte de complexité à dix produit des fonctions découpées à des endroits arbitraires, et une logique plus difficile à suivre. Les professionnels surveillent donc les tendances plutôt que les seuils, utilisent les métriques pour lancer des discussions plutôt que pour noter les personnes, et combinent des métriques qui tirent dans des directions différentes. Quand une règle structurelle est claire, comme l’absence de cycles de dépendances, ils en font une fonction d’aptitude : une vérification automatisée qui fait échouer le build et impose un choix conscient, corriger le code ou modifier la règle.
La section complète se termine par une liste de contrôle pour revoir la conception de n’importe quel module. Ainsi s’achève le chapitre 3. Quand vous serez prêt, essayez le test facultatif du chapitre, puis passez au chapitre 4, consacré à la documentation et à la communication de l’architecture. Bravo.
Ce qu’il faut savoir
- Fred Brooks distinguait la complexité essentielle, qui appartient au problème, de la complexité accidentelle, qui vient de la manière dont la solution est construite ; les architectes placent la première et éliminent la seconde.
- YAGNI met en garde contre le fait de construire pour des besoins présumés, en raison des coûts de construction, de retard, de maintien et de réparation, mais il n’excuse pas de négliger les tests, la refactorisation ou les décisions qui sont des portes à sens unique.
- DRY, tel que le définissent Hunt et Thomas, veut que chaque élément de connaissance ait une seule représentation faisant autorité ; fusionner du code qui se ressemble seulement crée un couplage néfaste.
- La flexibilité est une option qui a un prix : payez-la quand le changement est probable ou très coûteux sans elle, et que l’option est bon marché, et comptez sur une bonne modularité pour le reste.
- La complexité cyclomatique, M = E − N + 2P, compte les chemins indépendants à travers une fonction et vaut, pour du code structuré, le nombre de décisions plus un ; McCabe suggérait une limite d’environ 10.
- Le LCOM, les métriques de couplage et de stabilité, le couplage de changement et les hotspots révèlent chacun des problèmes structurels différents, et tous deviennent trompeurs lorsqu’on en fait des objectifs (loi de Goodhart).
- Les fonctions d’aptitude transforment des décisions structurelles, comme des dépendances interdites, en vérifications automatisées qui font échouer le build lorsqu’elles ne sont pas respectées.
Deux types de complexité
Dans « No Silver Bullet: Essence and Accidents of Software Engineering » (présenté au congrès mondial de l’informatique de l’IFIP en 1986 et publié dans IEEE Computer en 1987), Fred Brooks a séparé les difficultés de la construction du logiciel en deux types. La complexité essentielle appartient au problème : les vraies règles de la TVA, la vraie diversité des produits d’assurance, les vraies façons dont une livraison peut échouer. Aucune conception ne peut l’éliminer sans changer ce que fait le système. La complexité accidentelle vient de la manière dont nous construisons la solution : langages, outils, frameworks, mécanismes de déploiement, contournements, et une structure plus compliquée que ne l’exige le problème. Brooks soutenait que les langages de haut niveau et d’autres progrès avaient déjà éliminé une grande partie de la difficulté accidentelle, si bien qu’aucune nouvelle technique isolée n’apporterait un gain d’un facteur dix en une décennie.
Pour les architectes, cette distinction est un outil quotidien. La complexité essentielle doit être placée : mettez-la là où les personnes qui la comprennent peuvent y travailler, derrière une frontière qui l’empêche de se répandre partout ailleurs (un module de tarification, un service fiscal). La complexité accidentelle doit être éliminée ou évitée : chaque service, file d’attente, cache, couche d’abstraction et option de configuration supplémentaire en ajoute, et chacun doit mériter sa place en résolvant un vrai problème. L’essai de Ben Moseley et Peter Marks « Out of the Tar Pit » (2006) a poussé l’idée plus loin en désignant l’état mutable et le flux de contrôle comme des sources majeures de complexité accidentelle, ce qui explique en partie la popularité des conceptions fonctionnelles et pilotées par les événements qui limitent l’état partagé.
Le reste de cette section présente les principes qui contiennent la complexité accidentelle (KISS, YAGNI, DRY bien compris, réversibilité) et les mesures qui vous aident à voir la structure de façon objective, avec leurs limites.
KISS et YAGNI
KISS, généralement développé en « keep it simple, stupid » (« reste simple, idiot »), est une maxime de conception attestée dans l’US Navy en 1960 et associée à Kelly Johnson, l’ingénieur en chef des Skunk Works de Lockheed, qui aurait exigé que les avions à réaction puissent être réparés sur le terrain par un mécanicien moyen muni d’une poignée d’outils. La lecture logicielle est la même : une conception est bonne si les personnes qui doivent l’exploiter et la réparer, souvent à 3 h du matin et sous pression, peuvent la comprendre. Simple ne veut pas dire facile ni naïf. Cela signifie peu de concepts, peu de pièces mobiles et peu de cas particuliers pour le problème à résoudre.
YAGNI (« you aren’t gonna need it », « vous n’en aurez pas besoin ») vient de l’Extreme Programming, à la fin des années 1990 : ne construisez pas de capacités pour des besoins seulement présumés. L’analyse de Martin Fowler est utile, car elle nomme les coûts liés au non-respect de ce principe. Il y a le coût de construction de la fonctionnalité présumée ; le coût du retard, parce qu’une chose utile n’a pas été construite à la place ; le coût de maintien, parce que chaque modification ultérieure doit composer avec le code supplémentaire ; et, si l’hypothèse était fausse, le coût de réparation. Même quand l’hypothèse s’avère juste, la fonctionnalité a été construite avec moins de connaissances que vous n’en auriez eu plus tard.
Deux nuances empêchent YAGNI de devenir une excuse. D’abord, il s’applique aux fonctionnalités et à la flexibilité spéculative, pas aux pratiques qui rendent le changement peu coûteux. Les tests automatisés, la refactorisation, la livraison continue et des frontières de modules claires sont ce qui permet de reporter des fonctionnalités en toute sécurité ; les abandonner au nom de YAGNI transforme le report en dette. Ensuite, certaines décisions sont des portes à sens unique (section 1.6). Choisir un modèle de données, la forme d’une API publique, un modèle de multilocation ou une frontière de sécurité n’est pas une fonctionnalité que l’on peut ajouter plus tard au même coût. Pour ces décisions, anticiper n’est pas de la spéculation : c’est le travail de l’architecte.
DRY porte sur la connaissance, pas sur le texte
Andy Hunt et Dave Thomas ont introduit DRY dans The Pragmatic Programmer (1999 ; édition du 20e anniversaire en 2019) avec cette définition : Chaque élément de connaissance doit avoir une représentation unique, non ambiguë et faisant autorité au sein d’un système.
Le mot clé est connaissance. Si le taux de TVA, la règle de validité d’un code postal ou la signification d’un statut de commande est écrit à plusieurs endroits, le modifier exige de retrouver chaque copie, et les copies divergeront. L’édition de 2019 souligne que DRY porte sur la duplication de la connaissance et de l’intention, pas sur des lignes de code qui se trouvent se ressembler.
Le mauvais usage courant va dans l’autre sens. Deux fonctions qui se ressemblent sont fusionnées parce qu’elles se ressemblent, alors qu’elles représentent des connaissances différentes qui changeront pour des raisons différentes : la règle des frais de livraison et la règle de commission des restaurants multiplient toutes deux un total par un pourcentage aujourd’hui, mais elles relèvent d’acteurs différents (la finance et les partenariats) et divergeront. Les fusionner couple deux concepts que le domaine sépare : les deux dépendent désormais d’une seule fonction partagée, si bien qu’une modification demandée pour l’une change l’autre en silence. Dans les termes de la section 3.6, cela crée une connascence là où le problème n’en a aucune (partager une fonction n’est le bon remède que si les deux côtés doivent réellement calculer la même chose) ; c’est aussi une violation de la responsabilité unique (section 3.5). Le conseil bien connu de Sandi Metz est de préférer la duplication à la mauvaise abstraction, car une mauvaise abstraction partagée accumule paramètres et conditions jusqu’à ce que personne n’ose plus la modifier. Beaucoup d’équipes appliquent la règle de trois : tolérer une deuxième copie, et extraire une abstraction quand la troisième apparaît et que la connaissance commune est claire.
À l’échelle de l’architecture, le compromis devient explicite. Une bibliothèque partagée que chaque service doit utiliser supprime le code dupliqué, mais couple les cycles de mise en production des services. Beaucoup d’organisations à microservices acceptent délibérément du code dupliqué (de petits utilitaires copiés dans chaque service) tout en exigeant un responsable unique pour la connaissance dupliquée (la fiche client de référence, les règles fiscales). Posez la question pour toute duplication : si cette règle change, toutes les copies doivent-elles changer ensemble ? Si oui, c’est de la connaissance dupliquée, qui doit avoir un seul responsable. Si non, c’est une ressemblance fortuite, qui peut rester.
La réversibilité et le coût des options
La simplicité a une dimension temporelle. Une conception simple aujourd’hui mais impossible à modifier n’est pas bon marché ; une conception pleine d’une flexibilité que personne n’utilise ne l’est pas non plus. Le concept utile est la réversibilité : combien coûterait-il d’annuler ou de modifier cette décision plus tard ? La section 1.6 a présenté les portes à sens unique et à double sens. Ici, l’idée est que la flexibilité se comporte comme une option financière : elle a un prix maintenant (code supplémentaire, abstraction supplémentaire, coût d’exploitation supplémentaire) et une valeur qui dépend de la probabilité et du coût du changement contre lequel elle protège.
Cela donne une règle empirique qui concilie YAGNI et anticipation. Ne payez une option que si le changement est raisonnablement probable, ou s’il serait très coûteux sans l’option, et si l’option elle-même est bon marché. Envelopper un prestataire de paiement derrière une interface qui vous appartient coûte une journée et protège contre un changement à la fois plausible et coûteux : c’est une bonne option. Construire une couche d’abstraction multicloud pour une start-up qui n’a qu’un client est une option coûteuse contre un changement improbable. Souvent, l’option la moins chère est simplement une bonne modularité : des frontières bien placées gardent de nombreux changements futurs locaux sans que personne ait à les prédire, ce qui est l’argument originel de Parnas en faveur du masquage de l’information (section 3.1).
Mesurer la structure : la complexité cyclomatique
Le jugement sur la simplicité s’améliore quand il s’appuie sur la mesure. La plus ancienne métrique de code largement utilisée est la complexité cyclomatique, introduite par Thomas McCabe dans « A Complexity Measure » (IEEE Transactions on Software Engineering, 1976). Elle traite une fonction comme un graphe de flot de contrôle, avec des nœuds pour les blocs de code et des arêtes pour les transferts de contrôle possibles, et compte les chemins linéairement indépendants qui le traversent : M = E − N + 2P, où E est le nombre d’arêtes, N le nombre de nœuds et P le nombre de composantes connexes (1 pour une seule fonction). Pour du code structuré avec un seul point d’entrée, cela équivaut au nombre de points de décision plus un.
McCabe suggérait 10 comme limite supérieure raisonnable pour un module. Le rapport ultérieur du NIST sur le test structuré, d’Arthur Watson et McCabe (1996), note que des limites allant jusqu’à 15 ont été utilisées avec succès par des équipes expérimentées, et recommande une explication écrite chaque fois qu’un module dépasse la limite choisie. Des outils d’analyse statique comme radon ou le plugin mccabe pour flake8 en Python, la règle complexity d’ESLint en JavaScript et SonarQube la calculent automatiquement. Les outils diffèrent sur des détails, par exemple selon que chaque and ou or d’une condition compte ou non comme une décision supplémentaire : comparez donc des chiffres issus du même outil.
def delivery_fee(total: int, distance_km: float, priority: bool) -> int:
if total >= 2500: # decision 1: free delivery over 25 pounds
return 0
fee = 299
if distance_km > 5: # decision 2
fee += 100
if priority: # decision 3
fee += 200
return feeLe graphe de flot de contrôle et le décompte
Exemple commenté
Exemple commenté : quand la fonction grossit
Un an plus tard, delivery_fee s’est enrichie d’une boucle sur le panier et d’une condition composée. Calculez sa complexité et décidez quoi faire.
| Nouveau code | for item in items: avec if item.bulky: fee += 50 à l’intérieur, et if priority and rider_available: fee += 200 à la place de if priority. |
|---|---|
| Décisions | Les 3 d’origine, plus la boucle for (1), plus le if item.bulky (1), plus le and (1, si l’outil compte les opérateurs booléens) : 6. |
| Complexité cyclomatique | 6 + 1 = 7. En dessous de la limite de 10 de McCabe, mais elle a presque doublé. |
| Conséquence pour les tests | Le test des chemins de base exige au moins 7 cas de test pour parcourir une fois chaque chemin indépendant ; l’ancienne suite de 4 ne couvre plus tous les chemins. |
| Lecture de la conception | La fonction mêle désormais trois préoccupations de tarification : le seuil de montant du panier, les suppléments liés à la distance et aux articles, et la priorité. Elles évoluent à la demande d’acteurs différents (marketing, opérations, produit). |
| Refactorisation (refactoring) | Extrayez distance_surcharge, bulky_surcharge et priority_surcharge, chacune d’une complexité de 2 ou 3, et laissez delivery_fee en faire la somme. La complexité totale est similaire, mais chaque élément est petit, nommé et testable isolément. |
La refactorisation réduit rarement la complexité totale ; elle la redistribue en éléments plus faciles à comprendre, à tester et à modifier indépendamment. La complexité essentielle des règles de tarification demeure.
La métrique a signalé une tendance qui méritait une discussion ; la décision de découper a été prise en lisant le code et en se demandant qui le modifie.
Mesurer la structure au-delà des fonctions
La complexité cyclomatique ne voit qu’une fonction à la fois. Les architectes ont besoin de mesures des relations entre modules.
Métriques de couplage et de stabilité. La section 3.4 (gérer les dépendances) a présenté le couplage afférent et efférent, l’instabilité I = Ce / (Ca + Ce), le degré d’abstraction et la distance à la séquence principale. Ces métriques se calculent à peu de frais à partir des graphes d’imports ; elles révèlent bien les composants dont beaucoup d’autres dépendent mais qui dépendent eux-mêmes de code volatil, et détectent les cycles de dépendances.
Métriques de cohésion. La famille LCOM (manque de cohésion des méthodes) estime si une classe porte une seule responsabilité ou plusieurs. La version originale de Shyam Chidamber et Chris Kemerer (1994) compare des paires de méthodes selon qu’elles utilisent ou non des champs communs. Une variante ultérieure très répandue, LCOM4, de Martin Hitz et Behzad Montazeri, construit un graphe dans lequel les méthodes sont reliées si elles partagent un champ ou si l’une appelle l’autre, puis compte les groupes connexes. Une classe avec LCOM4 = 1 est cohérente ; une classe avec LCOM4 = 3 est en réalité trois classes qui partagent un fichier. Par exemple, une classe Customer dont update_address et format_address utilisent les champs d’adresse, dont add_points et points_balance utilisent les champs de fidélité, et dont send_newsletter n’utilise que le champ e-mail a un LCOM4 = 3, ce qui suggère des responsabilités d’adresse, de fidélité et de marketing.
Métriques fondées sur l’historique. Le contrôle de version enregistre la façon dont le système change réellement, ce que l’analyse statique ne peut pas voir. Le couplage de changement repère les fichiers ou les services qui sont régulièrement commités ensemble : si pricing.py et invoice_template.html changent ensemble dans 70 % des commits, ils sont couplés, que l’un importe l’autre ou non, et ce couplage peut franchir une frontière de service censée les séparer. Les hotspots (points chauds) combinent la fréquence des changements et la complexité pour repérer le code à la fois difficile à manipuler et fréquemment modifié ; Your Code as a Crime Scene d’Adam Tornhill (2015 ; deuxième édition en 2024) a popularisé cette technique. La section 1.7, consacrée à la dette technique, a montré comment les hotspots orientent le remboursement vers la dette qui coûte le plus d’intérêts.
Les métriques structurelles en un coup d’œil
| Métrique | Portée | Ce qu’elle révèle | Principal angle mort |
|---|---|---|---|
| Complexité cyclomatique | Fonction | Une logique de branchement difficile à tester et à raisonner | Ne dit rien du nommage, des données ni des relations entre fonctions |
| Ca, Ce, instabilité, distance | Paquet ou composant | Sens des dépendances, stabilité, cycles | Compte les dépendances, pas leur force ni leur signification |
| LCOM (par exemple LCOM4) | Classe | Des classes portant plusieurs responsabilités sans rapport entre elles | Sensible aux accesseurs et aux fonctionnalités du langage ; bruitée sur les petites classes |
| Couplage de changement | Fichier, module ou service | Un couplage caché qui n’apparaît que dans la façon dont le système change | Exige un historique suffisant ; les commits massifs créent du bruit |
| Hotspots | Fichier ou module | Là où une amélioration est rentabilisée le plus vite | Établit des priorités ; ne dit pas ce qui ne va pas |
| Connascence (évaluée en revue) | Toutes | La forme et la force de chaque dépendance | Largement manuelle ; peu d’outils mesurent les formes dynamiques |
La loi de Goodhart et les limites des métriques
Chacune des métriques ci-dessus est un indicateur indirect de quelque chose qui nous importe mais que nous ne pouvons pas mesurer directement : la facilité à comprendre et à modifier le système. La loi de Goodhart, nommée d’après l’économiste Charles Goodhart (1975) et surtout connue dans la formulation de l’anthropologue Marilyn Strathern en 1997, avertit que lorsqu’une mesure devient un objectif, elle cesse d’être une bonne mesure. Les gens optimisent le chiffre, pas la propriété.
Les équipes logicielles le constatent sans cesse. Une limite stricte de complexité cyclomatique à 10 produit des fonctions découpées à des endroits arbitraires, qui passent chacune la vérification tandis que la logique devient plus difficile à suivre. Un objectif de couverture de 80 % produit des tests qui exécutent du code sans rien vérifier. Une règle interdisant à toute classe de dépasser 200 lignes produit des classes scindées en OrderHelper et OrderHelper2. Un objectif « zéro cycle » peut être atteint en déplaçant du code dans un paquet fourre-tout common dont tout dépend.
Les professionnels utilisent les métriques de trois manières qui résistent à la manipulation. Ils regardent les tendances plutôt que des seuils absolus (la complexité du module de paiements a doublé en six mois : pourquoi ?). Ils utilisent les métriques pour lancer des discussions et prioriser les revues, pas pour noter les personnes. Et ils combinent des métriques qui tirent dans des directions différentes, comme la complexité avec la fréquence des changements, ou le couplage avec la responsabilité des équipes, afin qu’aucun chiffre isolé ne puisse être optimisé au détriment des autres.
Fonctions d’aptitude : rendre la structure vérifiable
Certaines règles structurelles sont assez claires pour être automatisées, et elles cessent alors de dépendre de la vigilance. Building Evolutionary Architectures de Neal Ford, Rebecca Parsons et Patrick Kua (2017 ; deuxième édition en 2022 avec Pramod Sadalage) appelle ces vérifications des fonctions d’aptitude (fitness functions) : des tests objectifs et automatisés d’une caractéristique architecturale, exécutés dans le pipeline de build ou sur le système en fonctionnement. Exemples pour la structure : aucun cycle de dépendances entre les paquets de premier niveau ; le paquet du domaine n’importe rien des paquets web ou base de données ; aucune fonction au-dessus d’une complexité de 15 sans dérogation explicite ; aucun service ne lit les tables de base de données d’un autre service.
Des outils comme ArchUnit (Java), import-linter (Python) et dependency-cruiser (JavaScript et TypeScript) expriment ces règles sous forme de code ; la section 3.4 en a donné des exemples. La différence avec une métrique de tableau de bord est qu’une fonction d’aptitude encode une décision que l’équipe a déjà prise : l’enfreindre fait échouer le build et impose un choix conscient, corriger le code ou modifier la règle en consignant pourquoi. Le traitement complet, y compris les fonctions d’aptitude pour les performances, la sécurité et d’autres qualités, se trouve dans Fonctions d’aptitude et architecture évolutive.
[importlinter]
root_package = shop
[importlinter:contract:domain-is-pure]
name = Domain must not depend on infrastructure
type = forbidden
source_modules =
shop.domain
forbidden_modules =
shop.infrastructure
shop.webListe de contrôle : revoir la conception d’un module
Utilisez-la pour revoir un nouveau module, composant ou service, ou une modification importante de l’un d’eux. Elle s’appuie sur toutes les sections de ce chapitre.
- Pouvez-vous énoncer sa responsabilité en une phrase, et nommer l’acteur qui demande ses modifications ?Si la phrase a besoin d’un « et », ou si plusieurs acteurs le modifient, envisagez de le découper (modularité, SRP, CCP).
- Masque-t-il une décision de conception susceptible de changer ?Un module doit détenir un secret, comme un format de données, un fournisseur ou un algorithme (masquage de l’information).
- L’interface est-elle petite par rapport à ce qu’elle masque ?Les modules profonds masquent beaucoup derrière peu ; les couches de simple transmission ajoutent un coût sans rien masquer.
- Ses dépendances pointent-elles vers des modules plus stables et plus abstraits ?Recherchez les cycles et les modules stables qui dépendent de modules volatils (ADP, SDP, DIP).
- Que doit savoir un client pour l’utiliser correctement ?Recherchez les paramètres positionnels, les valeurs magiques, les ordres d’appel imposés et les hypothèses de timing ; affaiblissez-les ou gardez-les locaux (connascence).
- Qui en est responsable, et cette équipe peut-elle le modifier et le déployer sans se coordonner avec d’autres ?Une seule équipe responsable par module ; les interfaces entre équipes doivent être explicites et versionnées (loi de Conway).
- Une partie de sa complexité est-elle accidentelle ?Interrogez chaque couche, option et mécanisme générique supplémentaire : à quelle exigence réelle répond-il (KISS, YAGNI) ?
- Une connaissance est-elle dupliquée, ou une ressemblance fortuite est-elle partagée à tort ?La connaissance dupliquée a besoin d’un seul responsable ; du code qui se ressemble mais relève de responsables différents peut rester séparé (DRY).
- Quelles décisions qu’il contient sont difficiles à annuler, et sont-elles délibérées ?Les formes des API publiques, les modèles de données et les frontières méritent un examen plus attentif que les détails internes.
- Que disent les métriques et l’historique ?Vérifiez les tendances de complexité, les métriques de couplage, le couplage de changement et les hotspots, et ajoutez une fonction d’aptitude pour toute règle qui mérite d’être conservée.
Vérifiez votre compréhension
Une fonction contient deux instructions if et une boucle while, sans opérateur booléen. Quelle est sa complexité cyclomatique ?
Trois décisions plus un, donc 4.
Deux services contiennent chacun une fonction identique de 20 lignes pour formater des dates. Est-ce une violation de DRY ?
Probablement pas. C’est du texte dupliqué plutôt que de la connaissance métier dupliquée, et la partager couplerait les mises en production des services ; les règles métier dupliquées, comme le calcul de l’impôt, sont une autre affaire.
Pourquoi YAGNI ne s’applique-t-il pas à l’écriture de tests automatisés ?
Les tests font partie de ce qui rend le changement futur peu coûteux et sûr ; YAGNI consiste à ne pas construire de fonctionnalités ni de flexibilité spéculatives, et il repose sur de telles pratiques pour fonctionner.
On impose à une équipe qu’aucune fonction ne dépasse une complexité de 10, et la complexité baisse tandis que les défauts augmentent. Que s’est-il probablement passé ?
La loi de Goodhart : les fonctions ont été découpées mécaniquement pour satisfaire la métrique, ce qui rend la logique plus difficile à suivre sans la rendre plus simple.
Sources et lectures complémentaires
Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.
Demander : « Notre équipe veut imposer une complexité cyclomatique maximale et une couverture de tests minimale dans la CI. Comment utiliser ces métriques sans que les gens les manipulent ? »