low-class-cohesion¶
Catégorie : Conception de classes
Sévérité : Configurable par seuil
Déclenché par : pyscn analyze, pyscn check
Ce que fait cette règle¶
Signale les classes dont les méthodes ne partagent pas d'état d'instance (la métrique LCOM4 — Lack of Cohesion of Methods, version 4). pyscn construit un graphe dans lequel deux méthodes sont connectées si elles touchent un attribut self. commun, puis compte les composantes connexes. LCOM4 = 1 signifie que chaque méthode est liée à toutes les autres ; LCOM4 = N signifie que la classe est en réalité N sous-classes indépendantes collées ensemble.
Les méthodes décorées avec @staticmethod ou @classmethod ne référencent pas self et sont exclues du graphe.
En clair : cette classe accomplit des tâches sans rapport — scindez-la, ou faites-en un module de fonctions.
Pourquoi est-ce un problème ?¶
Une classe est censée regrouper un état avec les opérations qui agissent dessus. Lorsque les méthodes ne touchent pas le même état :
- Le nom de la classe ment — elle prétend être une chose mais se comporte comme deux ou trois.
- Les changements se dispersent — un bogue dans une responsabilité ne peut être trouvé qu'en lisant du code qui n'a rien à voir avec lui.
- La réutilisation est bloquée — vous ne pouvez pas extraire la partie dont vous avez besoin sans entraîner tout le reste.
- Cela précède souvent une vraie abstraction — la classe « Utilities » ou « Manager » est un symptôme classique.
Exemple¶
class UserUtility:
def __init__(self, db, smtp, clock):
self.db = db
self.smtp = smtp
self.clock = clock
self.cache = {}
# --- persistence ---
def load(self, user_id):
if user_id in self.cache:
return self.cache[user_id]
row = self.db.fetch("users", user_id)
self.cache[user_id] = row
return row
def save(self, user):
self.db.upsert("users", user)
self.cache[user.id] = user
# --- email ---
def send_welcome(self, address):
self.smtp.send(address, "Welcome")
def send_reset(self, address, token):
self.smtp.send(address, f"Reset: {token}")
# --- formatting ---
def format_joined_at(self, user):
return self.clock.format(user.joined_at)
LCOM4 = 3 : {load, save} partagent db et cache, {send_welcome, send_reset} partagent smtp, {format_joined_at} est isolé. Trois composantes, une seule classe.
À utiliser à la place¶
Scindez en classes cohésives et déplacez la partie sans état vers des fonctions libres.
class UserRepository:
def __init__(self, db):
self._db = db
self._cache = {}
def load(self, user_id):
if user_id in self._cache:
return self._cache[user_id]
row = self._db.fetch("users", user_id)
self._cache[user_id] = row
return row
def save(self, user):
self._db.upsert("users", user)
self._cache[user.id] = user
class UserMailer:
def __init__(self, smtp):
self._smtp = smtp
def send_welcome(self, address):
self._smtp.send(address, "Welcome")
def send_reset(self, address, token):
self._smtp.send(address, f"Reset: {token}")
# user_formatting.py — no class, no state
def format_joined_at(user, clock):
return clock.format(user.joined_at)
Chaque classe a maintenant LCOM4 = 1, et le formateur est une fonction d'une ligne, là où il doit être.
Options¶
| Option | Défaut | Description |
|---|---|---|
lcom.low_threshold |
2 |
À ce seuil ou en dessous, la classe est signalée comme à faible risque. |
lcom.medium_threshold |
5 |
Au-dessus de ce seuil, la classe est à risque élevé. |
Références¶
- Hitz, M. & Montazeri, B. Chidamber and Kemerer's Metrics Suite: A Measurement Theory Perspective. IEEE TSE, 1996 (définition de LCOM4).
- Implémentation :
internal/analyzer/lcom.go. - Catalogue des règles · high-class-coupling