Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

11.1 — Bonnes pratiques

🎯 Objectif : écrire du code que toi dans 6 mois remerciera toi d’aujourd’hui. Et savoir reconnaître les conseils utiles des conseils dogmatiques.

À l'issue de cet axe, tu sauras :

  • Connaître les principes : Clean Code, SOLID, DRY, KISS, YAGNI, Rule of Three
  • Nommer correctement variables, fonctions, classes
  • Reconnaître les code smells courants
  • Donner et recevoir une revue de code constructive
  • Écrire des messages de commit selon Conventional Commits

Confirmé 10 min prérequis : axes 5-10 lus

La règle d’or — le code est lu 10× plus qu’il n’est écrit

Section intitulée « La règle d’or — le code est lu 10× plus qu’il n’est écrit »

Tu écris une fonction en 10 minutes. Toi-même la reliras des dizaines de fois. D’autres la reliront aussi. Optimise pour la lecture.

« Programs must be written for people to read, and only incidentally for machines to execute. » — Harold Abelson, SICP (1985)

// ❌
function fn(d: Date) {
const r = [];
for (const u of getUsers()) {
if (u.cd > d) r.push(u);
}
return r;
}
// ✅
function findUsersCreatedAfter(date: Date): User[] {
return getUsers().filter(user => user.createdAt > date);
}
TypeConventionExemple
Variable, fonctioncamelCaseuserCount, findActiveUsers
ConstanteSCREAMING_SNAKE_CASEMAX_RETRIES
Classe, type, interfacePascalCaseUserService, OrderStatus
Booléenpréfixe is/has/can/shouldisActive, hasPermission
Fonction asyncsuffixe pas obligatoire en TS, Async en C# / JavafetchUser
Privé (TS)#name ou _name#count, _internal
  • getUser → tu retournes un user, synchrone et certain.
  • findUser → tu cherches, peut-être pas trouvé (User | null).
  • loadUser → souvent async, charge depuis externe.
  • fetchUser → souvent async via réseau.

Un nom précis te dispense d’une demi-douzaine de commentaires.

« Functions should do one thing, do it well, do it only. » — Robert C. Martin

Indicateur pratique : si tu as besoin de scroller pour lire la fonction, elle est trop longue. Cible 15-30 lignes max. Pas un dogme, mais un signal.

// ❌ Mélange validation, logique métier, formatage
function processOrder(input: any) {
if (!input.email || !input.email.includes('@')) throw new Error();
const total = input.items.reduce((s, i) => s + i.price * i.qty, 0);
const discount = input.code === 'SUMMER' ? 0.1 : 0;
return {
customer: input.email.toLowerCase(),
total: total * (1 - discount),
formatted: `$${(total * (1 - discount)).toFixed(2)}`,
};
}
// ✅ Séparé par préoccupation
function processOrder(input: OrderInput): OrderResult {
validateOrder(input);
const total = computeTotal(input.items);
const discounted = applyDiscount(total, input.code);
return formatOrder(input.email, discounted);
}

Une fonction de haut niveau doit lire comme une table des matières : pas de détails techniques mélangés à la logique métier.

// ✅ Lisible comme une recette
async function publishArticle(article: Article) {
await validateArticle(article);
await saveToDatabase(article);
await notifySubscribers(article);
await invalidateCache(article.id);
}

Chaque fonction appelée descend ensuite à un niveau de détail plus bas.

5 principes nés en POO, applicables (avec souplesse) à la programmation moderne.

Une classe (ou module) a une seule raison de changer.

// ❌ La classe change si les règles métier OU le format JSON OU l'envoi email changent
class OrderManager {
validate(o: Order) { ... }
toJSON(o: Order) { ... }
sendConfirmationEmail(o: Order) { ... }
}
// ✅ Découpé
class OrderValidator { validate(o: Order) { ... } }
class OrderSerializer { toJSON(o: Order) { ... } }
class OrderEmailer { sendConfirmation(o: Order) { ... } }

Ouvert à l’extension, fermé à la modification. En pratique 2026 : les types unions et le pattern matching remplacent souvent les hiérarchies.

// ❌ Doit modifier la fonction pour ajouter un type
function area(shape: { kind: string; w?: number; h?: number; r?: number }) {
if (shape.kind === 'rect') return shape.w! * shape.h!;
if (shape.kind === 'circle') return Math.PI * shape.r! ** 2;
}
// ✅ Discriminated union — TypeScript force l'exhaustivité
type Shape =
| { kind: 'rect'; w: number; h: number }
| { kind: 'circle'; r: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'rect': return shape.w * shape.h;
case 'circle': return Math.PI * shape.r ** 2;
}
}

Une sous-classe doit pouvoir remplacer la classe parente sans casser le code. Le bug classique : Square extends Rectangle qui casse setWidth/setHeight.

En pratique : si une override surprend l’utilisateur du parent, c’est un L violation.

