Cours Clean Code Complet ✨
Les 12 chapitres essentiels — nommage, fonctions, refactoring, tests et principes de code maintenable
🏠 Hub Programmation
🏗️ POO
🔀 Git & GitHub
🐍 Python
🔷 TypeScript
Pourquoi le clean code
Le clean code n'est pas un luxe — c'est une nécessité économique. Un développeur passe 70% de son temps à lire du code existant et seulement 30% à en écrire. Du code illisible ralentit toute l'équipe. La « dette technique » s'accumule : chaque raccourci aujourd'hui coûte 10× plus cher à corriger demain. Le clean code, c'est du code que n'importe quel développeur de l'équipe peut comprendre, modifier et étendre sans risque de tout casser. Ce n'est pas du code « joli » — c'est du code professionnel.
| Code sale | Clean code |
|---|---|
| On comprend en 30 minutes | On comprend en 30 secondes |
| Modifier une feature = peur de tout casser | Modifier = confiance (tests + clarté) |
| Un seul dev comprend le code | Toute l'équipe peut contribuer |
| Bugs cachés partout | Comportement prévisible |
| Ralentit de plus en plus | Vélocité constante |
Nommage
d = 86400
def calc(a, b): …
lst = get_data()
for i in lst:
if i[4] == 1: …
# ✅ Noms qui racontent une histoire
SECONDS_PER_DAY = 86400
def calculate_shipping_cost(weight, distance): …
users = get_active_users()
for user in users:
if user.is_active: …
# 1. Variables = noms (ce que c'est)
user_count = 42 # ✅ Nom descriptif
is_admin = True # ✅ Booléen = is_/has_/can_/should_
max_retries = 3 # ✅ Avec contexte
# 2. Fonctions = verbes (ce que ça fait)
def get_user_by_id(id): … # ✅ get/set/create/delete/validate/send
def is_valid_email(e): … # ✅ Booléen retourné = is_/has_/can_
def calculate_total(c): … # ✅ Verbe clair
# 3. Classes = noms (ce que c'est)
class UserRepository: … # ✅ PascalCase
class PaymentProcessor: … # ✅ Rôle clair
# 4. Constantes = UPPER_SNAKE_CASE
MAX_FILE_SIZE = 5_000_000 # ✅
API_BASE_URL = « https://… » # ✅
# 5. Éviter les abréviations sauf universelles
# ✅ OK : url, id, html, css, api, db, http, json
# ❌ NON : usr, cnt, mgr, proc, tmp, val, btn
Un bon nom élimine le besoin de commentaire. Si vous devez écrire un commentaire pour expliquer ce que fait une variable, c'est que le nom est mauvais. Testez : est-ce qu'un collègue qui lit le code pour la première fois comprend immédiatement ? Sinon, renommez.
Fonctions
def process_order(order):
# Valider…
# Calculer le total…
# Appliquer les réductions…
# Vérifier le stock…
# Envoyer un email…
# Mettre à jour la BDD…
# Générer la facture…
# 200 lignes de code
# ✅ Fonctions petites et composables
def process_order(order: Order) -> Receipt:
validate_order(order)
total = calculate_total(order)
total = apply_discounts(total, order.coupons)
check_stock(order.items)
charge_payment(order.payment, total)
send_confirmation(order.user.email)
return generate_receipt(order, total)
# Chaque sous-fonction fait UNE chose, 5-15 lignes max
def calculate_total(order: Order) -> float:
return sum(item.price * item.quantity for item in order.items)
def apply_discounts(total: float, coupons: list[Coupon]) -> float:
for coupon in coupons:
total *= (1 – coupon.discount)
return round(total, 2)
# 1. Maximum 3 paramètres (idéal : 0-2)
# ❌
def create_user(name, email, age, role, team, dept, mgr): …
# ✅ Regrouper dans un objet
@dataclass
class UserInput:
name: str; email: str; age: int
role: str = « user »
def create_user(data: UserInput): …
# 2. Pas d'effets de bord surprenants
# ❌ get_user() qui modifie la BDD en secret
# ✅ Le nom dit EXACTEMENT ce que ça fait
# 3. Retourner tôt (early return)
# ❌ Imbrication profonde
def process(user):
if user:
if user.is_active:
if user.has_permission:
# code…
# ✅ Guard clauses (retour anticipé)
def process(user):
if not user: return
if not user.is_active: return
if not user.has_permission: return
# code principal — au même niveau
Formatage et lisibilité
def getData(x,y):
result=x+y
if result>0:
data=fetch(result)
if data: return process(data)
else: return None
return None
# ✅ Propre, aéré, structuré
def get_combined_data(base_value: int, offset: int) -> Data | None:
total = base_value + offset
if total <= 0:
return None
raw_data = fetch_data(total)
if not raw_data:
return None
return process_data(raw_data)
| Outil | Langage | Ce qu'il fait |
|---|---|---|
| Black | Python | Formatteur automatique (opinionné, 0 config) |
| Ruff | Python | Linter ultra-rapide (remplace flake8+isort+pylint) |
| Prettier | JS/TS | Formatteur automatique (opinionné) |
| ESLint | JS/TS | Linter (erreurs + style) |
| mypy | Python | Type checker statique |
Automatisez le formatage — ne perdez plus jamais de temps en débat de style. Configurez Black + Ruff (Python) ou Prettier + ESLint (TypeScript) dans votre IDE. Format on save. Tout le monde dans l'équipe a le même style. Les code reviews se concentrent sur la logique, pas les espaces.
DRY, KISS, YAGNI
# ❌ Code dupliqué
def create_admin(name, email):
validate_email(email)
user = User(name, email, role=« admin »)
db.save(user)
send_welcome_email(email)
def create_editor(name, email):
validate_email(email)
user = User(name, email, role=« editor »)
db.save(user)
send_welcome_email(email)
# ✅ Une seule fonction paramétrable
def create_user(name: str, email: str, role: str = « user ») -> User:
validate_email(email)
user = User(name, email, role=role)
db.save(user)
send_welcome_email(email)
return user
# ❌ Trop malin
result = (
reduce(lambda a, b: a + b,
map(lambda x: x[« price »] * x[« qty »],
filter(lambda x: x[« active »], items)))
)
# ✅ Simple et lisible
total = sum(
item[« price »] * item[« qty »]
for item in items
if item[« active »]
)
# ❌ « Au cas où on en aura besoin un jour »
class UserService:
def export_to_csv(self): …
def export_to_xml(self): …
def export_to_pdf(self): …
def export_to_excel(self): …
# Personne n'utilise aucun de ces exports
# ✅ Implémenter quand c'est nécessaire
class UserService:
def export_to_csv(self): … # Seul format demandé
DRY ≠ « jamais dupliquer une seule ligne ». Si deux morceaux de code se ressemblent mais changent pour des raisons différentes, la duplication est acceptable. DRY concerne la duplication de connaissance/logique, pas la duplication de syntaxe. Fusionner du code qui n'a pas la même raison de changer crée un couplage artificiel.
Structures de code
# app.py → models + routes + services + utils
# ✅ Un fichier = un module = une responsabilité
# project/
# models/
# user.py → Modèle User
# order.py → Modèle Order
# services/
# user_service.py → Logique métier User
# order_service.py → Logique métier Order
# repositories/
# user_repo.py → Accès BDD User
# routes/
# user_routes.py → Endpoints API User
# tests/
# test_user.py
# test_order.py
// src/
// users/
// user.model.ts
// user.service.ts
// user.controller.ts
// user.repository.ts
// user.test.ts
// orders/
// order.model.ts
// order.service.ts
// …
// shared/
// database.ts
// logger.ts
Deux approches : par couche (models/, services/, routes/) ou par feature (users/, orders/). Par feature est souvent meilleur car tout ce qui concerne « users » est au même endroit. Règle : un fichier ne devrait pas dépasser 200-300 lignes. Si c'est plus → découper.
Gestion des erreurs
try:
data = fetch_data()
result = process(data)
except: # Attrape TOUT — même KeyboardInterrupt
pass # Ignore silencieusement — bug invisible
# ✅ Exceptions spécifiques, messages clairs
try:
user = get_user_by_id(user_id)
except UserNotFoundError as e:
logger.warning(f »User not found »)
raise HTTPException(status_code=404, detail=str(e))
# Créer des exceptions métier
class InsufficientStockError(Exception):
def __init__(self, product: str, requested: int, available: int):
self.product = product
super().__init__(
f » : demandés, disponibles »
)
# Valider tôt (fail fast)
def create_order(items: list, payment: Payment) -> Order:
if not items:
raise ValueError(« La commande doit contenir au moins un article »)
if not payment.is_valid():
raise PaymentError(« Moyen de paiement invalide »)
# … logique métier
Règle d'or : ne jamais avaler une exception silencieusement. except: pass est le pire pattern — les bugs deviennent invisibles. Attrapez des exceptions spécifiques, loggez-les, et propagez celles que vous ne savez pas gérer. Retourner None au lieu de lever une exception est aussi dangereux — ça pousse l'erreur plus loin dans le code.
Tests
import pytest
def test_calculate_total_with_discount():
# Arrange — préparer les données
cart = Cart()
cart.add(Product(« Laptop », 1000), qty=1)
cart.add(Product(« Mouse », 50), qty=2)
coupon = Coupon(discount=0.10) # -10%
# Act — exécuter l'action
total = cart.calculate_total(coupon)
# Assert — vérifier le résultat
assert total == 990.0 # (1000 + 100) * 0.90
def test_withdraw_insufficient_funds_raises():
account = BankAccount(« Alice », balance=100)
with pytest.raises(ValueError, match=« insuffisant »):
account.withdraw(500)
def test_create_user_with_invalid_email():
with pytest.raises(ValidationError):
create_user(name=« Alice », email=« pas-un-email »)
| Type de test | Quoi | Vitesse | Quantité |
|---|---|---|---|
| Unitaire | Une fonction / classe isolée | 🟢 Millisecondes | 70% des tests |
| Intégration | Plusieurs composants ensemble (API + BDD) | 🟡 Secondes | 20% des tests |
| E2E | L'app complète (navigateur, API) | 🔴 Minutes | 10% des tests |
Nommez vos tests comme des phrases : test_withdraw_insufficient_funds_raises dit exactement ce qui est testé et le résultat attendu. Un test qui échoue doit immédiatement indiquer ce qui est cassé, sans besoin de lire le code du test.
Commentaires
i += 1 # Incrémenter i de 1
user = get_user(id) # Récupérer l'utilisateur
# ❌ Commentaire qui masque du mauvais code
# Vérifier si l'utilisateur est éligible à la réduction
if u.a > 18 and u.t == « p » and u.d > 365: …
# ✅ Le code s'explique lui-même
if user.is_eligible_for_discount(): …
# ✅ Commentaire utile : POURQUOI, pas QUOI
# Stripe exige un montant en centimes, pas en euros
amount_cents = int(total * 100)
# ✅ Commentaire utile : avertissement
# ATTENTION : cet endpoint est rate-limité à 100 req/min par Stripe
# ✅ TODO/FIXME acceptables (temporaires)
# TODO: migrer vers le nouveau SDK Stripe v12
# FIXME: race condition si 2 paiements simultanés
Le code devrait se lire comme de la prose bien écrite. Si vous avez besoin d'un commentaire pour expliquer CE QUE fait le code → renommez les variables et fonctions. Réservez les commentaires pour expliquer POURQUOI une décision a été prise (contrainte externe, bug connu, choix technique). Les docstrings pour les APIs publiques sont utiles — les commentaires ligne par ligne sont du bruit.
Refactoring
# SMELL 1 : Magic numbers
# ❌
if age > 18 and score > 750:
approve_loan()
# ✅
LEGAL_AGE = 18
MIN_CREDIT_SCORE = 750
if age > LEGAL_AGE and score > MIN_CREDIT_SCORE:
approve_loan()
# SMELL 2 : Condition complexe → extraire en fonction
# ❌
if (user.age >= 18 and user.kyc_verified and
user.country in ALLOWED_COUNTRIES and
not user.is_banned and user.credit_score > 600):
# ✅
if user.is_eligible_for_trading():
# SMELL 3 : Nested if → guard clauses (vu ch.3)
# SMELL 4 : Long parameter list → objet
# SMELL 5 : God class → découper en services
# SMELL 6 : Feature envy → déplacer la méthode
# SMELL 7 : Primitive obsession → value objects
| Code smell | Refactoring |
|---|---|
| Magic numbers (42, 86400) | Extraire en constante nommée |
| Condition complexe | Extraire en méthode (is_eligible) |
| Duplication | Extraire en fonction commune |
| Fonction trop longue (> 20 lignes) | Découper en sous-fonctions |
| Classe trop grosse (> 200 lignes) | Découper en services |
| Trop de paramètres (> 3) | Regrouper dans un objet / dataclass |
| Nested if profond | Guard clauses (early return) |
| Strings en dur (« admin ») | Enum ou constante |
Code reviews
| Vérifier | Question |
|---|---|
| Nommage | Les noms sont-ils clairs sans commentaire ? |
| Simplicité | Y a-t-il un moyen plus simple ? (KISS) |
| Responsabilité | Chaque fonction fait-elle une seule chose ? |
| Duplication | Y a-t-il du code copié-collé ? (DRY) |
| Erreurs | Les cas d'erreur sont-ils gérés ? |
| Tests | Les changements sont-ils testés ? |
| Sécurité | Injection SQL ? Données sensibles exposées ? |
| Performance | Boucles inutiles ? Requêtes N+1 ? |
# 1. Linter + formatter passent ?
ruff check .
black –check .
# 2. Tests passent ?
pytest
# 3. Types corrects ?
mypy .
# 4. Relire son propre diff
git diff –staged
# 5. Commit message clair ?
# ✅ « fix: prevent duplicate charges on retry »
# ❌ « fix stuff »
Convention de commits (Conventional Commits) : feat: nouvelle fonctionnalité, fix: correction de bug, refactor: restructuration sans changement de comportement, test: ajout de tests, docs: documentation, chore: maintenance. Permet de générer automatiquement les changelogs.
Bonnes pratiques
| Principe | En une phrase |
|---|---|
| DRY | Ne pas dupliquer la logique métier |
| KISS | La solution la plus simple qui marche |
| YAGNI | Ne pas coder ce dont on n'a pas besoin maintenant |
| SOLID | 5 principes de design orienté objet |
| Fail Fast | Valider et lever les erreurs le plus tôt possible |
| Boy Scout Rule | « Laisser le code plus propre qu'on ne l'a trouvé » |
| Principle of Least Surprise | Le code fait ce que son nom suggère |
| Separation of Concerns | Chaque module gère un seul aspect |
✅ À FAIRE
• Noms descriptifs (variables = noms, fonctions = verbes)
• Fonctions courtes (5-15 lignes, 1 responsabilité)
• Guard clauses (early return, pas de nesting)
• Type hints systématiques (Python, TypeScript)
• Tests avant de merger (pytest, jest)
• Formatter automatique (Black, Prettier)
• Linter dans le CI (Ruff, ESLint)
• Commits conventionnels (feat:, fix:, refactor:)
• Code review avant merge
• Refactorer continuellement (Boy Scout Rule)
❌ À ÉVITER
• Variables à 1 lettre (sauf i/j dans une boucle courte)
• Fonctions de 100+ lignes (découper)
• except: pass (erreurs silencieuses)
• Magic numbers (extraire en constantes)
• Commentaires qui paraphrasent le code
• Code mort (supprimer, Git le garde)
• Nesting profond (> 3 niveaux)
• print() au lieu de logging
• Ignorer les warnings du linter
• Optimiser avant que ça marche correctement
🏠 Hub Programmation
🏗️ Cours POO
🧠 Cours Algorithmes
🔀 Cours Git & GitHub
🐍 Cours Python
🔷 Cours TypeScript
Cours Clean Code Complet — Nommage, fonctions, tests, refactoring et principes de code maintenable
Référence : Refactoring Guru | Conventional Commits

