Agile Software Development
Chapitre 10 / 15 · 19 min de lecture

Les principes de conception des packages

Les six principes propres au livre : cohésion (REP, CCP, CRP) et couplage (ADP, SDP, SAP), avec la métrique de la séquence principale.

Au-dessus de la classe, il existe une autre unité d'organisation que SOLID ignore : le package (package). Quand une application grossit, la classe — si pratique soit-elle pour les petits programmes — devient un grain trop fin pour structurer l'ensemble. Il faut quelque chose de « plus gros », un conteneur qui regroupe des classes liées et dont on puisse raisonner les relations à un niveau d'abstraction supérieur. Martin consacre à ce sujet six principes qui lui sont propres : on ne les retrouve ni dans Clean Code ni dans le catalogue GoF. Trois gouvernent la cohésion des packages (quelles classes mettre ensemble) ; trois gouvernent leur couplage (comment les packages se relient). Les deux derniers introduisent par surcroît un jeu de métriques de gestion des dépendances (Dependency Management metrics) qui permet de mesurer, chiffres à l'appui, la qualité d'une architecture.

Une remarque liminaire, et elle est contre-intuitive : ces principes ne se décrètent pas en début de projet. La structure des packages émerge au fil de la croissance du code, par tâtonnements successifs. Il faut d'abord avoir découvert des classes et leurs relations avant de pouvoir les partitionner intelligemment.

La cohésion : quelles classes vont ensemble ?

Les trois principes de cohésion adoptent un point de vue ascendant (bottom-up) : ils supposent qu'au moins une partie des classes et de leurs interrelations a déjà été mise au jour. La question qu'ils résolvent est celle de l'allocation — quelles classes regrouper dans un même package.

REP : le grain de réutilisation est le grain de release

Le premier principe, l'équivalence réutilisation/release (Reuse-Release Equivalency Principle, REP), part d'une question très concrète : qu'attendez-vous de l'auteur d'une bibliothèque que vous comptez réutiliser ? Au-delà d'une bonne documentation, d'un code qui marche et d'interfaces bien spécifiées, vous voulez surtout des garanties. Que l'auteur maintienne le code pour vous ; qu'il vous prévienne à l'avance de tout changement d'interface ; qu'il vous laisse le droit de refuser une nouvelle version pendant que vous traversez une période chargée ; qu'il continue à supporter votre ancienne version le temps que vous migriez.

Ces préoccupations semblent politiques et administratives, mais elles ont un effet profond sur le découpage du logiciel. Pour offrir ces garanties, l'auteur doit organiser son code en packages réutilisables et les suivre par des numéros de release. D'où l'énoncé :

Le grain de réutilisation est le grain de release.

On ne peut réutiliser que ce qui est suivi. Il est illusoire d'écrire une classe et de la déclarer « réutilisable » : la réutilisabilité ne naît qu'avec un système de suivi qui apporte notification, sécurité et support. Conséquence directe sur le contenu d'un package : ou bien toutes ses classes sont réutilisables, ou bien aucune ne l'est. Et il faut aussi penser au public visé. Une bibliothèque de conteneurs et un framework financier sont l'un et l'autre réutilisables, mais ne doivent pas cohabiter dans le même package : beaucoup de gens voudront réutiliser les conteneurs sans avoir le moindre intérêt pour la finance.

CRP : ce qui est réutilisé ensemble vit ensemble

Le principe de réutilisation commune (Common-Reuse Principle, CRP) affine la décision :

Les classes d'un package sont réutilisées ensemble. Si vous réutilisez l'une d'elles, vous les réutilisez toutes.

Les classes sont rarement réutilisées isolément. Une abstraction réutilisable collabore avec d'autres classes : un conteneur va de pair avec ses itérateurs, parce qu'ils sont fortement couplés. Ces classes appartiennent au même package, où l'on s'attend précisément à voir beaucoup de dépendances mutuelles.

Mais le CRP en dit autant sur ce qu'il faut séparer. Dès qu'un package en utilise un autre, une dépendance se crée entre eux — et elle reste entière même si le package client n'emploie qu'une seule classe du package utilisé. Car les packages ont des représentations physiques : un fichier JAR, une DLL, une bibliothèque partagée. Si le package utilisé est livré sous forme de JAR, le code client dépend du JAR tout entier. La moindre modification — fût-elle apportée à une classe dont le client se moque éperdument — provoque une nouvelle release du JAR, sa redistribution, et la revalidation du code client.

