Le couplage est souvent traité comme une notion vague : “c’est trop couplé” ne dit rien sur ce qu’il faut changer. La connascence propose un vocabulaire précis pour nommer les différentes formes de couplage, les comparer, et décider lesquelles réduire en priorité.
Le concept est documenté en détail sur connascence.io.
Trois axes pour évaluer une connascence
Chaque instance de connascence s’analyse selon trois axes :
- Force : plus une connascence est forte, plus elle est difficile à détecter et à refactoriser.
- Degré : une entité couplée à des centaines d’autres est plus problématique qu’une entité couplée à deux.
- Localité : deux composants proches (même classe, même module) peuvent se permettre des formes plus fortes. À distance, ces mêmes formes deviennent dangereuses.
Les 9 types de connascence
Connascence de nom (CoN)
Plusieurs composants s’accordent sur le nom d’une entité. C’est la forme la plus faible et la plus inévitable.
class PaymentService:
def charge(self, amount: int) -> None:
...
service = PaymentService()
service.charge(100) # dépend du nom "charge"
Renommer charge casse tous les appelants. C’est facile à corriger, mais le risque existe à grande échelle.
Connascence de type (CoT)
Plusieurs composants s’accordent sur le type d’une entité.
def send_invoice(amount: int) -> None:
...
send_invoice("100") # erreur à l'exécution : str au lieu de int
Python ne détecte pas ces erreurs à la compilation, mais mypy ou Pyright le font.
Connascence de sens (CoM)
Plusieurs composants s’accordent sur la signification de valeurs spécifiques. Les valeurs magiques sont le symptôme classique.
# Fragile : que signifie 1 ?
def get_card_type(card_number: str) -> int:
if card_number.startswith("4"):
return 1
# Mieux : nommer les constantes
VISA = 1
MASTERCARD = 2
def get_card_type(card_number: str) -> int:
if card_number.startswith("4"):
return VISA
Passer des valeurs magiques à des constantes nommées améliore la forme de couplage (CoM vers CoN).
Connascence de position (CoP)
Plusieurs composants s’accordent sur l’ordre des valeurs.
# Fragile : l'ordre des arguments compte
def create_user(first_name, last_name, year_of_birth, is_admin):
...
create_user("Thomas", "Richards", 1984, True)
# Mieux : dataclass
from dataclasses import dataclass
@dataclass
class UserData:
first_name: str
last_name: str
year_of_birth: int
is_admin: bool = False
Un dataclass élimine le risque de confusion liée à l’ordre des arguments.
Connascence d’algorithme (CoA)
Plusieurs composants s’accordent sur un algorithme particulier. Si deux côtés doivent implémenter le même calcul, un changement dans l’un doit être répliqué dans l’autre.
import hmac
import hashlib
def sign_payload(data: str, secret: str) -> str:
return hmac.new(secret.encode(), data.encode(), hashlib.sha256).hexdigest()
def verify_payload(data: str, secret: str, signature: str) -> bool:
expected = hmac.new(secret.encode(), data.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Si on change l’algorithme de signature dans sign_payload, verify_payload doit être mis à jour en même temps.
Connascence d’exécution (CoE)
L’ordre d’exécution entre composants est important.
import threading
lock = threading.Lock()
lock.acquire()
try:
process_critical_section()
finally:
lock.release()
Un context manager (with lock:) encapsule cet ordre et réduit le risque d’oubli.
Connascence de timing (CoT)
Le moment de l’exécution est important. Typique dans les systèmes concurrents.
import threading
import time
result = None
def fetch_data():
global result
time.sleep(0.5)
result = {"status": "ok"}
t = threading.Thread(target=fetch_data)
t.start()
# Race condition : result peut encore être None ici
print(result)
Connascence de valeur (CoV)
Plusieurs valeurs doivent changer ensemble. Fréquent entre code de production et tests.
class Article:
DRAFT = "draft"
PUBLISHED = "published"
def __init__(self, content: str):
self.content = content
self.status = self.DRAFT
# Test couplé à la valeur initiale
def test_initial_status():
article = Article("contenu")
# Référencer Article.DRAFT plutôt que la chaîne brute réduit ce couplage
assert article.status == Article.DRAFT
Connascence d’identité (CoI)
Plusieurs composants doivent référencer la même instance d’un objet.
shared_cache = {}
def writer(cache: dict):
cache["key"] = "value"
def reader(cache: dict):
return cache.get("key")
writer(shared_cache)
assert reader(shared_cache) == "value"
C’est la forme la plus forte. Si les deux fonctions n’opèrent pas sur le même objet, le comportement est imprévisible.
La règle pratique
La connascence n’impose pas d’éliminer tout couplage. Elle aide à arbitrer : une CoA dans une même classe est acceptable, une CoV entre deux services distants est un signal d’alarme.
La règle : plus la distance entre composants est grande, plus la forme de connascence doit être faible. Réduire la force ou rapprocher les composants concernés, c’est améliorer concrètement la maintenabilité du code.
Pour explorer tous les types en détail : connascence.io.
