Faire respecter l’architecture, ne pas se fier à l’intention
Faire respecter l’architecture, ne pas se fier à l’intention
Pourquoi les règles structurantes de l’architecture doivent devenir des vérifications exécutables plutôt que des intentions auxquelles se fier.
Partie de la série : Encoder le système · Voir toutes les parties
Précédemment dans cette série : Faire attribuer les boucles de feedback, pas seulement détecter
Idée centrale : Une architecture n’est pas ce qu’une équipe dit respecter. C’est ce que le système rend visible et vérifiable. Les règles qui portent le système doivent être appliquées par des vérifications exécutables, parce que l’intention, si sincère soit-elle, ne peut pas refuser un commit.
La version courte : 2 min L’article complet : 17 min
La version courte
Chaque équipe a une architecture à laquelle elle croit : le schéma, l’ADR, l’arborescence des dossiers, la présentation d’accueil. Et presque chaque équipe découvre, des mois plus tard, que la base de code a discrètement cessé de la suivre. Personne n’a décidé d’abandonner l’architecture. Elle s’est érodée, un commit d’apparence raisonnable à la fois.
Le problème n’est pas la discipline. Le problème est qu’un schéma ne peut pas refuser un commit. La documentation, les conventions et la mémoire collective sont des déclarations d’intention, et l’intention n’a aucun pouvoir d’application. Une architecture ne devient réelle que lorsque la déviation devient visible, et elle ne devient durable que lorsque la déviation devient coûteuse.
La sortie consiste à exprimer les règles structurantes de l’architecture comme des vérifications exécutables : contraintes d’import, frontières de modules, sens des dépendances, présence des contrats, complétude du câblage. Chaque vérification est petite. Ensemble, elles transforment l’architecture, d’une histoire que l’équipe se raconte en une propriété que le système vérifie.
En trois idées
-
L’intention se dégrade en silence ; les vérifications échouent bruyamment. Une frontière qui vit dans un document s’érode sans laisser de trace. Une frontière exprimée comme une règle d’import laisse une marque rouge à l’instant où elle est franchie.
-
Les violations dangereuses sont des absences, pas des présences. Un import interdit se repère facilement. Un handler écrit mais jamais câblé, un port sans adapter, un schéma de validation qui existe mais n’est pas appelé à la frontière : tout cela passe la vérification de types et les tests. L’application des règles doit chercher les choses requises qui manquent, pas seulement les choses interdites qui sont là.
-
Une bonne règle protège un concept, pas une convention. « Aucun fichier de plus de 120 lignes » impose un goût. « Le domaine ne doit pas dépendre de l’infrastructure » impose le système. Les règles qui protègent de vrais concepts survivent ; les règles qui encodent des préférences apprennent aux gens à ignorer le vérificateur.
À retenir
Ne demandez pas si l’équipe fait confiance à l’architecture. Demandez ce qui passerait au rouge si l’architecture était trahie aujourd’hui. Tout ce qui ne passerait pas au rouge n’est pas encore de l’architecture. C’est de l’espoir.
L’article complet
L’article précédent s’achevait sur une promesse. Il soutenait que les boucles de feedback deviennent puissantes quand le code leur donne une structure explicite à observer, et il montrait une petite : trois états, trois événements, neuf cellules, chacune classée. Puis il disait que le cas intéressant est le grand : le protocole à plusieurs dizaines d’états et d’événements, où la matrice cesse d’être une vérification locale et devient une véritable surface d’audit.
Cet article tient cette promesse. Mais pour y arriver, il doit commencer par une observation plus inconfortable.
La plupart des architectures ne sont appliquées par rien du tout.
Chaque base de code a deux architectures
Il y a l’architecture que l’équipe déclare : le schéma dans le wiki, les décisions enregistrées, le dessin en couches de la dernière session d’accueil, la phrase « le domaine ne touche jamais directement la base de données » que tout le monde approuve d’un hochement de tête.
Et il y a l’architecture que le code a réellement : le vrai graphe d’imports, les vrais sens de dépendance, les vrais endroits où la validation a lieu ou n’a pas lieu, les vrais propriétaires de chaque mutation.
Le premier jour, les deux coïncident. Puis le projet vit. Une échéance pousse un appel à la base de données dans un composant, « juste pour l’instant ». Un helper migre d’un module feuille vers les utilitaires partagés et traîne ses dépendances avec lui. Une nouvelle fonctionnalité imite le mauvais exemple, et sa forme devient l’exemple de la suivante. Chaque pas est localement raisonnable. Aucun n’est signalé, parce que rien ne regarde.
C’est la , et l’essentiel à son sujet, c’est qu’elle est silencieuse. Personne n’écrit un ADR intitulé « nous abandonnons par la présente le découpage en couches ». L’architecture déclarée reste immaculée dans le wiki précisément parce qu’elle vit dans le wiki. Les documents ne compilent pas, donc ils ne peuvent pas casser.
Les modèles de réflexion logicielle, décrits par Murphy, Notkin et Sullivan dans les années 1990, ont été construits exactement autour de cet écart : prendre le modèle de haut niveau auquel les ingénieurs croient, extraire la structure réelle des sources, et montrer les différences. Ce qu’ils ont trouvé, encore et encore, c’est que les deux vues divergent dans tout système qui vit longtemps, et que les ingénieurs sont systématiquement surpris de l’endroit où elles divergent.
La leçon n’est pas que les équipes sont négligentes. La leçon est que l’alignement entre l’intention et le code n’est pas un état stable. C’est un processus contrôlé, et sans contrôleur il ne se produit pas.
La confiance n’est pas une stratégie de maintenance. Le feedback en est une.
L’architecture déclarée vit dans des documents et ne peut pas refuser un commit ; l’architecture réelle vit dans le code et dérive un changement raisonnable à la fois. Une règle exécutable est la seule chose qui compare les deux à chaque changement.
Chargement du diagramme…
À quoi ressemble une règle d’architecture exécutable
L’alternative à l’intention à laquelle on se fie, c’est la règle appliquée : une vérification qui tourne contre la base de code et échoue quand une propriété structurante ne tient plus. La littérature sur l’architecture évolutive appelle cela des . Quel que soit le nom, celles qui sont utiles partagent une anatomie.
Une règle a trois parties.
D’abord, le concept qu’elle protège. Pas « les imports depuis ce dossier sont interdits » comme une prohibition flottante, mais le concept système derrière : cette frontière existe pour que le domaine reste indépendant du mécanisme de livraison. Le concept est le pourquoi, et il a sa place dans la règle, parce qu’une règle dont la raison s’est perdue devient un rituel, et les rituels se suppriment.
Ensuite, le signal qu’elle détecte. Quelque chose de mécaniquement vérifiable : une arête dans le graphe d’imports, un motif dans l’AST, un nom qui ne correspond pas à une forme requise, un schéma déclaré mais jamais référencé. Le signal est l’endroit où la règle touche le code.
Enfin, le diagnostic qu’elle produit. Et c’est là que la plupart des outils sont en dessous. Un diagnostic utile ne pointe pas seulement une ligne. Il renvoie au concept : pas « import de pg interdit ici » mais « ce module est à l’intérieur de la frontière du domaine, et le domaine ne doit pas dépendre de l’infrastructure ; la dépendance a sa place derrière un port ». L’article précédent appelait cela l’. Une règle qui attribue enseigne ; une règle qui se contente de rejeter entraîne les gens à l’apaiser.
Concrètement, la même règle de frontière peut être appliquée à plusieurs niveaux, et les niveaux diffèrent en force, pas seulement en outillage :
- une règle de lint sur les imports (
domain/**ne doit pas importerinfrastructure/**), peu coûteuse et immédiate ; - des frontières de modules au niveau du compilateur, ou des références de projet, qui font échouer le build sur l’arête interdite ;
- une vérification sur le graphe de dépendances extrait, qui voit les chemins transitifs que les règles au niveau fichier manquent ;
- le packaging, la forme la plus forte, où le domaine ne peut littéralement pas nommer l’infrastructure parce qu’il n’en dépend pas.
La progression compte plus que les noms d’outils. Chaque marche déplace la règle de détectée après coup vers impossible à exprimer, et ce mouvement est le sujet d’un article à venir. Des outils comme ArchUnit dans le monde JVM ou dependency-cruiser dans le monde JavaScript existent précisément pour rendre les marches intermédiaires peu coûteuses.
Côté mécanique, le réglage utile est simple : les règles doivent être statiques (décidables depuis les sources, sans exécution), atomiques (chaque règle vérifie une propriété à un endroit, pour qu’un échec soit directement actionnable) et déclenchées (elles tournent à chaque changement, dans l’éditeur ou dans la porte de la CI, pas dans une revue trimestrielle). Des vérifications statiques, atomiques et déclenchées sont assez peu coûteuses pour tourner toujours, et les vérifications qui tournent toujours sont les seules qui attrapent la dérive quand elle a un commit d’âge plutôt qu’un an.
Présences interdites et pièces manquantes
Voici la distinction qui sépare une véritable application de l’architecture d’un tas de règles de lint. Les règles d’architecture ont deux polarités, et elles échouent de manières opposées.
Une interdit une présence : cette arête ne doit pas exister, cette couche ne doit pas être importée ici, cette variable globale ne doit pas être touchée depuis ce module. Quand une prohibition est violée, la preuve est concrète. Il y a une ligne à pointer. Les prohibitions sont la moitié facile, et la plupart des outils existants vivent là.
Une exige une présence, ce qui veut dire que sa violation est une absence : chaque port doit avoir un adapter câblé ; chaque handler joignable de l’extérieur doit valider son entrée ; chaque événement émis par ce module doit avoir un consommateur enregistré ; chaque machine à états doit traiter chaque événement qu’elle peut recevoir. Quand une obligation est violée, il n’y a pas de ligne à pointer. La preuve est un trou.
Une prohibition échoue sur une arête qui existe et ne devrait pas, ici core qui importe web ; une obligation échoue sur une arête qui devrait exister et n’existe pas, ici un port sans adapter câblé. La première a une ligne à pointer, la seconde un trou.
Chargement du diagramme…
Les deux polarités comptent parce que le feedback ordinaire est aveugle à la seconde.
Un adapter manquant passe la vérification de types. Un handler écrit mais jamais enregistré passe ses tests unitaires, parce que les tests l’appellent directement. Un schéma de validation qui existe mais n’est pas invoqué à la frontière satisfait toutes les vérifications locales, parce que localement il n’y a rien qui cloche. Le système a l’air terminé et n’est pas câblé. Rien ne passe au rouge, parce que chaque outil de la boucle locale examine ce qui est là, et que le défaut est ce qui n’est pas là.
C’est ici que l’application de l’architecture gagne sa place par rapport à compiler-et-tester. Vérifier une obligation demande un modèle de la forme requise : quelque chose doit dire « un port est censé avoir un adapter » avant que l’absence d’un adapter puisse être détectée. Ce modèle, c’est exactement l’architecture déclarée, enfin mise au travail. Les auteurs des modèles de réflexion avaient aussi un nom pour cette polarité : divergence pour la présence interdite, absence pour la pièce manquante.
Une propriété de plus en découle, et elle est pratique. Une prohibition peut être vérifiée à chaque instant d’un changement, parce qu’une arête interdite est fausse dès qu’elle apparaît. Une obligation ne peut être vérifiée qu’à l’achèvement, parce qu’une pièce requise est légitimement absente pendant que la fonctionnalité s’assemble encore. Appliquez les obligations trop tôt et vous noyez les constructeurs sous les fausses alertes ; ignorez-les et vous livrez le handler non câblé. Les prohibitions se vérifient à chaque pas ; les obligations se vérifient quand le travail se déclare terminé.
La matrice de disposition, à l’échelle d’un protocole
Maintenant, la promesse de l’article précédent.
Rappelez-vous la forme : une machine à états où chaque paire (état, événement) est classée par une disposition. Pas seulement les transitions que vous autorisez, mais un verdict explicite pour chaque cellule : Handled, Ignored, Stale, Rejected, Unexpected. Le système de types rend la matrice totale, de sorte qu’une paire non classée ne compile pas. À trois états par trois événements, c’est une jolie astuce locale. Le vérificateur de types la contrôle pendant que vous tapez.
Prenons le plus petit protocole réel de ce site : le cycle de vie d’un article. Quatre états, une poignée d’événements, et déjà la question que la matrice impose : que veut dire publish tant que l’article est encore un brouillon en relecture, et que veut dire un nouvel edit une fois qu’il est publié ?
Un article passe de brouillon à en relecture puis à publié, et de publié à archivé ; une modification le ramène en brouillon. La matrice classe aussi toutes les autres paires état-événement, pas seulement ces transitions.
Chargement du diagramme…
Le diagramme montre les transitions que quelqu’un a pris la peine de dessiner. La matrice, c’est le diagramme plus chaque cellule que le diagramme laisse vide, chacune avec un verdict, et le type rend les cellules vides illégales :
TypeScript
type State = "draft" | "inReview" | "published" | "archived";
type Event = "submit" | "reject" | "approve" | "edit" | "archive";
type Disposition = "Handled" | "Ignored" | "Rejected" | "Unexpected";
// Chaque paire (état, événement) doit être classée, sinon ceci ne compile pas.
export const dispositions = {
draft: { submit: "Handled", reject: "Rejected", approve: "Rejected", edit: "Handled", archive: "Ignored" },
inReview: { submit: "Ignored", reject: "Handled", approve: "Handled", edit: "Rejected", archive: "Rejected" },
published: { submit: "Rejected", reject: "Unexpected", approve: "Ignored", edit: "Handled", archive: "Handled" },
archived: { submit: "Rejected", reject: "Unexpected", approve: "Unexpected", edit: "Rejected", archive: "Ignored" },
} satisfies Record<State, Record<Event, Disposition>>;Vingt cellules, et le réducteur qui les implémente est un artefact séparé. Rien ci-dessus ne prouve que le réducteur est d’accord avec la matrice ; c’est le travail de l’oracle, et il peut être généré, un cas par cellule :
TypeScript
for (const [state, events] of Object.entries(dispositions)) {
for (const [event, disposition] of Object.entries(events)) {
test(`${state} × ${event} → ${disposition}`, () => {
expect(classify(reduce(state, event))).toBe(disposition);
});
}
}Changez d’échelle. Un vrai protocole (un flux de paiement, un cycle de vie de document, un gestionnaire de session, un moteur de synchronisation) atteint facilement des dizaines d’états et des dizaines d’événements. Disons trente états et quarante événements : mille deux cents cellules. À cette taille, trois choses changent de nature, pas seulement de degré.
D’abord, la matrice cesse d’être lisible d’un coup d’œil et devient un artefact relisible à part entière. Personne ne garde mille deux cents cellules en tête. Mais la matrice peut être rendue, diffée et relue comme une spécification, parce que c’est ce qu’elle est : le contrat comportemental complet du protocole, sans trou par construction. Une pull request qui change trois cellules vous montre, précisément, quelles situations ont changé de sens.
Ensuite, la matrice devient une surface d’audit au sens exact de cet article. Elle est statique : déclarée dans les sources, vérifiable sans rien exécuter. Elle est atomique : chaque cellule est une affirmation indépendamment vérifiable sur une situation. Elle est déclenchée : la totalité est revérifiée à chaque changement, parce que le compilateur l’impose, et le jour où quelqu’un ajoute un état ou un événement, chaque cellule que cette décision crée doit être classée avant que le code compile à nouveau. La matrice est une fonction de fitness sur le protocole : pas « ce code tourne-t-il » mais « chaque signal que ce protocole peut recevoir, dans chaque situation où il peut se trouver, a-t-il un sens réfléchi ».
Enfin, l’analyse de polarité s’applique à l’intérieur. La plupart des cellules sont des prohibitions par l’esprit (« cet événement dans cet état est une violation du protocole, réveillez quelqu’un »), et le compilateur les vérifie gratuitement. Mais la correspondance entre la matrice et le réducteur qui l’implémente est une obligation : la matrice affirme saving × segment-ready → Stale, et quelque chose doit vérifier que le réducteur le traite effectivement comme périmé. L’article précédent l’avait déjà concédé : la totalité est vérifiée par le compilateur, l’accord est vérifié par un test. À l’échelle d’un protocole, ce test mérite d’être généré depuis la matrice elle-même, un cas par cellule, pour que les deux artefacts ne puissent pas diverger en silence. La matrice est l’architecture déclarée du protocole ; les tests générés sont son application.
C’est le motif de tout l’article en miniature. L’intention de l’équipe (« nous gérons proprement les événements tardifs ») est devenue une structure (la matrice), la structure est devenue des affirmations vérifiables (les cellules), et les affirmations sont appliquées par deux boucles (le compilateur pour la totalité, les tests générés pour l’accord). Rien de la correctness du protocole n’est confié à la mémoire.
Appliquer l’intention, pas la lettre
Chaque règle appliquée crée une incitation à la satisfaire à la lettre et à la trahir dans l’esprit. Ce n’est pas du cynisme ; c’est ce que la pression des échéances fait à toute contrainte. Un système d’application qui l’ignore sera contourné jusqu’à l’inutilité.
Un exemple concret, parce que le geste n’a de sens qu’une fois qu’on voit ce qui le déclenche. Deux règles gardent le design system. L’une dit que l’identité du produit, ses couleurs et ses tokens d’espacement, ne vit que dans les composants du design system : un composant produit peut composer Button et Card, jamais écrire bg-brand-600 lui-même. L’autre dit que les composants du design system sont agnostiques du produit : rien sous ui/ n’importe un hook produit, un type produit ou une décision produit. Maintenant, un composant produit, sous échéance, se met à porter un bg-brand-600 écrit à la main. La règle de style tire. La réparation la moins chère n’est pas d’extraire la partie visuelle ; c’est de déplacer tout le fichier dans ui/, où les classes d’identité sont permises. La règle de style passe au vert. Le fichier est au bon endroit. L’architecture est toujours violée, parce que le composant importe toujours des hooks produit, connaît toujours les types du produit, possède toujours une décision produit. Seule son adresse a changé. La réparation honnête était une scission : la partie visuelle devient un composant du design system, et la logique produit reste où elle était et le compose.
La défense consiste à lier les règles au concept plutôt qu’à son substitut le moins cher, et à les lier par paires, pour que contourner l’une fasse tomber sur l’autre. Ici la seconde règle est le concept lui-même, et c’est une simple vérification d’imports : rien sous ui/ ne peut importer depuis un périmètre produit. Le fichier déplacé la déclenche sur le même commit. Une bonne règle demande qui possède la responsabilité, pas seulement où vit le fichier. « Le fichier est-il sous ui/ » est un . « Ce module importe-t-il de la logique produit » est plus proche du concept. « Quelque chose en dehors du module propriétaire mute-t-il cet état » est le concept lui-même. Les proxys sont moins coûteux à vérifier, et un jeu de règles mûr en utilise, mais chaque règle-proxy devrait porter le concept écrit dessus, pour que, quand le proxy est contourné, l’écart soit visible et la règle puisse être affinée.
Si le code respecte la règle de dossier mais viole la propriété, l’audit devrait quand même échouer. Quand il n’échoue pas, la solution n’est pas de faire honte au développeur qui l’a contourné. La solution est de le remercier pour le test d’intrusion gratuit et d’encoder ce qu’il a trouvé.
Un diff dit ce qui a changé. Un audit dit ce que le changement a fait au système.
La revue de code, telle qu’elle se pratique, consiste à lire des deltas. Le relecteur voit les lignes qui ont changé, plus quelques lignes de contexte, et reconstruit les conséquences dans sa tête. Pour les propriétés locales, ça marche. Pour les propriétés systémiques, ça ne peut pas marcher : l’information n’est pas dans le diff.
Un diff de trois lignes peut franchir une frontière qui a mis un an à s’établir. Ajouter un import est un changement d’une ligne dont le sens (« le domaine dépend maintenant du mécanisme de livraison ») n’existe qu’au niveau du graphe entier. Supprimer une ligne d’enregistrement est un changement d’une ligne dont le sens (« ce handler est maintenant injoignable ») est une absence visible nulle part dans le diff. Le relecteur aurait besoin du graphe de dépendances entier, de la table de câblage entière et de la matrice du protocole entière dans sa tête pour voir ce que le diff a fait au système. Aucun relecteur n’a cela, et à l’ère des changements produits par des agents, aucun relecteur n’a non plus le temps de faire semblant.
C’est la division du travail vers laquelle toute la série construit. Le diff répond à « qu’est-ce qui a changé ». L’architecture appliquée répond à « qu’est-ce que le changement a fait au système » : quelles frontières il a franchies, quelles obligations il a remplies ou rompues, quelles cellules de quel protocole ont changé de sens. La revue humaine est alors libérée pour faire la seule chose qu’aucune des deux boucles ne peut faire : juger si le changement est une bonne idée.
Ce que l’application coûte, et où elle déraille
Section honnêteté, parce que cette idée échoue de façons connues.
Les vieilles bases de code sont pleines de violations, et une règle qui échoue sur toutes sera désactivée d’ici vendredi. Le motif qui marche est le cliquet : enregistrer les violations existantes dans une , les accepter, et n’échouer que sur les nouvelles. La vieille dérive est une dette à rembourser délibérément ; la nouvelle dérive est bloquée à la porte. Les outils, dans tous les écosystèmes, convergent vers cette forme parce que rien d’autre ne survit au contact d’une vraie base de code.
Les règles qui encodent un goût corrodent la confiance dans les règles qui encodent une structure. Chaque faux positif dépense de la crédibilité. Un jeu de règles gagne le droit de bloquer des commits en ayant raison sur des choses qui comptent, et « compter » a un test : cette règle protège-t-elle une frontière, un contrat, un invariant, une responsabilité ou un protocole ? Si elle protège une préférence, elle peut être une convention de formatage, mais elle ne devrait pas faire échouer un build. Le vérificateur qui crie au loup entraîne l’équipe à sortir la dérogation, et la dérogation, une fois habituelle, avale aussi les vraies violations.
Chaque exception doit rester visible. Les vrais systèmes ont besoin de portes de sortie ; l’architecture qui n’admet aucune exception est un fantasme. Mais une exception invisible est une dette architecturale dont on a détruit les reçus. La forme qui marche est l’exemption annotée, datée, motivée, que l’audit rapporte comme une exemption, de sorte que la liste des endroits où l’architecture est suspendue soit elle-même un artefact inspectable.
L’application peut figer une architecture fausse. Les vérifications appliquent le modèle déclaré, et le modèle déclaré peut être faux, ou le devenir quand le produit change. L’application ne remplace pas le jugement selon lequel le modèle mérite d’être appliqué ; elle le présuppose. Quand le modèle doit changer, les règles doivent changer avec lui, délibérément, dans un commit relisible. Ce n’est pas une faiblesse de l’approche. Une architecture visible, versionnée, contestable est précisément ce qui rend le changement délibéré possible ; on ne peut pas renégocier une règle que personne ne voit.
Et un audit parfait qui n’est jamais livré ne protège rien. Une règle approximative utile aujourd’hui vaut mieux qu’un système de règles complet l’année prochaine. Commencez par les deux ou trois frontières dont l’érosion ferait le plus mal, appliquez celles-là, et faites grandir le jeu de règles comme l’architecture a grandi : progressivement, sous feedback.
Les agents héritent de l’architecture que vous avez appliquée, pas de celle que vous vouliez
L’argument multi-intelligences traverse cette série, et ici il devient tranchant.
Un développeur humain absorbe l’architecture déclarée par des canaux qu’un agent n’a pas : la session d’accueil, la correction dans le couloir, le souvenir de l’incident qui a motivé la frontière. Un agent qui travaille dans votre base de code a le code, les types, les vérifications qui échouent, et les documents qui se trouvent être dans son contexte. Pour un agent, la partie non appliquée de votre architecture pourrait aussi bien ne pas exister, et les agents produisent vite du code plausible, ce qui veut dire qu’ils produisent vite des violations plausibles. Un agent ajoutera volontiers l’import manquant qui fait passer les tests, droit à travers une frontière dont il n’a aucun moyen de savoir qu’elle est sacrée.
Mais retournez le même fait. Un agent prend une règle appliquée plus au sérieux que la plupart des humains, en un sens mécanique : il ne peut pas discuter avec la porte, et une vérification rouge avec un diagnostic qui nomme le concept et le chemin de correction est exactement le feedback qu’un agent convertit en commit corrigé. L’architecture appliquée n’est pas seulement une protection contre les agents. C’est l’interface par laquelle on peut leur donner l’architecture tout court : l’intention, compilée sous une forme qui survit au contact d’un producteur qui n’était pas dans la pièce.
Les équipes qui tireront un levier des agents de code ne seront pas celles qui ont les meilleurs schémas. Ce seront celles dont les schémas sont exécutables.
Conclusion
Le premier article soutenait que le code est un système, pas de la prose. Le deuxième, que ce qui porte le système doit être encodé, pas sous-entendu. Le troisième, que la structure explicite est ce qui permet au feedback d’attribuer les échecs au lieu de seulement les détecter. Cet article referme la boucle sur les trois : l’architecture elle-même, la plus grande structure de toutes, doit être tenue par la même discipline. Déclarée, encodée, puis appliquée, parce qu’une règle que rien ne vérifie est un sous-entendu, et le deuxième article a déjà dit ce qui arrive aux sous-entendus.
Une architecture qui vit dans des documents est une croyance. Une architecture qui vit dans des règles exécutables est une propriété. Les croyances s’érodent un commit raisonnable à la fois ; les propriétés échouent bruyamment et se corrigent.
Ne demandez pas si l’équipe croit encore à l’architecture. Demandez ce qui passerait au rouge si elle était trahie aujourd’hui.
Et remarquez ce que les meilleures règles de cet article avaient en commun : la frontière qui ne pouvait pas être exprimée, l’état qui ne pouvait pas rester non classé, la matrice qui ne pouvait pas être partielle. Elles ne détectaient pas des violations. Elles rendaient les violations impossibles à écrire. C’est un geste plus fort que vérifier, et la série y arrivera. Mais il y a une étape avant, et c’est l’inconfortable. Tout ce qui est appliqué ici cherche une présence interdite, et une vérification qui n’en trouve aucune n’a rien dit du tout de ce qui devrait être là. Avant de rendre les violations inécrivables, un standard doit prouver la présence, et dire à voix haute ce qu’il n’a pas regardé. C’est le prochain article.
Pour aller plus loin
Les idées d’ici ont de longues lignées, et les originaux valent votre temps.
- Gail Murphy, David Notkin et Kevin Sullivan. Software Reflexion Models : le travail fondateur sur la comparaison entre architecture déclarée et réalité extraite, y compris la distinction divergence/absence sur laquelle cet article s’appuie.
- Neal Ford, Rebecca Parsons et Patrick Kua. Building Evolutionary Architectures : la source du terme fonction de fitness architecturale et du vocabulaire mécanique (atomique, déclenchée, statique) utilisé ici.
- ArchUnit (JVM) et dependency-cruiser (JavaScript) : des outils mûrs et pratiques pour exprimer des règles de frontières et de dépendances comme des tests.
- Le motif baseline/cliquet, tel que réalisé dans des outils comme Betterer, les baselines de lint et le « Clean as You Code » de SonarQube : le modèle d’adoption qui rend l’application supportable dans une base de code existante.
- David Harel. Statecharts: A Visual Formalism for Complex Systems : l’arrière-plan profond pour traiter l’espace complet état-événement d’un protocole comme un artefact de premier rang.
Une remarque après lecture ?
Si vous souhaitez envoyer un mot au sujet de cet article, vous pouvez écrire ici. Je partage ici parce que le sujet m’intéresse et que je veux apprendre des autres. Merci pour vos retours, surtout lorsqu’ils sont formulés avec soin.