À retenir

Le CRP énonce surtout ce qu'il ne faut pas mettre ensemble : des classes qui ne sont pas étroitement liées par des relations de classe n'ont rien à faire dans le même package. Sinon, vous revaliderez et redistribuerez plus que nécessaire, à pure perte.

CCP : ce qui change ensemble vit ensemble

Le principe de fermeture commune (Common-Closure Principle, CCP) est, selon les mots mêmes de Martin, le principe de responsabilité unique (Single-Responsibility Principle, SRP) reformulé pour les packages :

Les classes d'un package devraient être fermées ensemble face aux mêmes types de changements. Un changement qui affecte un package affecte toutes les classes de ce package et aucun autre package.

De même que le SRP interdit à une classe d'avoir plusieurs raisons de changer, le CCP l'interdit à un package. Dans la plupart des applications, la maintenabilité prime sur la réutilisabilité. Si le code doit changer, mieux vaut que le changement se concentre dans un seul package plutôt que de se disperser dans plusieurs : on ne release alors que le package modifié, et les packages qui n'en dépendent pas n'ont ni à être revalidés ni à être re-livrés.

Le CCP est intimement lié au principe ouvert/fermé (Open-Closed Principle, OCP) : c'est de « fermeture » au sens de l'OCP qu'il s'agit. Comme une fermeture à 100 % est inatteignable, elle doit être stratégique — on ferme le système face aux types de changements les plus fréquents que l'expérience a révélés. Le CCP amplifie cette idée à l'échelle du package : en regroupant les classes ouvertes aux mêmes changements, on maximise les chances qu'une évolution des besoins reste cantonnée à un nombre minimal de packages.

La tension entre les trois

Ces trois principes s'opposent mutuellement. Le REP et le CRP tirent vers la réutilisation et tendent à faire grossir les packages (englober tout ce qui est réutilisé ensemble). Le CCP, lui, tire vers la maintenabilité et tend à les faire rétrécir (isoler ce qui change ensemble). L'architecte doit arbitrer entre ces forces opposées, et l'arbitre n'est pas figé : le découpage adéquat aujourd'hui ne le sera peut-être plus l'an prochain.

        Réutilisabilité                    Maintenabilité
              │                                  │
        REP ──┤                                  ├── CCP
        CRP ──┤   (font grossir le package)      │   (fait rétrécir le package)
              ▼                                  ▼
        ┌───────────────────────────────────────────────┐
        │   Le triangle de tension de la cohésion        │
        │   On ne peut satisfaire les trois à la fois.   │
        └───────────────────────────────────────────────┘

Note

Le projet jeune privilégie souvent la développabilité : le CCP domine, on regroupe ce qui change ensemble. Le projet mature, dont les composants se stabilisent, glisse vers la réutilisabilité : le CRP prend le relais. La composition des packages « vibre » et évolue ainsi avec le temps. C'est normal, c'est même souhaitable.

Le couplage : comment les packages se relient ?

Les trois principes suivants gouvernent les relations entre packages — un graphe de dépendances dont la qualité conditionne la « constructibilité » (buildability) du système tout entier.

ADP : aucun cycle dans le graphe de dépendances

Avez-vous déjà passé une journée à faire marcher quelque chose, puis êtes rentré chez vous pour découvrir le lendemain matin que plus rien ne fonctionne ? Pourquoi ? Parce qu'un collègue est resté plus tard que vous et a modifié quelque chose dont vous dépendiez. Martin nomme ce fléau le « syndrome du lendemain matin » (morning-after syndrome). Il devient cauchemardesque dès que l'équipe grandit : il n'est pas rare, dans les équipes indisciplinées, que des semaines s'écoulent sans qu'on parvienne à produire une version stable. Le principe des dépendances acycliques (Acyclic-Dependencies Principle, ADP) le conjure d'une phrase :

N'autorisez aucun cycle dans le graphe de dépendances des packages.

