Bonnes pratiques¶
DRY, KISS, YAGNI, composition, couplage — les heuristiques qui guident les décisions quotidiennes.
Ce que sont ces principes¶
Ces heuristiques ne sont pas des règles absolues. Ce sont des signaux d'alerte : quand votre code les viole, quelque chose merite attention. Ils s'appliquent avec du jugement, pas mecaniquement.
Des guides, pas des lois
Un principe appliqué aveuglément devient une contrainte. DRY mal compris produit des abstractions prematurees. KISS mal compris produit du code naif qui ne passe pas à l'échelle. Comprenez le pourquoi avant d'appliquer le quoi.
DRY — Don't Repeat Yourself¶
Chaque piece de connaissance doit avoir une représentation unique, non ambigue et autoritaire dans un système.
DRY ne signifie pas "ne jamais écrire deux fois le même code". Il signifie ne pas dupliquer la connaissance — c'est-a-dire la logique, la règle métier, la décision.
# Ces deux fonctions ressemblent au meme code MAIS
# elles changent pour des raisons differentes
def format_user_address(user) -> str:
return f"{user.street}, {user.city}"
def format_delivery_address(delivery) -> str:
return f"{delivery.street}, {delivery.city}"
# Les fusionner sous pretexte qu'elles se ressemblent
# va creer un couplage artificiel entre deux concepts distincts
Règle pratique : avant d'abstraire, demandez-vous si les deux occurrences changent pour les mêmes raisons. Si non, la duplication est acceptable.
KISS — Keep It Simple, Stupid¶
La solution la plus simple qui fonctionne est presque toujours la bonne.
La complexité est le principal ennemi de la maintenabilité. Elle se glisse dans le code sous forme de :
- Abstractions pour des cas qui n'existent pas encore
- Patterns appliques ou il n'y a qu'un seul cas
- Configurations génériques là où une valeur hardcodee suffirait
class GreetingStrategyFactory:
_registry: dict = {}
@classmethod
def register(cls, lang: str, strategy):
cls._registry[lang] = strategy
@classmethod
def create(cls, lang: str):
return cls._registry.get(lang, DefaultGreetingStrategy)()
class EnglishGreetingStrategy:
def greet(self, name: str) -> str:
return f"Hello, {name}"
KISS ne signifie pas "code naif". Un algorithme de tri efficace est complexe — mais c'est une complexité nécessaire. La complexité accidentelle (patterns inutiles, abstractions vides) est l'ennemie.
YAGNI — You Aren't Gonna Need It¶
N'implementez pas une fonctionnalité tant qu'elle n'est pas nécessaire.
YAGNI s'applique aux features speculatives et aux abstractions anticipees :
# YAGNI viole — "on en aura peut-etre besoin"
class UserRepository:
def find_by_id(self, id): ...
def find_by_email(self, email): ...
def find_by_username(self, username): ... # personne ne l'utilise encore
def find_by_phone(self, phone): ... # idem
def find_with_pagination(self, ...): ... # idem
def find_with_complex_filter(self, ...): ... # idem
# YAGNI respecte
class UserRepository:
def find_by_id(self, id): ...
def find_by_email(self, email): ...
# les autres methodes quand elles seront necessaires
Coût de YAGNI viole : maintenance de code mort, surface d'attaque plus grande, complexité cognitive, tests inutiles.
Composition vs héritage¶
L'héritage est souvent utilisé là où la composition serait meilleure.
Problème de l'héritage profond :
# Heritage : la sous-classe est liee a l'implementation du parent
class Animal:
def breathe(self): ...
class Dog(Animal):
def bark(self): ...
class PoliceDog(Dog): # prend TOUT de Dog et Animal
def search(self): ...
Modifier Animal peut casser PoliceDog sans relation directe. L'héritage dit "est-un" — utilisez-le seulement si la relation "est-un" est stable et réelle.
Composition : assembler des comportements par injection :
class Searchable:
def search(self, area): ...
class Trainable:
def train(self, command): ...
class Dog:
def __init__(self, name: str):
self.name = name
class PoliceDog:
def __init__(self, dog: Dog, search: Searchable):
self._dog = dog
self._search = search
def search_area(self, area):
return self._search.search(area)
Preferez la composition à l'héritage — Design Patterns, GoF
Couplage et cohesion¶
Deux métriques de qualité a garder en tete :
| Concept | Définition | Objectif |
|---|---|---|
| Couplage | Degré de dépendance entre deux modules | Faible (loose) |
| Cohesion | Degré ou les éléments d'un module vont ensemble | Forte (high) |
Faible couplage : modifier le module A ne force pas a modifier le module B.
Forte cohesion : tout ce qui est dans un module a une raison d'être la.
# Forte cohesion : tout ce qui concerne la commande est ici
class Order:
def add_item(self, ...): ...
def total(self): ...
def confirm(self): ...
def cancel(self): ...
# Faible couplage : Order ne connait pas EmailService
# Elle emet un evenement, EmailService ecoute
class Order:
def confirm(self):
self.status = "confirmed"
return OrderConfirmed(order_id=self.id) # pas d'import EmailService
Tableau recapitulatif¶
| Principe | En une ligne | Signal de violation |
|---|---|---|
| DRY | Une connaissance = une source de vérité | Modifier une règle nécessité N changements |
| KISS | La solution la plus simple qui marche | Abstractions sans cas d'usage réel |
| YAGNI | N'implementez que ce qui est nécessaire | Méthodes/classes non utilisées |
| Composition | Assembler plutôt qu'hériter | Héritage > 2 niveaux de profondeur |
| Cohesion | Un module = une responsabilité claire | "Et aussi" dans la description d'une classe |
| Couplage | Les modules changent independamment | Modification en cascade sur plusieurs modules |