Cours Clean Code Complet ✨

Les 12 chapitres essentiels — nommage, fonctions, refactoring, tests et principes de code maintenable

12
Chapitres
100+
Exemples
Python + TS
Langages
A1→C2
Niveaux

CHAPITRE 01

Pourquoi le clean code

✨ Le code est lu 10× plus qu'il n'est écrit

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

CHAPITRE 02

Nommage

🏷️ La compétence la plus sous-estimée
# ❌ Noms cryptiques
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: …

# Règles de nommage

# 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.

CHAPITRE 03

Fonctions

⚙️ Petites, focalisées, un seul rôle
# ❌ Fonction géante qui fait tout
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)

# Règles pour les fonctions

# 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

CHAPITRE 04

Formatage et lisibilité

📐 Style cohérent = lisibilité
# ❌ Inconsistant, dense, illisible
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.

CHAPITRE 05

DRY, KISS, YAGNI

🎯 Les 3 principes fondamentaux
# DRY — Don't Repeat Yourself

# ❌ 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

# KISS — Keep It Simple, Stupid

# ❌ 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 »] )

# YAGNI — You Ain't Gonna Need It

# ❌ « 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.

CHAPITRE 06

Structures de code

📁 Organisation des fichiers et modules
# ❌ Un fichier de 2000 lignes qui fait tout
# 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

// TypeScript — organisation par feature
// 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.

CHAPITRE 07

Gestion des erreurs

🚨 Erreurs propres et prévisibles
# ❌ Attraper tout, ignorer les 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.

CHAPITRE 08

Tests

🧪 Code sans tests = code qui va casser
# Structure d'un test : Arrange → Act → Assert
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.

CHAPITRE 09

Commentaires

💬 Le meilleur commentaire est celui qui n'a pas besoin d'exister
# ❌ Commentaire qui paraphrase le code
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.

CHAPITRE 10

Refactoring

🔄 Améliorer sans changer le comportement
# Code smells → 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

CHAPITRE 11

Code reviews

👀 Relire le code des autres (et le sien)
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 ?
# Checklist avant de push

# 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.

CHAPITRE 12

Bonnes pratiques

📚 Résumé des principes
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
✅ Bonnes pratiques

✅ À 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

🧠 Quiz
Quand un commentaire est-il utile vs nuisible ?
Utile : expliquer POURQUOI une décision a été prise (contrainte Stripe, workaround pour un bug connu, choix d'algorithme non-évident). Docstrings pour les APIs publiques. TODO/FIXME temporaires. Nuisible : paraphraser CE QUE fait le code (i += 1 # incrémenter), commentaire qui compense du mauvais nommage, commentaires obsolètes qui contredisent le code. Si le code a besoin d'un commentaire « quoi » → renommez plutôt.
DRY vs YAGNI — ils se contredisent parfois ?
Oui. DRY dit « ne duplique pas » → tentation de créer une abstraction commune. YAGNI dit « n'abstrait pas avant d'en avoir besoin ». La Rule of Three résout le conflit : acceptez la duplication la 1ère et 2ème fois. À la 3ème occurrence, refactorez. Une abstraction prématurée est pire que la duplication — elle crée du couplage entre des choses qui n'ont peut-être rien en commun.
C'est quoi la Boy Scout Rule ?
« Laissez le code plus propre que vous ne l'avez trouvé. » Chaque fois que vous touchez un fichier, améliorez un petit truc : renommez une variable floue, extrayez un magic number, supprimez du code mort. Pas un gros refactoring — juste une petite amélioration. Sur 100 commits, le code s'améliore continuellement au lieu de se dégrader. C'est plus efficace qu'un « grand nettoyage » qui n'arrive jamais.

Cours Clean Code Complet — Nommage, fonctions, tests, refactoring et principes de code maintenable

Référence : Refactoring Guru | Conventional Commits