Au fil des décennies, deux solutions à ce fléau ont émergé, l'une et l'autre venues de l'industrie des télécommunications. La première est le build hebdomadaire (weekly build) ; la seconde est l'ADP. Le build hebdomadaire est courant dans les projets de taille moyenne, et son fonctionnement est simple : pendant quatre jours, tous les développeurs s'ignorent, travaillent sur des copies privées du code et ne se soucient pas de s'intégrer les uns aux autres ; puis, le vendredi, ils intègrent l'ensemble de leurs changements et construisent le système. L'avantage est réel — chacun vit dans un monde isolé quatre jours sur cinq — mais la rançon en est la lourde pénalité d'intégration payée le vendredi. Et cette parade ne tient pas à l'échelle : à mesure que le projet grossit, il devient impossible de terminer l'intégration le vendredi ; elle déborde sur le samedi, puis on la fait commencer le jeudi, et le cycle de travail utile se rétrécit. On passe alors à un build bihebdomadaire, mais le temps d'intégration continue de croître avec la taille du projet. On débouche sur une crise : pour préserver l'efficacité, il faut sans cesse allonger le calendrier de build, mais l'allonger accroît les risques du projet — l'intégration et les tests deviennent de plus en plus ardus, et l'équipe perd le bénéfice du retour rapide. Le build hebdomadaire n'est donc qu'un pis-aller ; la vraie solution est l'ADP.

La parade consiste à partitionner l'environnement de développement en packages livrables, qui deviennent des unités de travail. Quand une équipe fait marcher son package, elle lui attribue un numéro de release et le met à disposition des autres. Chacun décide alors pour soi s'il adopte tout de suite la nouvelle version ou s'il reste sur l'ancienne. Plus personne n'est à la merci des autres ; l'intégration se fait par petits incréments. Mais cela n'est possible qu'à la condition que le graphe de dépendances soit un graphe orienté acyclique (DAG). S'il contient un cycle, le syndrome du lendemain matin redevient inévitable.

Voici une structure saine — un DAG. Quel que soit le package où l'on part, impossible en suivant les flèches de revenir à son point de départ.

                  ┌──────────────────┐
                  │   MyApplication  │
                  └──┬──────┬─────┬──┘
            ┌────────┘      │     └────────┐
            ▼               ▼              ▼
    ┌──────────────┐  ┌──────────┐  ┌──────────┐
    │ MessageWindow│  │ TaskWindow│  │  MyTasks │──────► Database
    └──────┬───────┘  └────┬─────┘  └──┬────┬──┘
           │               ▼           │    ▼
           │            ┌──────┐       │ ┌──────────┐
           │            │ Task │◄──────┘ │ MyDialogs│
           │            └──┬───┘         └────┬─────┘
           └───────────────┴──────┬───────────┘

                            ┌──────────┐
                            │ Windows  │
                            └──────────┘

Dans ce graphe, quand l'équipe de MyDialogs publie une release, il suffit de remonter les flèches pour savoir qui est touché : MyTasks et MyApplication, et eux seuls. Les autres packages ignorent jusqu'à l'existence de MyDialogs. Pour tester MyDialogs, on n'a besoin de compiler que lui et Windows. Et la release globale se fait proprement, de bas en haut : Windows d'abord, puis Task, et ainsi de suite jusqu'à MyApplication.

Que se passe-t-il si un nouveau besoin force une classe de MyDialogs à utiliser une classe de MyApplication ? Un cycle apparaît, et les ennuis avec.

                  ┌──────────────────┐
                  │   MyApplication  │◄───────────┐
                  └──┬──────┬─────┬──┘            │  ← la flèche
                     ...    ...   ...             │     qui crée
                            ┌──────────┐          │     le cycle
                            │ MyDialogs│──────────┘
                            └──────────┘

Désormais MyTasks ne dépend plus seulement de Task, MyDialogs, Database et Windows : via le cycle, il dépend de tous les autres packages du système. MyApplication, MyTasks et MyDialogs doivent maintenant être toujours releasés ensemble — ils ont, de fait, fusionné en un seul gros package. Pire : pour tester MyDialogs, il faut désormais lier l'intégralité du système, y compris Database. Si vous vous êtes déjà demandé pourquoi un simple test unitaire vous oblige à lier tant de bibliothèques étrangères, c'est très probablement à cause d'un cycle.

Piège courant

Surveillez le graphe en permanence. Le découpage « vibre » au gré des besoins changeants, et les cycles s'y faufilent. Quand un cycle apparaît, il faut le casser, par l'un des deux moyens suivants — quitte à faire grossir la structure de packages.

Il y a toujours moyen de restaurer le DAG. Deux mécanismes :

  1. Appliquer le principe d'inversion de dépendance (Dependency-Inversion Principle, DIP). On crée une interface portant exactement ce dont MyDialogs a besoin, on la place dans MyDialogs, et on fait implémenter cette interface par MyApplication. La dépendance s'inverse, le cycle disparaît. Remarquez encore une fois que l'interface est nommée d'après son client, pas d'après son fournisseur : les interfaces appartiennent à ceux qui les utilisent.