Plusieurs petites interfaces > une grosse interface.

// ❌ Le composant qui veut juste lire est forcé d'implémenter write
interface Storage { read(); write(); delete(); list(); }
// ✅ Tu déclares ce dont tu as besoin
interface Readable { read(); }
interface Writable { write(); }

Dépends d’abstractions, pas de classes concrètes. En TS : passe des types interfaces plutôt que des classes spécifiques.

// ❌ Couplage fort
class UserService {
private db = new PrismaClient();
}
// ✅ Inversion : on injecte
class UserService {
constructor(private db: UserRepository) {}
}

Bénéfice : tests faciles (mock du repo), changement de DB sans toucher au service.

Si tu copies-colles 3 fois → extrais une fonction. Mais attention :

// Ces deux fonctions partagent du code MAIS représentent des concepts DIFFÉRENTS
function formatInvoiceTotal(amount: number) {
return `Total: $${amount.toFixed(2)}`;
}
function formatProductPrice(amount: number) {
return `Price: $${amount.toFixed(2)}`;
}

Si demain l’invoice doit afficher avec virgule et le prix avec point, l’extraction t’aurait piégé. Wrong abstraction is more costly than duplication.

Préfère toujours la solution simple si elle suffit. Pas de framework abstrait pour 3 routes.

N’écris pas de code « au cas où ». Écris ce qu’aujourd’hui demande. Quand le besoin réel arrive, tu adaptes.

// ❌ YAGNI violation — tu n'as JAMAIS eu besoin de role
function createUser(email: string, role?: 'user' | 'admin' | 'super-admin' | 'moderator') {
// ...
}
// ✅ Quand tu auras 2 rôles, tu les ajouteras
function createUser(email: string) { ... }

Tu peux dupliquer 2 fois. À la 3e, tu extrais. Ça évite les abstractions prématurées.

SmellSymptômeRefactor
God classClasse de 1000 lignes qui fait toutDécouper par responsabilité
Long methodFonction de 100+ lignesExtraire des sous-fonctions
Long parameter listfn(a, b, c, d, e, f, g)Objet de paramètres
Primitive obsessionTout en string/number, perte de sensBranded types, value objects
Feature envyMéthode qui utilise plus l’autre classe que la sienneDéplacer la méthode
Switch/case géant12 cases avec logique différentePolymorphisme ou map de fonctions
Comments expliquant le code// incrémente le compteurRenommer la variable
Dead codeFonctions/variables jamais utiliséesSupprimer
Magic numbersif (x > 86400)Constante : SECONDS_PER_DAY
Shotgun surgeryUne modif demande de toucher 10 fichiersReorganiser, regrouper
  1. Sois rapide : reviews stagnantes = équipe bloquée. < 24 h idéal.
  2. Distingue préférence et exigence :
    • nit: (nitpick) — préférence personnelle, pas bloquant.
    • suggestion: — idée, pas obligatoire.
    • question: — j’ai pas compris, explique-moi.
    • blocking: — doit être corrigé avant merge.
  3. Sois précis et bienveillant : « Je préfère X parce que Y » > « non, fais X ».
  4. Approve si globalement OK, même avec des nits — le PR-author traitera.

Convention standard pour structurer les commentaires :

nit: utilise `const` au lieu de `let`
suggestion: extraire dans une fonction `formatMoney()`
question: pourquoi pas `Map` plutôt qu'objet ?
blocking: cette query est vulnérable à l'injection SQL
praise: belle simplification du flow !
  • Ne te défends pas automatiquement — la review est sur le code, pas sur toi.
  • Pose des questions si tu n’es pas d’accord plutôt que d’argumenter.
  • Refuse poliment quand tu sais ce que tu fais : « Je préfère garder X parce que Y ».

Rappel rapide :

feat(auth): add password reset flow
fix: prevent crash when user has no avatar
docs: update README with new env vars
refactor(orders): extract pricing logic
test(api): cover login error cases
chore: bump dependencies

Bénéfices :

  • Changelog auto-généré (changesets, semantic-release).
  • Versioning sémantique automatique (feat → minor, fix → patch, BREAKING CHANGE → major).
  • Historique grep-able.
Tu vois 3 fonctions qui partagent 5 lignes identiques. Ton réflexe ?
Tu vois `function fn(a, b, c, d, e, f) { ... }`. Smell ?
Tu commits "fix login bug". Avis ?
  • Clean Code — Robert C. Martin (à lire avec esprit critique)
  • The Pragmatic Programmer — Hunt & Thomas
  • A Philosophy of Software Design — John Ousterhout (le livre moderne, plus nuancé que Clean Code)
  • Refactoring — Martin Fowler (le catalogue des refactors)
  • Conventional Commentsconventionalcomments.org

Suite : 11.2 — Tests — pyramide, Vitest, Pest, Playwright.