Read in English · Leer en español · Auf Deutsch lesen
Section 3.6 sur 3.8 · Intermédiaire · 22 min de lecture
La connascence : une mesure plus fine du couplage
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Le couplage vous dit que deux parties sont reliées. La connascence vous dit comment, à quel point c’est risqué, et quelle refactorisation rendrait les choses plus sûres. Dans cette leçon, vous apprendrez ses formes, et les trois façons de les juger.
Quand deux amis conviennent de se retrouver au même endroit et à la même heure que d’habitude, ils sont liés par un accord. Personne d’autre ne peut le voir. Et si l’un d’eux change ses plans, l’autre doit le savoir. Le logiciel est plein d’accords de ce genre : des noms, l’ordre des valeurs, ce qu’un nombre signifie en secret, l’étape qui doit avoir lieu en premier. La connascence est une façon de les recenser, de juger du risque de chacun, et de rendre les plus risqués plus faibles, plus visibles ou plus rapprochés.
Le mot vient du latin et signifie « nés ensemble » ; Meilir Page-Jones l’a introduit en 1992. Deux éléments sont connascents si une modification de l’un exige une modification de l’autre pour que le système reste correct. Les éléments peuvent être n’importe quoi : une fonction et ses appelants, un producteur et les consommateurs de ses messages, une colonne de base de données et chaque rapport qui la lit.
Les formes statiques peuvent être repérées en lisant le code source. La plus faible est la connascence de nom : un appelant et une fonction s’accordent sur le nom de celle-ci. Le renommage est mécanique, et les outils trouvent chaque utilisation. Connascence de type : les deux côtés s’accordent sur un type, comme des frais de livraison en pence entiers. Les vérificateurs de types et les schémas la rendent visible. Connascence de signification : les deux côtés s’accordent sur ce que signifie une valeur, comme le statut quatre qui veut dire livreur affecté. Les valeurs magiques de ce genre sont invisibles pour les outils. Connascence de position : l’ordre des valeurs compte, comme pour les arguments d’une fonction ou les colonnes d’un fichier. Et connascence d’algorithme : les deux côtés doivent calculer quelque chose de la même façon, comme une même somme de contrôle.
Les formes dynamiques n’apparaissent que lorsque le système s’exécute : elles sont donc plus difficiles à repérer et généralement plus fortes. Connascence d’exécution : l’ordre des étapes compte. Le paiement doit être autorisé avant que le restaurant soit notifié. Connascence de timing : le moment où les choses se produisent compte. Une app de livreur qui abandonne au bout de cinq secondes, alors que l’affectation prend jusqu’à huit secondes, échouera. Connascence de valeur : plusieurs valeurs doivent changer ensemble. Si le frontend et le backend détiennent chacun la liste des zones de livraison, les deux listes doivent toujours concorder. Et connascence d’identité : les éléments doivent faire référence exactement à la même chose, comme deux modules qui écrivent dans une même transaction de base de données.
Chaque occurrence peut être jugée de trois façons. La force mesure la difficulté à trouver et à modifier la forme. Le nom est faible, la signification est plus forte, et le timing et l’identité sont les plus fortes. La localité mesure la proximité des éléments. Des arguments positionnels entre deux fonctions utilitaires privées d’un même fichier sont sans danger. Des champs positionnels dans un message échangé entre équipes sont dangereux. Le degré mesure le nombre d’éléments qui la partagent. Si le code de statut trois apparaît à vingt-huit endroits détenus par trois équipes, changer sa signification implique vingt-huit modifications coordonnées. Cela donne trois actions. Affaiblir la forme, par exemple en remplaçant une valeur magique par une constante nommée. Rapprocher les éléments fortement liés. Ou faire passer une convention partagée par une seule définition, pour réduire son degré.
Cette grille rassemble le tout. Les formes vont de la plus faible à gauche à la plus forte à droite, et la distance augmente de haut en bas. Au sein d’une même fonction, toutes les formes sont acceptables, car la personne qui modifie un côté voit l’autre. Entre services, seuls le nom et le type sont sains : ce sont les accords qu’un schéma publié rend explicites. Tout ce qui est plus fort est en corail : refactorisez vers une forme plus faible, ou rapprochez les éléments. Et si deux services partagent une connascence de valeur ou d’identité, la frontière est probablement au mauvais endroit. L’un devrait détenir les données, ou les deux devraient ne former qu’un seul service.
Voici un exemple commenté tiré d’une application de VTC. Sa fonction de réservation a grossi jusqu’à huit arguments positionnels : un passager, quatre coordonnées, un nombre, true et zéro. Intervertissez les coordonnées de prise en charge et de dépose, et la vérification de types passe toujours, puis le chauffeur est envoyé silencieusement au mauvais endroit. C’est une connascence de position. Deux signifie un grand véhicule, true signifie payer par carte, et zéro signifie réserver maintenant. C’est une connascence de signification. Et il y a trente et un sites d’appel répartis dans trois composants. La refactorisation se fait par étapes. Introduisez un type de localisation et des arguments nommés. Remplacez les valeurs magiques par des valeurs nommées. Puis regroupez le tout dans un seul objet de demande de réservation. Le comportement n’a pas changé. Mais des accords forts et invisibles sont devenus des accords faibles de nom et de type, que le vérificateur de types et l’éditeur peuvent vérifier.
La section complète contient un tableau des neuf formes, avec des façons d’affaiblir chacune, et examine de plus près la connascence entre services. Ensuite, dans la section trois point sept, nous nous intéresserons à la loi de Conway. À très vite.
Ce qu’il faut savoir
- Deux éléments sont connascents si une modification de l’un exige une modification de l’autre pour que le système reste correct ; Meilir Page-Jones a introduit cette idée en 1992.
- Les formes statiques (nom, type, signification, position, algorithme) sont visibles dans le code source ; les formes dynamiques (exécution, timing, valeur, identité) n’apparaissent qu’à l’exécution et sont généralement plus fortes.
- La force mesure la difficulté à trouver et à modifier une forme, la localité la distance entre les éléments, et le degré le nombre d’éléments qui partagent l’accord.
- Convertissez les formes fortes en formes plus faibles, gardez locale toute connascence forte, et réduisez le degré en faisant passer une convention par une seule définition ou un seul propriétaire.
- Remplacer les paramètres positionnels par des paramètres nommés, et les valeurs magiques par des énumérations nommées, transforme des accords forts et invisibles en accords faibles que les outils peuvent vérifier.
- Entre services, seules les formes faibles, comme le nom et le type dans un schéma publié, sont saines ; une connascence de valeur ou d’identité entre services signale une frontière mal placée.
Pourquoi le couplage a besoin d’un vocabulaire plus fin
La section 3.2 a décrit le couplage à l’aide des niveaux de la conception structurée (contenu, commun, externe, contrôle, estampille, données) et des formes distribuées qui comptent entre services. Ces niveaux classent la manière dont les modules sont reliés. Ils en disent moins sur deux questions que les ingénieurs se posent chaque jour : à quel point un changement sera-t-il douloureux, et quelle refactorisation réduirait la douleur ? La connascence répond aux deux.
La connascence (du latin, « né ensemble ») a été introduite par Meilir Page-Jones dans un article de 1992 des Communications of the ACM, « Comparing techniques by means of encapsulation and connascence », puis développée dans son livre What Every Programmer Should Know About Object-Oriented Design (Dorset House, 1995), remanié plus tard sous le titre Fundamentals of Object-Oriented Design in UML (1999). Sa définition est simple : deux éléments sont connascents si une modification de l’un exigerait une modification de l’autre pour que le système reste correct. Les éléments peuvent être n’importe quoi : deux fonctions, une fonction et ses appelants, un producteur et les consommateurs de ses messages, une colonne de base de données et chaque rapport qui la lit.
Deux caractéristiques rendent l’idée utile aux architectes. D’abord, la connascence se présente sous des formes nommées que l’on peut classer par force, si bien que l’on peut dire non seulement « ces éléments sont couplés », mais « ils sont couplés par position, ce qui est pire qu’un couplage par nom, et voici comment convertir l’un en l’autre ». Ensuite, elle possède deux dimensions supplémentaires, la localité et le degré, qui indiquent si une forme donnée est acceptable là où elle se trouve.
Formes statiques : visibles dans le code
La connascence statique peut être repérée en lisant le code source, souvent à l’aide d’outils, sans exécuter le programme. De la plus faible à la plus forte, dans l’ordre le plus souvent donné :
Connascence de nom : les éléments doivent s’accorder sur un nom. Un appelant utilise calculate_tax ; un consommateur JSON lit customer_id. C’est la forme la plus faible, car le renommage est mécanique et les outils trouvent chaque utilisation.
Connascence de type : les éléments doivent s’accorder sur un type. Une fonction attend un nombre entier de pence ; lui passer un flottant fonctionne en Python mais corrompt le résultat. Les vérificateurs de types et les schémas la rendent visible.
Connascence de signification (ou de convention) : les éléments doivent s’accorder sur ce que signifie une valeur. Le statut 3 signifie « expédié » ; -1 signifie « introuvable » ; une chaîne vide signifie « utiliser la valeur par défaut ». De telles valeurs magiques sont invisibles pour les outils : rien ne relie le 3 du code de l’entrepôt au 3 du service d’e-mail.
Connascence de position : les éléments doivent s’accorder sur l’ordre des valeurs. create_user("Ann", "Smith", "ann@example.com") dépend de l’ordre des paramètres ; un fichier CSV dépend de l’ordre des colonnes ; un tuple (lat, lon) casse silencieusement si quelqu’un suppose (lon, lat), comme le fait GeoJSON.
Connascence d’algorithme : les éléments doivent s’accorder sur un algorithme. L’émetteur et le récepteur d’un webhook doivent calculer la même signature HMAC ; un client et un serveur doivent appliquer le même schéma de hachage des mots de passe ou la même somme de contrôle ; chaque service qui route par client doit utiliser la même fonction de hachage pour choisir un shard.
Formes dynamiques : visibles seulement à l’exécution
La connascence dynamique dépend du comportement du système à l’exécution : elle est donc plus difficile à repérer et généralement plus forte.
Connascence d’exécution : l’ordre d’exécution compte. Une connexion doit être ouverte avant l’envoi des données ; begin() doit précéder commit() ; une commande doit être créée avant que son paiement soit encaissé.
Connascence de timing : le moment de l’exécution compte. Le délai d’expiration d’un client doit être plus long que le temps de traitement normal du serveur ; un test qui attend deux secondes espère qu’une tâche asynchrone est terminée ; deux threads se font la course pour mettre à jour un solde.
Connascence de valeur : plusieurs valeurs doivent changer ensemble, généralement à cause d’un invariant. La liste des codes de pays valides dans le frontend web et dans le backend doit concorder ; le total d’une commande doit être égal à la somme de ses lignes ; les débits et les crédits d’une écriture comptable doivent s’équilibrer.
Connascence d’identité : les éléments doivent faire référence à la même entité, et pas seulement à une entité égale. Deux dépôts doivent partager la même transaction de base de données pour que leurs écritures soient atomiques ; deux composants doivent publier dans la même instance de file d’attente, et non dans deux files ayant la même configuration.
Page-Jones a aussi nommé la relation opposée, la contranascence : des éléments qui doivent différer, comme deux classes d’un même espace de noms qui ne doivent pas porter le même nom, ou deux services qui ne doivent pas utiliser le même port. Elle obéit aux mêmes règles.
Les neuf formes, avec un exemple de chacune
| Forme | Nature | Exemple dans une application de livraison de repas | Façon habituelle de l’affaiblir |
|---|---|---|---|
| Nom | Statique | Le service des commandes appelle restaurant.get_menu() | Déjà la forme la plus faible ; gardez des noms parlants |
| Type | Statique | Frais de livraison transmis en pence entiers, et non en livres sous forme de flottant | Annotations de type, schémas, un type Money |
| Signification | Statique | status == 4 signifie « livreur affecté » | Constantes nommées ou énumération, partagées via une seule définition |
| Position | Statique | quote(2.5, 51.5, -0.12, True) | Arguments nommés, objet paramètre, champs JSON nommés |
| Algorithme | Statique | L’app et le serveur calculent la même somme de contrôle du panier | Une bibliothèque partagée unique, ou laisser un seul côté calculer et envoyer le résultat |
| Exécution | Dynamique | Le paiement doit être autorisé avant que le restaurant soit notifié | Encapsuler la séquence en un seul endroit (un workflow ou une saga) |
| Timing | Dynamique | L’app du livreur abandonne au bout de 5 s alors que l’affectation prend jusqu’à 8 s | Rendre l’interaction asynchrone ; dériver les délais d’expiration des SLO |
| Valeur | Dynamique | Le frontend et le backend détiennent chacun la liste des zones de livraison | Un seul propriétaire des données ; les autres l’interrogent ou s’y abonnent |
| Identité | Dynamique | Deux modules doivent écrire dans la même transaction de base de données | Confier à un seul module la responsabilité de l’unité de travail |
Force, localité et degré
Chaque occurrence de connascence peut être jugée sur trois propriétés. La force mesure la difficulté à découvrir et à modifier la forme : le nom est faible, car les outils le trouvent et le renomment ; la signification est plus forte, car rien ne signale les valeurs magiques ; le timing et l’identité sont les plus fortes, car elles peuvent n’apparaître que sous charge ou en production. L’ordre du tableau est le classement conventionnel, même si les auteurs divergent légèrement sur les formes statiques (certains placent la position sous la signification).
La localité mesure la proximité des éléments connascents. Des paramètres positionnels entre deux fonctions utilitaires privées d’un même fichier sont sans danger : la personne qui modifie l’une voit l’autre. Des champs positionnels dans un message échangé entre services détenus par des équipes différentes sont dangereux, car les personnes qui doivent changer ensemble ne travaillent pas ensemble, ne publient pas ensemble et peuvent même ignorer l’existence les unes des autres.
Le degré mesure le nombre d’éléments concernés. Un nombre magique partagé par deux fonctions est une gêne ; le même nombre utilisé par quarante fichiers dans six services est un projet de migration. Le degré peut se compter : si le code de statut 3 apparaît à 14 endroits dans le service d’entrepôt, à 9 dans le service de notifications et à 5 dans les vues de la base de données de reporting, le degré est de 28, et changer sa signification implique de coordonner 28 modifications entre trois équipes et au moins deux déploiements, avec une migration de données pour les vues.
La force doit diminuer quand la distance augmente
Les règles
Les conseils de Page-Jones peuvent se résumer en trois énoncés : minimiser la connascence globale en découpant un système en éléments encapsulés ; minimiser la connascence qui franchit les frontières d’encapsulation ; et maximiser la connascence à l’intérieur de ces frontières. Le dernier surprend, mais ce n’est que la cohésion reformulée : ce qui doit changer ensemble doit vivre ensemble.
Jim Weirich, développeur Ruby surtout connu comme l’auteur de l’outil de build Rake, a fait redécouvrir l’idée à une nouvelle génération dans des conférences données à partir de 2009, dont « The Building Blocks of Modularity » (présentée lors de plusieurs conférences Ruby en 2009, parmi lesquelles MountainWest RubyConf), qu’il appelait en plaisantant une grande théorie unifiée du développement logiciel, et « Connascence Examined ». Il a condensé la pratique en deux règles. La règle de localité : plus deux éléments sont éloignés, plus la connascence entre eux doit être faible. La règle du degré : convertissez les formes fortes de connascence en formes plus faibles, surtout lorsque de nombreux éléments sont concernés.
En pratique, cela donne trois actions, à peu près par ordre de préférence :
Affaiblir la forme. Remplacer la signification par le nom (une constante nommée), la position par le nom (arguments nommés, champs nommés), l’algorithme par le nom (une seule fonction partagée que les deux côtés appellent), l’exécution par l’encapsulation (une seule méthode qui effectue les étapes dans le bon ordre).
Augmenter la localité. Si deux éléments doivent partager une forme forte, rapprochez-les : dans la même classe, le même module, le même composant ou le même service, détenu par la même équipe.
Réduire le degré. Si de nombreux éléments partagent une convention, faites-les passer par une seule définition, un seul propriétaire ou un seul adaptateur, afin que seuls quelques endroits connaissent le détail.
Exemple commenté
Exemple commenté : remplacer des paramètres positionnels et des valeurs magiques
Une application de VTC possède une fonction utilisée par l’API de réservation, le planificateur et un outil d’administration. Sa signature a grossi en deux ans : book(rider_id, 51.501, -0.142, 51.515, -0.072, 2, True, 0). Analysez la connascence et refactorisez.
| Position | Huit arguments positionnels. Intervertir les coordonnées de prise en charge et de dépose passe toujours la vérification de types et envoie silencieusement le chauffeur au mauvais endroit. |
|---|---|
| Signification | 2 est la catégorie de véhicule (1 standard, 2 grand, 3 accessible) ; True signifie « payer par carte » ; 0 signifie « réserver maintenant », tandis que tout autre nombre est une heure programmée en secondes depuis l’epoch. |
| Degré | Une recherche trouve 31 sites d’appel dans trois composants : chaque nouveau paramètre implique donc 31 modifications à une position fixe. |
| Étape 1 : le nom plutôt que la position | Introduire un type Location et des arguments uniquement nommés, afin que les appelants écrivent pickup=Location(lat=51.501, lon=-0.142). |
| Étape 2 : le nom plutôt que la signification | Remplacer 2 par VehicleClass.LARGE, True par Payment.CARD, et 0 par when=None, qui signifie maintenant. |
| Étape 3 : réduire le degré | Regrouper les arguments dans un objet BookingRequest ; ajouter plus tard un champ facultatif ne touche que le code qui en a besoin. |
| Résultat | La connascence de position et de signification (forte, degré 31) devient une connascence de nom et de type (faible), que le vérificateur de types et l’IDE peuvent vérifier et renommer automatiquement. |
Rien n’a changé dans le comportement ; la refactorisation a seulement changé les accords que les appelants doivent respecter et la visibilité de ces accords.
Effectuez le changement par étapes qui laissent fonctionner les anciens appelants, par exemple en acceptant les deux formes pendant une version et en journalisant l’utilisation de l’ancienne.
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class VehicleClass(Enum):
STANDARD = "standard"
LARGE = "large"
ACCESSIBLE = "accessible"
class Payment(Enum):
CARD = "card"
CASH = "cash"
@dataclass(frozen=True, kw_only=True)
class Location:
lat: float
lon: float
@dataclass(frozen=True, kw_only=True)
class BookingRequest:
rider_id: str
pickup: Location
dropoff: Location
vehicle: VehicleClass = VehicleClass.STANDARD
payment: Payment = Payment.CARD
when: datetime | None = None # None means now
def book(request: BookingRequest) -> str: ...La connascence à travers les frontières des services
Mark Richards et Neal Ford, dans Fundamentals of Software Architecture (2020 ; seconde édition 2025), présentent la connascence, aux côtés de la cohésion et du couplage, comme l’une des mesures fondamentales de la modularité, et soutiennent qu’elle est aussi utile entre services qu’au sein du code. Lisez les formes en pensant à un système distribué : chacune correspond à un problème familier.
Le nom et le type entre services sont les accords qu’un schéma publié rend explicites : noms et types des champs dans un document OpenAPI, un schéma Avro ou Protobuf, ou un catalogue d’événements. Ce sont les formes que vous voulez, et les tests de contrat peuvent les vérifier.
La signification entre services, c’est le code de statut partagé ou l’indicateur inexpliqué : ce que la section 3.2 appelait le couplage sémantique. Préférez les valeurs auto-descriptives ("status": "rider_assigned") aux nombres, et les unités dans les noms de champs (fee_pence, timeout_ms) aux suppositions. Les unités méritent une attention particulière : la sonde Mars Climate Orbiter de la NASA a été perdue en 1999 parce que le logiciel au sol d’une équipe produisait des valeurs d’impulsion en livres-force secondes, alors que le logiciel de navigation qui les consommait attendait des newtons-secondes : une connascence de signification entre organisations que personne n’avait rendue explicite.
La position entre services apparaît dans les échanges de fichiers à largeur fixe ou CSV et dans les formats de messages positionnels ; les champs nommés la suppriment. L’algorithme apparaît lorsque deux services doivent calculer indépendamment le même prix, la même remise ou le même hachage ; le remède consiste généralement à faire calculer la valeur par un seul service, qui envoie le résultat.
L’exécution et le timing entre services sont les problèmes difficiles des systèmes distribués, et le timing est la connascence qui se cache derrière le couplage temporel de la section 3.2 : un consommateur qui suppose que l’écriture en base du producteur est visible avant l’arrivée de l’événement, une chaîne d’appels synchrones dont les délais d’expiration ne sont pas correctement imbriqués, un workflow qui ne fonctionne que si les messages arrivent dans l’ordre. Rendre les interactions asynchrones, idempotentes et tolérantes au désordre affaiblit ces formes ; des patrons comme les sagas et l’outbox (chapitre 6) existent en grande partie pour les gérer.
La valeur et l’identité entre services sont les signes d’une frontière mal placée. Si deux services doivent garder une valeur cohérente (le total de la commande dans le service des commandes et le total de la facture dans la facturation), ou doivent agir sur la même ligne dans la même transaction, alors soit un service doit détenir ces données et l’autre les lui demander, soit les deux services devraient probablement n’en faire qu’un. Une connascence forte à travers un réseau, c’est la définition même d’un monolithe distribué.
Vérifiez votre compréhension
Une fonction prend (width, height) et un appelant passe (height, width). Quelle forme de connascence a été rompue, et quelle est la façon la plus simple de l’affaiblir ?
La connascence de position. Utilisez des arguments nommés ou un objet paramètre, afin que l’accord devienne un accord de nom.
Pourquoi la connascence de signification est-elle plus forte que la connascence de nom ?
Les outils et les lecteurs peuvent trouver chaque utilisation d’un nom, mais rien ne relie les utilisations séparées d’une valeur magique comme 3 : les modifications sont donc faciles à manquer.
Deux services doivent toujours mettre à jour la limite de crédit d’un client dans la même transaction. Qu’est-ce que cela suggère ?
Une forte connascence d’identité ou de valeur à travers une frontière réseau : les données ont probablement besoin d’un propriétaire unique, ou les deux services vont ensemble.
Pourquoi une connascence forte au sein d’une petite fonction peut-elle être parfaitement acceptable ?
Parce que la localité est élevée : la personne qui modifie un élément voit immédiatement l’autre, si bien que le coût et le risque de les garder cohérents sont minimes.
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 : « Nos services de commandes et de facturation calculent tous deux le total de la commande, et il arrive qu’ils diffèrent d’un penny. Comment décrire ce couplage en termes de connascence, et que devrions-nous changer ? »