// ❌ Avant : MyDialogs appelle directement MyApplication → cycle.
// (dans MyDialogs)
import { MyApplication } from "../my-application";

class DialogManager {
  fermer(app: MyApplication): void {
    app.rafraichir(); // dépendance concrète vers le haut
  }
}
// ✅ Après : l'interface vit chez le client (MyDialogs),
// MyApplication l'implémente → la dépendance s'inverse.

// (dans MyDialogs)
export interface Rafraichissable {
  rafraichir(): void;
}

class DialogManager {
  fermer(cible: Rafraichissable): void {
    cible.rafraichir(); // ne dépend plus que d'une abstraction locale
  }
}

// (dans MyApplication)
import { Rafraichissable } from "../my-dialogs";

class MyApplication implements Rafraichissable {
  rafraichir(): void {
    /* ... */
  }
}
  1. Créer un nouveau package dont MyDialogs et MyApplication dépendent tous les deux. On y déplace la ou les classes que chacun utilise. Le cycle se résorbe en faisant porter la dépendance commune par un tiers neutre.

La conception n'est pas descendante

Tout ceci mène à une conclusion incontournable : la structure des packages ne peut pas se concevoir de haut en bas (top-down). Ce n'est pas l'une des premières choses qu'on dessine. On croit volontiers qu'une décomposition à gros grain comme un découpage en packages reflète les fonctions du système. C'est faux. Un diagramme de dépendances de packages ne décrit pas ce que fait l'application : c'est une carte de constructibilité. Voilà pourquoi on ne le dessine pas au début — il n'y a encore rien à construire, donc aucune carte de construction n'est nécessaire. La structure émerge à mesure que les classes s'accumulent, qu'on applique le SRP et le CCP pour colocaliser ce qui change ensemble, que le CRP dicte la composition des éléments réutilisables, et qu'enfin l'ADP casse les cycles qui surgissent.

SDP : dépendre dans le sens de la stabilité

Un design ne peut pas être entièrement statique : une part de volatilité est nécessaire pour qu'il reste maintenable. Le CCP nous fait précisément créer des packages conçus pour être volatils — on s'attend à ce qu'ils changent. Le principe des dépendances stables (Stable-Dependencies Principle, SDP) protège cette volatilité :

Dépendez dans le sens de la stabilité.

Car c'est la perversité du logiciel : un module que vous avez conçu pour être facile à changer peut être rendu difficile à changer par quelqu'un d'autre qui se contente d'accrocher une dépendance dessus. Pas une ligne de votre code ne bouge, et pourtant votre module devient soudain rigide.

Qu'est-ce que la stabilité ? Posez une pièce de monnaie sur la tranche : elle ne change pas, mais elle est instable, car il faut très peu d'effort pour la renverser. Une table, à l'inverse, est stable parce qu'il faut beaucoup d'effort pour la retourner. La stabilité n'a donc rien à voir avec la fréquence de changement : elle mesure la quantité de travail requise pour changer. Or un moyen sûr de rendre un package difficile à changer est de faire dépendre beaucoup d'autres packages de lui : tout changement devra être réconcilié avec tous ses dépendants.

On peut chiffrer cela. On compte les dépendances qui entrent et qui sortent d'un package :

  • Ca — couplages afférents (afferent couplings) : le nombre de classes extérieures au package qui dépendent de classes intérieures. Ce sont les dépendants.
  • Ce — couplages efférents (efferent couplings) : le nombre de classes intérieures qui dépendent de classes extérieures. Ce sont les dépendances.
  • I — instabilité (instability) :
              Ce
   I = ─────────────        I ∈ [0, 1]
          Ca + Ce

Quand I = 0, le package est maximalement stable : d'autres dépendent de lui (Ca > 0), mais lui ne dépend de personne (Ce = 0). Il est responsable (ses dépendants l'empêchent de bouger) et indépendant (rien ne le force à bouger). Quand I = 1, il est maximalement instable : personne ne dépend de lui, et lui dépend des autres. Il est irresponsable et dépendant — rien ne le retient, et ses dépendances lui donnent toutes les raisons de changer.

Le SDP s'énonce alors comme une inégalité : le I d'un package doit être supérieur au I des packages dont il dépend. Autrement dit, le I doit décroître dans le sens des dépendances. On suit les flèches vers le bas, vers le toujours plus stable.

   I=1  ┌──────────┐        ┌──────────┐  I=1
        │ Instable │        │ Instable │       ← le code volatil
        └────┬─────┘        └────┬─────┘          en haut
             └────────┬──────────┘

                ┌──────────┐
                │  Stable  │  I=0             ← le code stable en bas
                └──────────┘

   Convention : toute flèche qui pointe vers le HAUT viole le SDP.

Tous les packages ne doivent surtout pas être stables : si tout était maximalement stable, le système serait figé. On veut un mélange — des packages volatils en haut qui dépendent de packages stables en bas. Et quand un package Stable finit par dépendre d'un package Flexible qu'on voulait justement garder souple, on rend Flexible rigide. Le remède est, là encore, le DIP : on extrait l'interface dont le code stable a besoin dans un package très stable (I = 0), que les deux côtés viennent référencer. Flexible retrouve son instabilité nécessaire, et toutes les dépendances coulent à nouveau dans le sens de I décroissant.

SAP : un package stable doit être abstrait

Reste une question gênante. On veut placer les décisions d'architecture de haut niveau dans des packages stables, pour qu'elles ne soient pas volatiles. Mais un package stable est par définition difficile à changer — ne risque-t-on pas de figer l'architecture ? La réponse tient dans l'OCP : on peut créer des classes extensibles sans modification. Quelles classes ? Des classes abstraites. D'où le principe des abstractions stables (Stable-Abstractions Principle, SAP) :

Un package devrait être aussi abstrait qu'il est stable.

Un package stable doit donc être abstrait pour que sa stabilité ne l'empêche pas d'être étendu ; un package instable peut rester concret, puisque son instabilité permet de modifier facilement le code concret qu'il contient. SDP et SAP combinés équivalent au DIP appliqué aux packages : le SDP fait pointer les dépendances vers la stabilité, le SAP assimile stabilité et abstraction — donc les dépendances pointent vers l'abstraction. La nuance est que le DIP, qui s'applique aux classes, ne connaît pas de demi-teinte (une classe est abstraite ou ne l'est pas), tandis que SDP + SAP, qui s'appliquent aux packages, tolèrent qu'un package soit partiellement stable et abstrait.

On mesure l'abstraction par un ratio simple :

        Na           Na = nombre de classes abstraites
   A = ────          Nc = nombre total de classes        A ∈ [0, 1]
        Nc

A = 0 : aucune classe abstraite. A = 1 : que des classes abstraites.

La séquence principale

On peut maintenant relier les deux métriques. Traçons un graphe avec A en ordonnée et I en abscisse. Les deux « bonnes » sortes de packages occupent deux coins : en haut à gauche (I=0, A=1), maximalement stable et abstrait ; en bas à droite (I=1, A=0), maximalement instable et concret. Entre les deux, le segment qui les relie est la séquence principale (main sequence) — un clin d'œil de Martin aux diagrammes de Hertzsprung-Russell de l'astronomie.

   A=1 ┌──────────────────────────────────────┐
       │ (0,1) ●                               │
       │  abstrait     ╲                       │
       │  & stable       ╲  Séquence           │
       │                   ╲  principale       │
       │     ZONE             ╲                │
       │     D'INUTILITÉ        ╲              │
       │     (1,1)               ╲            │
       │                          ╲   ZONE    │
       │   ZONE DE                  ╲  (1,0)  │
       │   DOULEUR (0,0)              ╲ ●     │
   A=0 └───────────────────────────────╲──────┘ concret
       I=0                                I=1   & instable

Les zones à fuir se déduisent par élimination. Près de (0,0) : un package stable et concret. Il est rigide — impossible à étendre faute d'abstraction, impossible à changer du fait de sa stabilité. C'est la zone de douleur (Zone of Pain). Le schéma de base de données en est l'archétype : notoirement volatil, extrêmement concret, et fortement dépendu — d'où la difficulté chronique des migrations de schéma. (Une bibliothèque utilitaire concrète mais non volatile, comme un package de chaînes de caractères, peut y séjourner sans dommage : elle ne change jamais. Martin note d'ailleurs un cas curieux — un tel package « possède une instabilité I de 1 » tout en se trouvant en (0,0) : ses classes sont toutes concrètes, mais comme rien ne le force à changer, sa place dans la zone de douleur reste inoffensive. On peut même imaginer un troisième axe, celui de la volatilité, et considérer que tout le graphe se lit ici dans le plan « volatilité = 1 ».)

Près de (1,1) : un package abstrait et sans dépendant. Maximalement abstrait, mais que personne n'utilise — donc parfaitement inutile. C'est la zone d'inutilité (Zone of Uselessness).

Un package posé sur la séquence principale n'est ni « trop abstrait pour sa stabilité », ni « trop instable pour son abstraction ». Il est dépendu dans la mesure où il est abstrait, et il dépend des autres dans la mesure où il est concret. Les positions idéales restent les deux extrémités du segment, mais l'expérience de Martin montre que moins de la moitié des packages d'un projet peuvent les atteindre ; les autres se portent au mieux sur ou près de la ligne.

La distance D'

Dernière métrique : à quelle distance un package se trouve-t-il de cet idéal ?

   D' = | A + I − 1 |        D' ∈ [0, 1]

D' = 0 : le package est pile sur la séquence principale. D' = 1 : il en est aussi éloigné que possible. On peut calculer le D' de chaque package et rééexaminer tous ceux dont la valeur s'écarte de zéro. On peut même faire des statistiques : moyenne et variance des D' proches de zéro pour un design conforme, « limites de contrôle » pour repérer les packages aberrants, et — particulièrement utile — un tracé de D' dans le temps, release après release, pour un package donné. Si le D' du package Payroll se met à grimper au fil des versions et franchit un seuil de contrôle (disons D' = 0,1), c'est le signal qu'une dépendance malsaine s'y est glissée et qu'il faut aller voir pourquoi.

Attention

Une métrique n'est pas un dieu, mais une simple mesure par rapport à un étalon arbitraire. Le motif de dépendance et d'abstraction encodé ici reflète une expérience — celle, longue, de Martin — de ce qui constitue de « bonnes » dépendances. Il se peut qu'il convienne à certaines applications et pas à d'autres. Servez-vous-en comme d'un radar, pas comme d'un verdict.

Récapitulatif des six principes

SigleNom (FR / EN)CatégorieÉnoncé en une phrase
REPÉquivalence réutilisation/release (Reuse-Release Equivalency)CohésionLe grain de réutilisation est le grain de release.
CRPRéutilisation commune (Common-Reuse)CohésionCe qui est réutilisé ensemble vit ensemble ; ne forcez pas un client à dépendre de ce qu'il n'utilise pas.
CCPFermeture commune (Common-Closure)CohésionCe qui change ensemble, pour les mêmes raisons, vit ensemble (le SRP des packages).
ADPDépendances acycliques (Acyclic-Dependencies)CouplageAucun cycle dans le graphe de dépendances.
SDPDépendances stables (Stable-Dependencies)CouplageDépendez dans le sens de la stabilité (le I décroît avec les flèches).
SAPAbstractions stables (Stable-Abstractions)CouplageUn package est aussi abstrait qu'il est stable (SDP + SAP = DIP des packages).

À retenir

  • Au-dessus de la classe, le package est l'unité d'organisation des grandes applications ; six principes propres à ce livre en régissent la cohésion et le couplage.
  • Cohésion — REP, CRP, CCP : on regroupe ce qui se release ensemble, ce qui se réutilise ensemble, ce qui change ensemble. Ces trois forces s'opposent (réutilisabilité contre maintenabilité) et le découpage « vibre » avec la maturité du projet.
  • ADP : aucun cycle dans le graphe, sous peine du « syndrome du lendemain matin ». On casse un cycle par le DIP (inverser une dépendance via une interface chez le client) ou par un nouveau package intermédiaire.
  • La structure de packages n'est pas descendante : c'est une carte de constructibilité qui émerge avec le code, pas une décomposition fonctionnelle décidée d'avance.
  • SDP : dépendez dans le sens de la stabilité, mesurée par l'instabilité I = Ce / (Ca + Ce) ; le I doit décroître quand on suit les flèches.
  • SAP : un package stable doit être abstrait (A = Na / Nc) pour rester extensible ; combiné au SDP, c'est le DIP à l'échelle des packages.
  • La séquence principale relie (I=0, A=1) à (I=1, A=0) ; on fuit la zone de douleur (stable et concret, ex. schéma de BDD) et la zone d'inutilité (abstrait et sans dépendant). La distance D' = |A + I − 1| chiffre l'écart à cet idéal.