Bibliothèque · La synthèse

Le grand livre du développeur

La moelle de dizaines de livres techniques, tirée en un seul fil : du courant dans le silicium jusqu'au jugement qu'aucune IA ne remplace. Chaque idée renvoie à sa fiche complète.

FR EN

On apprend à coder par morceaux : un langage cette année, un pattern le mois suivant, une astuce de perf un jeudi soir. Le savoir s'empile, mais le plan d'ensemble reste flou, et on finit par prendre des décisions sans trop voir d'où elles viennent. Cette page tente l'inverse : tracer une seule carte mentale du métier, qui va du courant dans le silicium jusqu'au jugement humain qu'aucune IA ne remplace. Pas une liste de résumés, mais le fil unique qui les traverse tous. Chaque chapitre est la conséquence du précédent, et chaque idée est racontée une seule fois, à sa place logique, en fondant ensemble tous les livres qui l'enseignent. Lisez de haut en bas, et le métier s'assemble.

La promesse est simple. Que vous débutiez ou que vous codiez depuis des années, vous repartez avec une carte à plusieurs étages qui répond aux questions que vous vous posez vraiment : non seulement quoi faire, mais pourquoi, , quand et comment le faire.

1

La machine ne comprend que des nombres

le coût

La machine ne pense pas : des nombres entrent, des nombres sortent. Un texte, une image, une condition, tout devient des nombres, et chaque calcul a un coût. La même tâche peut être instantanée ou ramer pendant des secondes, selon la façon dont vous l'écrivez. Ce chapitre vous apprend à voir ce coût avant de le payer. Quatre marches : la plus petite case (le bit), les étages de la mémoire, l'unité de mesure du coût (le Big O), et le traducteur qui s'occupe du reste.

1.1 Tout est nombre

Petit avertissement : c'est le passage le plus bas-niveau du livre, et c'est voulu. Ne cherchez pas à retenir les bits par cœur, visez l'intuition : comprendre pourquoi la machine ne sait stocker que des 0 et des 1. Le socle posé, tout le reste du livre en découle.

Un bit, c'est une case qui vaut 0 ou 1, comme un interrupteur éteint ou allumé : la seule chose que la machine sait physiquement retenir. On les groupe par huit (un octet : huit cases à deux choix chacune, soit 2×2×…×2 = 256 combinaisons, de 0 à 255), et la mémoire n'est qu'une immense rangée de ces octets, chacun repéré par un numéro, son adresse.

Tout s'y ramène : une lettre tient dans un octet (l'ASCII, une convention partagée par toutes les machines, fixe que 'A' vaut 65), un mot en occupe quelques-uns, une image des millions, car chaque pixel est trois nombres (rouge, vert, bleu). Les millions d'octets d'une photo n'ont rien de magique : juste énormément de petites cases de 0 et de 1.

Un octet par lettre, c'était vrai pour l'anglais. Nos alphabets accentués et les emojis ont forcé une convention plus large, l'UTF-8 : 'A' garde son octet, 'é' en prend deux, '🎉' quatre. D'où un piège bien réel : la « longueur » d'un texte dépend de ce qu'on compte, des octets ou des caractères, et couper au n-ième octet peut trancher un caractère en deux.

Pourquoi deux valeurs seulement ? Ça paraît primitif. Parce que le monde physique est bruité. Une tension dans un fil dérive avec la chaleur, les parasites, l'âge des composants : distinguer dix niveaux (un par chiffre décimal) demanderait une précision intenable. Deux états, eux, sont sans ambiguïté, « éteint » ou « allumé ». Il faut un bruit énorme pour confondre un 0 et un 1.

Le bit est donc le minimum d'information qui survit au bruit, porté par le composant le plus simple et le moins cher à produire en milliards d'exemplaires : l'interrupteur. Et comme deux états collent à vrai/faux, toute la logique et l'arithmétique se posent par-dessus. Loin d'être simpliste, c'est le compromis le plus robuste qu'on connaisse. On a d'ailleurs essayé autre chose : l'ENIAC de 1945 comptait en décimal, le soviétique Setun de 1958 en ternaire. Le binaire a gagné sur la fiabilité.

Reste à savoir comment une suite de chiffres devient un nombre. Tout tient dans la position. Partons de la base 10, celle qu'on connaît. 234, ce n'est pas « 2, 3, 4 » collés : chaque colonne porte un poids, les unités, les dizaines, les centaines.

Après la virgule, on continue vers le bas : les dixièmes (1/10), les centièmes (1/100)… 0.1 signifie donc « 1 dans la colonne des dixièmes ». Ce n'est pas le point qui fait la valeur, c'est la colonne.

Le binaire suit exactement la même règle, mais avec deux chiffres seulement, et chaque colonne vaut une puissance de 2 : 1, 2, 4, 8, 16… Ainsi 101, c'est 4 + 0 + 1 = 5.

Un entier est ainsi une somme exacte de puissances de 2 : il se range toujours parfaitement. Après la virgule, les colonnes binaires deviennent des fractions : 1/2, 1/4, 1/8, 1/16… Un nombre à virgule doit s'écrire comme une somme de celles-là.

0.1 n'y arrive pas non plus. En binaire, une fraction ne tombe juste que si son dénominateur (le bas de la fraction) est une puissance de 2 : 1/2, 1/4, 1/8… Or 0.1 = 1/10, et 10 n'en est pas une. Il cache un facteur 5 (10 = 2 × 5), et 5 n'apparaît jamais quand on double : 2, 4, 8, 16… Du coup, aucune somme finie de 1/2, 1/4, 1/8 ne fait exactement 0.1, et l'écriture part en boucle infinie (0.0001100110011…). Exactement comme 1/3 = 0.333… ne tombe jamais juste en base 10.

Comment la ranger, alors ? Pas avec une virgule à position fixe, mais en notation scientifique binaire : comme on écrit 6,02 × 10²³ en base 10, la machine écrit le nombre comme une mantisse (les chiffres significatifs) fois 2 à un certain exposant (qui dit où tombe la virgule). C'est le sens de « virgule flottante » : l'exposant fait flotter la virgule, ce qui permet de coder l'immense comme l'infime.

Les bits exacts de 0.1, pour les curieux (facultatif)

Pour 0.1, lisons les trois morceaux. Le signe d'abord : 0, car 0.1 est positif (1 = négatif). La mantisse ensuite : on reprend 0.0001100110011… et on glisse la virgule jusqu'au premier 1, ce qui donne 1,100110011… × 2⁻⁴, dont les chiffres répètent 1001 sans fin. Un nombre ainsi normalisé commence toujours par « 1, », donc ce 1 de tête n'est jamais stocké : un bit gratuit. L'exposant enfin, -4. Les 11 bits qui le stockent ne savent coder que des nombres positifs (0 à 2047), or -4 est négatif. La parade : on lui ajoute un décalage fixe de 1023, ce qui le rend toujours positif. La machine range donc −4 + 1023 = 1019, écrit en binaire 01111111011 (et resoustrait 1023 à la relecture pour retrouver -4).

Mais la mantisse est finie : 52 bits. On coupe le motif infini, et le dernier groupe est arrondi vers le haut (…1001 → …1010). C'est ça, la miette : 0.1 stocké vaut un cheveu de trop. 0.2 subit le même sort, les miettes s'ajoutent, et le total manque 0.3 :

// ce que la machine stocke vraiment (la « miette ») :
0.1         0.1000000000000000055…   // un peu trop grand
0.2         0.2000000000000000111…   // un peu trop grand
0.3         0.2999999999999999888…   // un peu trop petit

0.1 + 0.2   0.3000000000000000444…   // dépasse le 0.3 stocké
0.1 + 0.2 === 0.3   // false → 0.30000000000000004
0.1 + 0.3 === 0.4   // true… mais par chance !
0.1 + 0.7 === 0.8   // false, comme 0.1 + 0.2

Tout est là : 0.1 et 0.2 penchent un peu trop haut, 0.3 un peu trop bas. La somme des deux dépasse le 0.3 rangé en mémoire. Les deux ne sont pas le même nombre, donc l'égalité est fausse.

Ce ratage n'est pas un accident isolé. L'égalité entre deux flottants est une loterie : selon que les miettes s'ajoutent ou se compensent, l'arrondi final tombe juste ou rate, sans qu'on puisse le deviner. 0.1 + 0.3 donne bien 0.4, mais par chance. 0.1 + 0.7, lui, manque 0.8.

La clé tient en une phrase : un entier est exact, un flottant ne l'est pas. D'où deux réflexes : on ne compare jamais deux flottants avec ==, on teste plutôt s'ils sont assez proches (leur écart sous un petit seuil de tolérance). Et un montant d'argent se range en centimes, des entiers. Le calcul redevient fiable, et toute une classe de bugs d'arrondi disparaît.

Write Great Code, vol. 1·Eloquent JavaScript

1.2 La mémoire est une pyramide

Jusqu'ici, la mémoire n'était qu'une rangée d'octets uniforme. En vrai, elle a des étages. Pourquoi ? Parce que le processeur (la puce qui exécute vos instructions une par une) ne calcule vite que sur des données tout près de lui. Le plus près, ce sont ses registres, une poignée de cases au cœur de la puce : les seules où il calcule. Tout le reste vit plus loin, par paliers : le cache (une réserve rapide collée à la puce, en L1, L2, L3), puis la RAM, puis le disque. Chaque palier qu'on descend est environ dix fois plus grand, mais dix fois plus lent. Sauf le dernier saut : entre la RAM et le disque, ce n'est plus un palier, c'est un gouffre (×100 000).

Les ordres de grandeur donnent le vertige : registre quasi instantané, cache ≈ 1 ns, RAM ≈ 100 ns, disque ≈ 10 ms. Pour saisir l'écart, imaginez qu'une lecture en cache prenne 1 seconde : la RAM répondrait alors en ~1 min 40, et le disque dur… en ~4 mois. Une donnée qu'on relit souvent doit donc rester le plus haut possible.

Autre réflexe : la machine ne va jamais chercher un seul octet, elle tire toute une ligne de cache (environ 64 octets) d'un coup. Lire des cases voisines est donc gratuit. Sauter partout coûte cher.

Le cas d'école, c'est le tableau 2D. La mémoire, elle, reste une seule longue rangée d'octets. Un tableau à deux dimensions est donc aplati dans cette rangée : ses lignes posées bout à bout, l'une après l'autre. Résultat : a[i][j] n'est pas un double accès, mais une seule position calculée, i × largeur + j.

De là, deux parcours aux coûts opposés. Parcourir une ligne (j avance), c'est aller de case en case voisine : chacune est déjà dans les 64 octets ramenés par le cache, donc gratuit. Parcourir une colonne (i avance), c'est sauter une largeur entière de tableau à chaque pas : on sort du cache à tous les coups et il faut recharger, donc lent.

(Ça suppose un vrai tableau contigu, comme en C, Go ou NumPy. Un tableau de tableaux (le [[…]] de JS, le int[][] de Java) range chaque ligne ailleurs, allouée à part : là, la contiguïté et le gain de cache tombent.)

Même tableau, même calcul, juste l'ordre des deux boucles qui change :

// même logique, deux ordres de boucles sur un tableau 2D
for (i...) for (j...) a[i][j]   // ✓ contigu : la ligne de cache resert
for (j...) for (i...) a[i][j]   // ✗ saute une ligne à chaque fois → jusqu'à 10× plus lent

Côté dev, c'est le piège de tous les jours : « je charge tout en mémoire ». Ramener 100 000 lignes d'une base pour n'en afficher que dix, c'est gonfler les données bien au-delà du cache : chaque accès repart alors les chercher loin, en RAM ou sur le disque. La performance s'écroule, pour la même raison de fond que le parcours en colonne : déplacer des données coûte plus cher que calculer.

Write Great Code, vol. 1·vol. 2

1.3 Le coût a une unité : le Big O

Écrire du code qui tourne, c'est bien. Savoir s'il tiendra quand la base passe de cent à dix millions de lignes, c'est mieux. Il faut pour ça une unité de mesure : le Big O. Il dit comment le nombre d'étapes grandit avec la taille de l'entrée, notée n, en ignorant les constantes (la machine, le langage). Quatre classes reviennent partout :

  • O(1) : temps constant, quelle que soit la taille (le plus rapide) ;
  • O(log n) : la recherche binaire coupe le tas en deux à chaque étape ;
  • O(n) : un seul passage sur les données ;
  • O(n²) : tous les couples deux à deux, ça explose vite (le pire ici).

Le O(log n), lui, paraît abstrait jusqu'au premier exemple : trouver un nom dans un annuaire d'un milliard d'entrées triées ne coûte qu'une trentaine d'étapes, parce que couper un milliard en deux, trente fois de suite, tombe à 1. C'est pour ça que sa courbe s'aplatit : doubler les données n'ajoute qu'une étape.

Le O(1) est l'idée la plus rentable du quotidien : une table de hachage (le dict Python, l'objet JS, l'array associatif PHP) retrouve une valeur par sa clé en temps constant, même sur un million d'entrées. C'est pourquoi on ne fait jamais if (nom in liste_de_1000) (O(n) : au pire, on parcourt toute la liste) mais if (dictionnaire[nom]) (O(1) : un seul accès direct).

L'écart se paie en vrai. L'exemple le plus parlant : un ordinateur des années 1970, des milliers de fois plus lent que le vôtre, mais qui utilise un bon algorithme (O(n)), bat une machine moderne ultra-rapide qui en utilise un mauvais (O(n³)), dès que les données grossissent. La leçon : aucune puissance de calcul ne rattrape un mauvais algorithme.

Une habitude qui paie : avant d'écrire une ligne, estimez à la louche le nombre d'opérations. Sur dix millions d'éléments, un seul passage (O(n)) fait dix millions d'opérations, soit une fraction de seconde. Les comparer deux à deux (O(n²)) en fait dix millions × dix millions = cent mille milliards, des heures de calcul. Dix millions de fois plus : ce petit calcul, fait en trente secondes, vous dit lequel est jouable avant même de coder.

La même logique guide le choix de la structure de données. Un tableau range ses éléments côte à côte : atteindre le n-ième est instantané (on calcule son adresse), mais insérer au milieu oblige à décaler tous les suivants d'une case. Une liste chaînée relie chaque élément au suivant par un pointeur : insérer ne coûte que de rebrancher deux liens, mais pour trouver le n-ième il faut suivre la chaîne depuis le début. Aucun n'est « meilleur » : on choisit selon ce qu'on fait le plus, lire ou insérer.

Grokking Algorithms·Programming Pearls

1.4 Penser la machine, écrire haut niveau

On code aujourd'hui en langage clair (PHP, JavaScript, Python…), loin du langage brut de la machine. Entre les deux, un traducteur : le compilateur, qui transforme tout le code en instructions pour la puce avant l'exécution, ou l'interpréteur, qui traduit au fil de l'exécution, comme PHP et Python (JavaScript mélange les deux). Les leçons qui suivent valent pour les deux. Connaître un peu ce traducteur, et la machine qu'il vise, reste payant pour deux raisons.

D'abord, ça aide à flairer un coût caché derrière une ligne banale. Le cas d'école : recalculer la longueur d'un texte à chaque tour d'une boucle, alors qu'elle ne change pas. La machine la recompte en entier, des milliers de fois pour rien. Il suffit de la calculer une fois, avant la boucle.

for (let i = 0; i < longueur(texte); i++) { … }   // ✗ recomptée à CHAQUE tour
const n = longueur(texte);                         // ✓ comptée une fois
for (let i = 0; i < n; i++) { … }

Ensuite, ça dit ce que le compilateur fait pour vous, et ce qu'il ne fera jamais. Il optimise les petits détails tout seul : il calcule par exemple 3 × 4 une fois pour toutes, au lieu de le refaire à chaque exécution. Mais il ne touchera jamais à vos grands choix : il ne transformera pas une recherche lente en recherche rapide. L'algorithme reste votre travail. La machine ne fait que polir ce que vous lui donnez.

Le bas niveau réserve aussi des surprises. Pour aller vite, le processeur parie à l'avance sur le résultat de chaque test if (c'est la « prédiction de branche »). Quand il se trompe trop souvent, il perd du temps à revenir en arrière. Conséquence absurde mais bien réelle : parcourir un tableau trié peut être plusieurs fois plus rapide que le même parcours sur le même tableau en désordre, parce que les tests deviennent prévisibles.

Or ça, personne ne le devinerait, et des effets cachés comme celui-ci, il y en a beaucoup. La leçon : sur la performance, l'intuition se trompe souvent. Le seul moyen fiable de savoir si un code est rapide, c'est de le mesurer (le chronométrer pour de vrai), jamais de le deviner.

Write Great Code, vol. 2

↳ ce qui mène au chapitre 2

La machine a un coût et ne parle que nombres. Pour exprimer notre intention par-dessus, il nous faut un intermédiaire : le langage de programmation. Et il ne se contente pas de traduire la pensée en instructions, il devient un outil pour penser, des mots qui décident ce qu'on est capable de concevoir.

« Le livre de la nature est écrit en langage mathématique. » — Galilée, Il Saggiatore, 1623

2

Le langage, outil de pensée

exprimer

La machine ne parle que nombres. Le langage est la couche qui traduit notre intention en instructions. Mais ce n'est pas un simple traducteur : son modèle mental décide de ce qu'on peut penser facilement. Six idées le montrent, de la plus concrète (ce que contient vraiment une variable, pièges compris) à la plus profonde : nommer une chose, c'est étendre ce qu'on peut penser.

2.1 Valeurs et références : ce que la variable contient vraiment

Une variable contient-elle la donnée elle-même, ou seulement son adresse ? Toute la suite en découle. Pour un nombre ou un booléen, la variable est la donnée : l'assigner à une autre la recopie, et les deux vivent séparément.

Mais pour un objet, une liste ou un dictionnaire, la variable ne tient qu'une poignée (un pointeur, c'est-à-dire l'adresse mémoire du chapitre 1) vers la donnée. L'assigner copie la poignée, pas la donnée : on se retrouve avec deux noms pour une seule donnée.

const a = [1, 2, 3];
const b = a;      // b partage le MÊME tableau de fond
b[0] = 9;         // a[0] vaut 9 aussi : une seule donnée derrière deux noms

En PHP, un objet passé à une fonction n'est pas recopié : la fonction modifie l'original. Confondre les deux est une source inépuisable de bugs « j'ai changé une copie et l'original a bougé ». Distinguer les deux n'est pas une astuce de syntaxe : c'est un modèle mental que le langage installe en vous, et c'est lui qui vous fait voir le bug venir.

Et les chaînes de caractères, valeur ou référence ?

Selon le langage, soit la chaîne se copie comme un nombre (PHP), soit elle est immuable : on ne la modifie jamais, on en fabrique une nouvelle (JavaScript, Python). Dans les deux cas le piège disparaît : impossible de « changer la copie et abîmer l'original », elle se comporte comme une valeur.

Learning Go·PHP 8 Objects

2.2 Les types sont des ensembles de valeurs

Vous connaissez les types par leurs noms : int, string, boolean. Mais un type n'est pas qu'une étiquette : c'est l'ensemble des valeurs qu'une variable peut prendre.

  • le type boolean ne contient que deux valeurs : vrai et faux ;
  • le type 0 | 1 | 2 (un nombre forcément 0, 1 ou 2) en contient trois ;
  • le type string, une infinité.

Sous cet angle, combiner deux types revient à opérer sur des ensembles : l'union (A | B) réunit toutes les valeurs, l'intersection (A & B) ne garde que les communes, utile pour exiger qu'un objet honore deux contrats à la fois.

Cette vision explique aussi le typage structurel : un type est jugé sur ce qu'il a, pas sur son nom. TypeScript le fait sur la forme d'un objet (ses propriétés). Go, sur ses interfaces (un type la satisfait dès qu'il en a les méthodes, sans jamais écrire implements).

interface Point { x: number; y: number }
function longueur(p: Point) {…}   // longueur attend un Point : un x et un y
const p = { x: 3, y: 4, z: 5 };
longueur(p)                         // ✓ ok : la forme {x, y} y est, le z en trop est ignoré
longueur({ x: 3, y: 4, z: 5 })      // ✗ refusé : sur un littéral direct, TS signale le z en trop

C'est l'inverse du typage nominal (Java, PHP), où l'objet doit déclarer explicitement implements Point pour être accepté. Ici, la forme suffit.

Reste l'idée la plus rentable d'Effective TypeScript, qui tient en deux mots opposés :

let a: any     = JSON.parse(s);  a.foo.bar  // ✗ "fais-moi confiance" : 0 vérif, ça plante à l'exécution
let b: unknown = JSON.parse(s);  b.foo      // ✓ erreur de compil : prouve d'abord ce que c'est

any éteint le compilateur et contamine en silence tout ce qu'il touche. unknown dit « je ne sais pas encore, je vérifierai avant d'agir ». Choisir unknown, c'est garder le filet.

Effective TypeScript·Learning Go

2.3 Les fonctions sont des valeurs

Dans les langages modernes, une fonction est une valeur comme une autre : on la range dans une variable, on la passe en argument, on la renvoie. Cette seule idée débloque trois outils. Ce sont en réalité trois façons de penser que le langage vous ouvre, magiques tant qu'on n'en a pas vu le mécanisme :

  • la closure : une fonction qui se souvient des variables de l'endroit où elle est née (un compteur qui garde son total privé d'un appel à l'autre) ;
  • le décorateur : une fonction qui en emballe une autre pour lui ajouter un comportement sans toucher à son code (chronométrer, mettre en cache, vérifier des droits, en une ligne) ;
  • le générateur (yield) : une fonction qui produit ses valeurs une à une, à la demande, au lieu de tout calculer d'avance (parcourir un fichier énorme sans le charger en entier).

La plus surprenante des trois, c'est la closure. Un exemple en Python :

def compteur():
    n = 0
    def inc():
        nonlocal n; n += 1; return n   # inc se SOUVIENT de n (closure)
    return inc

Ce mécanisme est partout : JavaScript et Go capturent la variable tout seuls. PHP demande de la déclarer explicitement, avec use (&$n). Surtout, c'est plus lisible qu'un static, une valeur que la fonction garde d'un appel au suivant :

  • un static garde un seul état, partagé par toute la fonction ;
  • chaque appel de compteur() fabrique un n privé tout neuf : autant de compteurs indépendants qu'on veut (les inc() issus du même appel, eux, partagent le leur).

Au fond, une closure, c'est une machine à fabriquer des états privés.

Le décorateur en découle directement. Le grand classique : chronométrer une fonction sans toucher à son code :

def chrono(f):              # prend une fonction, en renvoie une autre
    def emballage(*a):
        t = time(); r = f(*a); print(time() - t)   # exécute f et mesure son temps
        return r
    return emballage

@chrono                     # @ : emballe lent(), qui est maintenant chronométré
def lent(): ...

Le générateur, lui, le plus contre-intuitif, évite le piège « je charge tout en mémoire » du chapitre 1 en livrant ses valeurs au compte-gouttes, à la demande :

def lignes(fichier):
    for ligne in fichier:
        yield ligne        # rend une ligne, se met en pause, reprend à la suivante
# → on lit un fichier de 50 Go sans jamais le charger entier en mémoire

Fluent Python pousse l'idée encore plus loin : pas seulement les fonctions, tout objet « parle » le langage natif dès qu'il implémente les bonnes méthodes spéciales. Donnez à votre classe un __len__, et len(mon_objet) marche. Un __iter__, et for x in mon_objet marche, sans hériter de quoi que ce soit. C'est le data model : on ne configure pas Python, on s'y branche.

Fluent Python·Eloquent JavaScript

2.4 La récursivité : résoudre un problème avec une version plus petite de lui-même

Une fonction qui s'appelle elle-même ? On croit d'abord à une boucle infinie, mais non. Tout tient en deux temps :

  • le cas d'arrêt : minuscule, résolu directement, sans relancer d'appel ;
  • le cas général : on ramène le problème à une version plus petite de lui-même.

Comme chaque appel rétrécit le problème, on retombe toujours sur le cas d'arrêt. Sans lui, c'est deux miroirs face à face : ça ne s'arrête jamais.

Le secret pour l'écrire sans se nouer le cerveau : régler le cas d'arrêt, puis faire confiance à l'appel plus petit en le supposant déjà résolu. On ne déroule pas toute la cascade dans sa tête.

def fact(n):
    if n <= 1: return 1        # cas d'arrêt
    return n * fact(n - 1)     # réduit à un problème plus petit
# fact(3) = 3 × fact(2) = 3 × 2 × fact(1) = 6

Sous le capot, chaque appel attend le résultat du suivant : ils s'empilent (la pile d'appels), puis se dépilent en remontant les résultats. D'où une limite bien réelle : une récursion trop profonde fait déborder la pile (le fameux stack overflow).

C'est l'outil naturel pour tout ce qui est arborescent (en forme d'arbre : une branche qui se redivise en branches plus petites) :

  • parcourir un dossier et ses sous-dossiers ;
  • un arbre HTML, le DOM (la page vue comme des balises emboîtées) ;
  • un JSON imbriqué.

Le code épouse alors la forme des données et devient bien plus court qu'avec des boucles.

Grokking Algorithms·Eloquent JavaScript

2.5 La permissivité a un prix

Certains langages acceptent presque tout, et c'est un piège qui se referme en silence. JavaScript convertit tout seul les types entre eux (texte, nombre, booléen) : c'est la coercition. Selon le contexte, le même + additionne ou colle bout à bout.

0 == false    // true  (== convertit false en 0, puis compare)
0 === false   // false (=== compare sans convertir : 0 n'est pas false)
"5" - 1       // 4     (- n'a aucun sens sur du texte → "5" devient 5)
"5" + 1       // "51"  (+ voit une chaîne → 1 devient "1", puis collés)

La règle d'or tient à un caractère : toujours ===, jamais ==. Et c'est précisément cette permissivité que TypeScript, présenté à la section 2.2, est venu corseter en imposant un typage strict. Connaître la permissivité d'un langage, c'est connaître ses bugs avant de les écrire.

Eloquent JavaScript

2.6 Nommer un concept change ce qu'on peut penser

Le vrai pouvoir d'un langage, c'est de nommer des abstractions. Une fois qu'on peut dire « une liste », « une interface », « une promesse », on raisonne dessus sans repenser aux mécanismes de bas niveau en dessous. Prenons deux de ces noms dans deux langages, avant de monter d'un cran.

Premier nom : l'interface. En Go, un type l'implémente sans jamais le déclarer : il suffit qu'il ait les bonnes méthodes. C'est le typage structurel de 2.2, appliqué aux interfaces : on juge le type sur ce qu'il sait faire, jamais sur son nom.

Le consommateur dit « il me faut quelque chose qui sait faire X » sans connaître le type concret derrière. Le fournisseur, lui, n'a rien à déclarer. Ce seul nom suffit à découpler celui qui utilise de celui qui fournit : c'est la graine de toute l'architecture du chapitre 4.

Deuxième nom : la promesse, celle qui fait mal à tout débutant JavaScript. Certaines opérations prennent du temps, par exemple aller chercher des données sur un serveur. JavaScript ne se fige pas à attendre : il continue, et vous rappelle quand le résultat arrive (c'est l'asynchrone).

Avant, on lui passait pour ça une fonction, un callback : « quand tu auras fini, exécute ceci ». Mais dès qu'une requête a besoin du résultat de la précédente, on emboîte un callback dans un callback dans un callback : le code part vers la droite et devient illisible. C'est le fameux « callback hell ».

// ✗ sans le mot : un callback dans un callback dans un callback
get(a, r1 => get(r1, r2 => get(r2, r3 => afficher(r3))))

La promesse, elle, nomme « une valeur qui arrivera plus tard ». Le mot-clé await veut dire « attends ici que la valeur arrive, puis donne-la-moi comme une variable normale ». Le même enchaînement se lit alors de haut en bas, comme du code ordinaire :

const r1 = await get(a);    // attends le 1er résultat
const r2 = await get(r1);   // puis le 2e, qui dépend du 1er
afficher(await get(r2));     // puis le 3e

Interface, promesse : ces deux mots nommaient de la technique. Il y a un cran au-dessus : nommer le métier lui-même. Quand l'expert dit « cargo », « itinéraire », « route », et que le code, lui, dit « ligne », « booléen », « flag », chaque réunion devient une traduction, et la traduction perd toujours quelque chose. Donner au code les mots exacts du métier, pour que l'équipe et l'expert parlent enfin une seule langue : cette langue, c'est l'Ubiquitous Language d'Eric Evans.

Interface, promesse, ou les mots du métier : seul le nom a changé ce qu'on peut écrire et suivre. Un nom bien choisi vous fait penser plus haut, et parfois parler la même langue que ceux qui connaissent le problème. C'est tout le chapitre en une idée.

Learning Go·Eloquent JavaScript·Domain-Driven Design·Learning DDD

↳ ce qui mène au chapitre 3

On sait maintenant tout exprimer. Mais tout exprimer et l'exprimer clairement sont deux choses différentes : un programme juste peut rester un casse-tête à relire. Le prochain enjeu n'est plus le pouvoir du langage, c'est la clarté.

« Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément. » — Boileau, L'Art poétique, 1674

3

Écrire pour les humains

la propreté

Le code est lu bien plus souvent qu'il n'est écrit. La machine, elle, se moque de l'élégance : un nom à une lettre s'exécute aussi vite qu'un nom parlant. La propreté n'est donc pas pour la machine : c'est de la communication avec le prochain humain qui ouvrira le fichier. Et ce prochain humain, c'est souvent vous. Cette propreté se construit en six gestes, du plus petit (bien nommer une variable) au plus grand (faire évoluer le système entier sans le casser).

3.1 Le nom révèle l'intention

Un bon nom rend le commentaire inutile. Il doit répondre à trois questions d'un coup :

  • pourquoi ça existe ;
  • ce que ça fait ;
  • comment on s'en sert.

Comparez la condition brute et la même intention nommée :

// ✗ on doit décoder la règle métier à chaque lecture
if (age >= 18 && solde > 0 && !suspendu) { ... }

// ✓ le nom EST l'explication, et la règle vit à un seul endroit
if (peutPasserCommande(client)) { ... }

C'est le pouvoir de nommer du chapitre 2, redescendu à l'échelle d'une seule variable.

Coder proprement

3.2 La fonction profonde, pas seulement courte

Ici deux maîtres s'affrontent, et la dispute est instructive :

  • Clean Code : les fonctions doivent être petites, puis plus petites encore ;
  • A Philosophy of Software Design : passé un point, découper encore n'aide plus personne.

La vraie question n'est pas « combien de lignes ? », mais « est-ce plus simple pour qui l'appelle ? ». Prenons un prix à calculer : HT, TVA, remise, port.

// ✗ sur-découpé : 4 micro-fonctions que l'appelant doit enchaîner à la main
const ht     = lirePrix(c);
const tva    = appliquerTVA(ht);
const remise = retirerRemise(tva, c);
const total  = ajouterPort(remise, c);

// ✓ une fonction "profonde" : un seul appel, les 4 étapes cachées dedans
const total = prixFinal(c);

Le découpage en lui-même n'est pas le problème : prixFinal peut très bien appeler ces quatre étapes en interne. Ce qui change, c'est qu'elles deviennent privées. L'appelant ne voit qu'un seul nom au lieu d'orchestrer la chaîne lui-même.

La fonction profonde offre ainsi une toute petite surface (un nom, un argument) pour beaucoup de travail caché. Découper, oui. L'exposer, non. On ne fusionne que dans un cas : deux étapes si soudées qu'on ne comprend pas l'une sans l'autre. Les séparer créerait des méthodes siamoises, le sur-découpage que dénonce A Philosophy of Software Design.

Coder proprement·A Philosophy of Software Design

3.3 Le commentaire dit le pourquoi, pas le quoi

Sur les commentaires, Clean Code est cinglant : Robert Martin va jusqu'à écrire que les commentaires sont toujours des échecs, la preuve qu'on n'a pas su s'exprimer dans le code. Un commentaire qui ne fait que paraphraser le code lui donne raison : il vieillit et finit par mentir, car le code change et lui non.

user.deactivate();   // ✗ désactive l'utilisateur        (le code le dit déjà)
timeout = 29_000;    // ✓ 29 s : juste sous le seuil de 30 s du load balancer

Mais tout bannir serait l'excès inverse. A Philosophy of Software Design, de John Ousterhout, rétablit l'équilibre : le bon commentaire dit ce que le code ne peut pas dire. Trois genres méritent qu'on les écrive :

  • le pourquoi : la décision non évidente, le compromis derrière le code. // en centimes : zéro arrondi flottant
  • l'avertissement : le piège, l'ordre qu'on ne doit pas casser. // NE PAS réordonner : valider avant d'enregistrer
  • le contrat : ce qu'une fonction promet à qui l'appelle (ce qu'elle attend, ce qu'elle renvoie, ses effets), pour ne pas avoir à lire son corps.

C'est même un détecteur : quand un commentaire devient long et pénible à écrire, c'est « le canari dans la mine », le signe que votre abstraction (la façon dont vous avez découpé le problème) est mauvaise. Ousterhout va jusqu'à les écrire avant le code, comme outil de conception.

Coder proprement·A Philosophy of Software Design

3.4 Faire disparaître l'erreur

L'idée la plus sous-estimée : la meilleure gestion d'erreur, c'est l'erreur qui n'existe pas. Plutôt que d'attraper une exception partout, on redéfinit la sémantique (le sens même de l'opération) pour que le cas d'erreur devienne un cas normal.

// ✗ Java : lève une exception, à blinder de vérifications partout
"hi".substring(0, 10);   // 💥 IndexOutOfBounds

# ✓ Python : une tranche hors limites se borne à ce qui existe, sans erreur
"hi"[0:10]               # → "hi"

Ce réflexe vaut aussi dans votre propre code, pas seulement dans la bibliothèque standard d'un langage (les fonctions livrées avec lui). Plutôt que d'imposer à tous les appelants un if (user == null) throw répété partout, renvoyez un objet « invité » qui répond comme un vrai utilisateur :

// ✗ chaque appelant doit se rappeler de tester le null
if (user == null) throw ...; user.nom();

// ✓ "Null Object" : pas d'utilisateur = un Invité, qui sait répondre
user.nom();   // → "Invité", droits vides : le cas d'erreur a disparu

C'est la même intuition que le principe de John Ousterhout, « tirer la complexité vers le bas » (l'absorber dans le module plutôt que la repousser vers ceux qui l'appellent) : qu'une équipe souffre une fois dedans, plutôt que mille appelants dehors.

A Philosophy of Software Design

3.5 Ne te répète pas, et facilite le changement

DRYDon't Repeat Yourself », ne te répète pas) est l'un des principes les plus mal compris. Ce n'est pas « ne copie-colle pas du code », c'est « chaque savoir a une seule source d'autorité dans le système ». Le mot qui compte est savoir, pas code, et deux conséquences contre-intuitives en découlent.

Du code identique n'est pas toujours une duplication. Si deux fragments se ressemblent par hasard et évolueront pour des raisons sans rapport, les fusionner les couple à tort. Le prix d'un article se valide avec prix > 0, sa quantité en stock avec quantite > 0 : tentant de tout réunir dans un seul estPositif(). Mais demain le stock devra accepter 0 (rupture de stock), pas le prix : c'étaient deux règles, identiques par coïncidence.

Et on se répète sans copier une seule ligne. Le même savoir fuite ailleurs : une règle de validation refaite côté client et côté serveur, la structure d'une table que le code re-décrit à la main, ou un commentaire qui redit ce que le code fait déjà, exactement le piège du commentaire-paraphrase vu plus haut. Aucun copier-coller, et pourtant deux vérités à garder synchronisées.

La parade : chaque savoir a un seul foyer. Tout le reste du code s'y réfère, au lieu de re-savoir la même chose dans son coin.

Derrière DRY se tient une valeur plus large, ETC (Easier To Change, plus facile à changer). Ce n'est pas une règle de plus, c'est une boussole : à chaque choix, une seule question, « est-ce que ça rendra le système plus facile, ou plus dur, à modifier ? ». Le découplage, les bons noms, DRY lui-même n'en sont que des cas particuliers, et c'est elle qui portera tout le chapitre suivant : l'architecture, c'est ETC à l'échelle du système.

// ✗ chaque nouveau canal rouvre la fonction
if (canal == "email") ... else if (canal == "sms") ...

// ✓ une table {canal → notifieur} : un nouveau canal = une ligne, la logique ne bouge pas
notifieurs[canal].envoyer(message)

The Pragmatic Programmer

3.6 Refactorer, c'est changer la forme sans changer le comportement

Refactorer n'est pas « réécrire » : c'est transformer la structure interne sans toucher au comportement observable. Un test qui passait avant passe encore après. Sinon ce n'est plus un refactoring, c'est une modification.

On avance par petits pas sûrs, guidés par les « odeurs » (ces signes qui trahissent un code mal structuré). L'une des plus courantes, le Data Clump : un groupe de paramètres qui voyagent toujours ensemble réclame une classe.

livrer(nom, rue, ville, cp)   // ✗ 4 paramètres collés partout
livrer(adresse)               // ✓ ils formaient un concept : une classe Adresse

Le geste clé, contre-intuitif, c'est la phrase de Kent Beck : rendre le changement facile, puis faire ce changement devenu facile (réorganiser d'abord, c'est parfois ça le plus dur). Vous devez brancher le paiement PayPal ? D'abord vous refactorez pour qu'un moyen de paiement devienne interchangeable, sans toucher au comportement. Ensuite vous ajoutez PayPal presque gratuitement.

Mais ces petits pas ne sont sûrs qu'avec un filet. Fowler le martèle : sans une batterie de tests qui confirme à chaque étape que le comportement observable n'a pas bougé, le refactoring devient un pari à l'aveugle (on y revient au chapitre 5).

Le réflexe quotidien, c'est la règle du boy-scout de Clean Code : laissez toujours le fichier un peu plus propre que vous ne l'avez trouvé. Pas tout nettoyer, juste vos déchets : la propreté devient une marée, pas un grand ménage qu'on repousse sans fin.

Refactoring

↳ ce qui mène au chapitre 4

On sait écrire une fonction, un fichier propre. Mais mille fonctions propres ne font pas un système clair : il leur manque la forme d'ensemble, celle qui décide quoi dépend de quoi et où passent les frontières. Cette forme, c'est l'architecture. Sans elle, les raccourcis locaux s'accumulent en dette technique, jusqu'à figer le tout.

« Un programme doit être écrit pour que les humains le lisent, et seulement accessoirement pour que les machines l'exécutent. » — Abelson & Sussman, SICP, 1984

4

Donner une forme au système

architecturer

À l'échelle du système, une question domine : qui dépend de qui, et qu'est-ce qui peut changer sans tout casser ? L'architecture, c'est décider de la forme des dépendances avant qu'elles ne se décident toutes seules, en désordre. Quatre niveaux de réponse : réutiliser sans hériter, reconnaître les patterns, inverser les dépendances, peser les compromis. Et au sommet, un seul esprit qui garde la forme.

À gauche, un bâtisseur assemble calmement une structure solide à partir de blocs modulaires interchangeables aux jointures nettes. À droite, une tour monolithique rigide se fend de haut en bas
Composer des pièces aux jointures nettes tient. Un bloc monolithique rigide se fend au premier changement.

4.1 Composer plutôt qu'hériter

Le premier réflexe pour réutiliser, c'est l'héritage : une classe enfant hérite d'un parent. Le piège : elle hérite de tout, même de ce qu'elle ne veut pas, et un changement du parent casse l'enfant à distance. L'exemple culte de Head First : tous les canards héritent de voler()… puis débarque le canard de bain, qui en hérite aussi alors qu'il ne vole pas. La composition règle ça : voler() devient un objet à part que le canard possède au lieu d'en hériter, qu'on lui branche et qu'on remplace (vrai vol, ou aucun) sans toucher au canard, même pendant que le programme tourne.

// ✗ héritage : voler est figé dans la hiérarchie
class CanardDeBain extends Canard { }  // hérite voler()… mais ne vole pas !

// ✓ composition : le comportement est un objet injecté, changeable
canard.comportementVol = new NePeutPasVoler()

« Préférez la composition à l'héritage » est le tout premier principe des patterns.

Design Patterns : Tête la première·Design Patterns (GoF)

4.2 Un pattern répond à un besoin, pas à un but

Un design pattern n'est pas une décoration qu'on plaque pour faire sérieux : c'est une réponse éprouvée à un problème récurrent.

  • Stratégie : changer d'algorithme à la volée ;
  • Observateur : prévenir une liste d'abonnés quand un état change ;
  • Décorateur : empiler des responsabilités sans sous-classer.

Aucun n'est sorcier. L'Observateur, par exemple, n'est qu'une liste de fonctions qu'on rappelle à chaque changement :

sujet.abonnés = [majAffichage, envoiMail]
sujet.changer(état) { abonnés.forEach(fn => fn(état)) }  // tout le monde est prévenu

Le GoF (le « Gang of Four », les quatre auteurs du livre fondateur Design Patterns) range ses 23 patterns en trois familles, et ce cadre vaut mieux que la liste :

  • Création (comment instancier) : Fabrique, Singleton, Monteur ;
  • Structure (comment assembler) : Décorateur, Adaptateur, Composite ;
  • Comportement (comment communiquer) : Stratégie, Observateur, Commande.

Vous ne mémorisez pas 23 recettes : vous posez une question, « mon problème est de créer, d'assembler ou de communiquer ? », et le couloir se réduit à quelques candidats.

Au-dessus de tous, le principe fondateur du livre : programmer vers une interface, pas vers une implémentation. Votre code travaille avec ce qu'un objet sait faire, jamais avec ce qu'il est.

// ✗ vers l'implémentation : le code est soudé à une classe précise
exporter(doc) { new PDF().écrire(doc) }          // ne sait faire que du PDF

// ✓ vers l'interface : « quelque chose qui sait écrire() »
exporter(doc, sortie) { sortie.écrire(doc) }   // PDF, CSV, HTML… : ce qu'elle FAIT, pas ce qu'elle EST

La règle finale : on n'applique pas un pattern, on le reconnaît quand le besoin l'appelle. Le danger n°1 est la sur-application : forcer un problème dans un pattern là où une solution simple suffirait, comme une Fabrique qui n'emballe qu'un seul new.

Design Patterns (GoF)·Tête la première

4.3 Dépendre d'abstractions, inverser les dépendances

Ici, tout se joue sur le sens des dépendances. La règle de dépendance de Clean Architecture : le code stable et important (les règles métier) ne doit jamais dépendre du code volatil (la base, le framework, l'UI). On inverse le sens habituel : le métier déclare une interface, l'infrastructure s'y plie.

// le métier déclare CE DONT il a besoin (une interface)
interface DépôtClient { trouver(id): Client }
// l'infra (SQL, API…) l'implémente, et le métier ignore tout d'elle
// → on change de base de données sans toucher une ligne de métier

C'est le « il me faut quelque chose qui sait faire X » du chapitre 2, monté à l'échelle d'un système : les frontières protègent ce qui compte du reste. Martin va plus loin avec une formule-choc : la base de données et le framework sont des détails, au même titre que la marque des câbles électriques d'une maison. On ne conçoit pas la maison autour des fils. Votre code métier ne devrait même pas savoir que MySQL existe. C'est pourquoi Martin formule l'objectif ainsi : un bon architecte maximise le nombre de décisions non prises. Moins on s'engage tôt sur les détails, plus on garde d'options ouvertes quand on en sait davantage.

Ce principe, poussé à l'échelle du système entier, donne les quatre cercles concentriques de Clean Architecture, héritiers de l'architecture hexagonale d'Alistair Cockburn (2005), dite ports et adaptateurs. Imaginez ces quatre cercles et une seule règle : les dépendances ne traversent jamais vers l'intérieur sans passer par une interface. Le cercle central, c'est le cœur métier. Autour, les règles applicatives. Ensuite, les adaptateurs (les contrôleurs HTTP, les accès base de données). Tout à l'extérieur, les détails techniques (MySQL, le framework, les APIs tierces). La règle unique : un cercle extérieur peut dépendre d'un cercle intérieur, jamais l'inverse.

En pratique : le cœur définit une interface DépôtCommande avec une méthode sauvegarder(). C'est le port : la prise que le cœur expose vers l'extérieur, dont il définit la forme. L'adaptateur MySQL DépôtCommandeMySQL implémente ce port. L'adaptateur test DépôtCommandeMémoire implémente le même port avec un simple tableau PHP. Le service du cœur appelle $this->depot->sauvegarder($commande) sans jamais savoir ce qui est branché. Passer de MySQL à PostgreSQL : brancher un autre adaptateur. Le cœur ne bouge pas.

Le SOLID formalise ça, cinq règles, une par lettre :

  • S (Single Responsibility) : un module ne répond qu'à un seul acteur, c'est-à-dire un groupe humain qui demande le même genre de changements ;
  • O (Open/Closed) : ouverte à l'extension, fermée à la modification ;
  • L (Liskov Substitution) : un sous-type doit pouvoir remplacer son parent sans surprise ;
  • I (Interface Segregation) : plein de petites interfaces valent mieux qu'une énorme ;
  • D (Dependency Inversion) : dépendre de l'abstraction, jamais du concret (ce qu'on vient de voir).

Le S est le plus souvent réduit à « une fonction fait une chose » : c'est en réalité un autre principe, plus bas niveau, issu de Clean Code. Martin le corrige lui-même dans Clean Architecture ch. 7 : l'unité du SRP, c'est un groupe humain, pas un comptage de lignes.

Le même réflexe vaut à la plus petite échelle, entre deux objets. La Loi de Déméter dit « ne parle qu'à tes voisins immédiats » : un objet appelle ses propres méthodes et celles des objets qu'on lui passe, jamais une chaîne comme commande.getClient().getAdresse().getVille() qui plonge dans la structure interne de trois inconnus. Le jour où l'un d'eux change de forme, l'appelant casse. Deux classiques nomment le même défaut. Clean Code parle de catastrophe ferroviaire, ces getters accrochés les uns aux autres comme des wagons. Refactoring parle de chaîne de messages et la corrige avec Hide Delegate, en exposant une méthode qui dit ce qu'on veut plutôt que le chemin pour l'obtenir. Couplage de système ou couplage d'objet, c'est la même hygiène : ne dépendre que de ce qu'on a le droit de connaître.

Clean Architecture·Clean Code·Refactoring

4.4 Pas de « best practice », seulement le moins pire des compromis

Plus on monte, moins il y a de réponses universelles. Hard Parts le pose net : « pour les architectes, chaque problème est un flocon de neige ». La compétence n'est pas de choisir le bon pattern, c'est de peser les compromis. Deux outils pour ça.

Le premier, le quantum d'architecture : le plus petit morceau qu'on peut déployer, tester et faire tomber seul. Deux services qui partagent la même table SQL, c'est comme deux appartements derrière un seul disjoncteur : l'un coupe le courant, l'autre est dans le noir. Ils ne forment qu'un seul quantum.

Le test, devant n'importe quel schéma : « si ce composant change ou crashe, combien d'autres tombent avec lui ? » Ce nombre, c'est la taille de votre quantum. Vingt « microservices » sur une base commune ? Un seul quantum, là encore.

Le second, « la réutilisation, c'est du couplage » : partager une classe métier entre services propage chaque changement partout à la fois. D'où un réflexe contre-intuitif : on duplique parfois exprès. On ne met en commun que ce qui est vraiment un seul savoir, tenu de rester cohérent (le DRY du chapitre 3). Pour deux bouts qui se ressemblent mais évolueront à part, un peu de copie vaut mieux qu'un mauvais couplage.

Aucun des deux ne se tranche par principe. Le petit quantum achète de l'indépendance, la mise en commun achète de la cohérence, et chacune se paie : l'une en duplication et en réseau, l'autre en couplage. On choisit au cas par cas, selon ce qui compte le plus ici : pas de règle, seulement le moins pire des compromis.

Software Architecture: The Hard Parts

4.5 L'intégrité conceptuelle : un seul esprit

La plus belle architecture meurt si quarante équipes empilent leurs idées sans coordination : l'API où chacune nomme « identifiant » autrement et formate les dates à sa façon. Chaque morceau marche seul. L'ensemble est illisible.

// ✗ trois équipes, trois noms pour le même identifiant, trois dates
GET /users   → { id,     "2026-06-09" }
GET /orders  → { userId, "09/06/2026" }
GET /cart    → { uid,    1749427200 }

// ✓ un seul vocabulaire, un seul format : l'appelant devine tout
GET /users · /orders · /cart → { id, "2026-06-09" }

Le Mythe du mois-homme, de Fred Brooks, tranche : « l'intégrité conceptuelle est la considération la plus importante dans la conception d'un système ». Elle doit sortir d'un seul esprit, ou d'un très petit groupe, sinon c'est la tour de Babel.

Un seul esprit ne veut pas dire une seule personne qui code tout : cette poignée décide de la forme, les autres la remplissent. Et Brooks insiste : « la forme libère ». Une fois la structure fixée, chacun sait où sa pièce s'emboîte, et code plus vite, pas plus lentement.

Mais une vision unique ne survit pas dans un document : le schéma sur le wiki dérive dès le premier sprint, parce que c'est le code qui bouge, pas le diagramme. Elle doit donc vivre dans le code. C'est le Model-Driven Design d'Eric Evans : un objet Commande est une commande du métier, pas une ligne de table déguisée, et la règle métier vit dans l'objet plutôt qu'éparpillée en procédures. Le code et le modèle ne font qu'un seul artefact.

La conséquence dérange : celui qui conçoit doit coder. L'architecte qui ne touche jamais le clavier finit par dessiner l'irréalisable. Evans appelle ça les Hands-on Modelers : pas de tour d'ivoire, la même tête tient le crayon et le clavier.

Le Mythe du mois-homme·Domain-Driven Design

↳ ce qui mène au chapitre 5

Une architecture, même élégante, n'est qu'une hypothèse tant qu'elle n'est pas prouvée. Et la règle de dépendance qu'on vient de poser ne se vérifie pas à l'œil : il faut un test qui échoue si une frontière est franchie. Prouver, livrer, tenir sous la charge réelle : la forme doit affronter le monde.

« Les discours ne coûtent rien. Montre-moi le code. » — Linus Torvalds, 2000

5

Prouver, livrer, tenir la charge

le flux

Du code qui « marche sur ma machine » à un service qui tient en production devant des milliers d'utilisateurs, il y a un gouffre. Le franchir, ce n'est pas coder plus : c'est installer un flux qui prouve, livre et tient, sans héroïsme et sans nuits blanches.

À gauche, un tapis roulant fluide envoie de petits colis qui s'écoulent régulièrement. À droite, un tapis croule sous une montagne de cartons bloqués devant un ouvrier débordé
Le flux régulier livre sans effort. La file saturée fige tout, même quand chacun est à fond.

5.1 Le test d'abord

Écrire le test avant le code inverse l'ordre habituel, et ça change tout : on définit le résultat attendu avant de savoir comment l'obtenir. Le cycle se répète à l'infini : Rouge (un test qui échoue), Vert (le code le plus bête qui le fait passer, même en dur), Refactor (nettoyer sans casser).

// 1. ROUGE : le test AVANT le code
test('5 + 3 = 8', () => expect(somme(5, 3)).toBe(8))   // ✗ échoue

// 2. VERT : le code le plus bête qui passe (oui, "8" en dur)
function somme(a, b) { return 8 }                       // ✓ vert, sans honte

// 3. REFACTOR : un 2e test casse le "8", on généralise
function somme(a, b) { return a + b }                   // ✓ propre, toujours vert

On ne refactorise jamais sur du rouge. Le but : du code propre qui marche.

Mais l'effet le plus profond du TDD (Test-Driven Development, le développement piloté par les tests) n'est pas d'attraper des bugs, c'est le design émergent : en écrivant le test d'abord, on conçoit l'API du point de vue de celui qui l'appelle, pas de celui qui l'implémente. Le code devient modulaire et découplé parce qu'il faut qu'il soit testable.

On ne dessine pas l'architecture en avance, on la laisse apparaître, test après test. C'est ce qui rend les chapitres 3 et 4 plus faciles à tenir.

L'économie confirme la discipline : un défaut attrapé ici, au clavier, coûte une fraction du même trouvé en production, où il revient dix à cent fois plus cher (Code Complete). Tester tôt n'est pas du zèle, c'est le chemin le moins cher.

Test-Driven Development: By Example·Code Complete

5.2 Déboguer, c'est refuser de croire

Les tests attrapent la plupart des bugs. Les survivants sont ceux qui ont l'air impossibles. Un programmeur qui peut se connecter assis mais jamais debout. Un terminal bancaire de Chicago qui plante dès qu'un client tape « Quito ». Le réflexe, c'est de soupçonner la magie : un rayon cosmique, un bug du compilateur, une machine hantée. La règle de Bentley tranche : « déboguer, c'est d'habitude refuser de croire ». Le bug impossible a toujours une explication ennuyeuse, et invoquer le surnaturel, c'est justement la manière de ne pas la trouver.

Rick Lemons, cité par Bentley, racontait que la meilleure leçon de débogage de sa vie, c'était un spectacle de magie : une demi-douzaine de tours impossibles d'affilée, dont aucun n'était vraiment impossible. Vous regardiez simplement la mauvaise main. Un bug, c'est pareil. Refusez l'impossible et l'explication banale remonte : le clavier avait deux touches interverties, donc l'homme tapait autrement debout. Le terminal lisait « Quito » comme sa commande quit (quitter).

Votre version à vous, c'est le bug qui n'apparaît qu'en production, ou seulement après le déploiement du vendredi. Il n'a rien de plus surnaturel que les autres. Arrêtez de fixer le code qui « ne peut pas » être faux et posez la seule question utile : qu'est-ce qui a changé ? L'environnement, le cache, l'ordre de deux requêtes, une config qui diffère d'une seule ligne. La discipline complète les tests du 5.1 : un test rouge dit ça casse, l'incrédulité dit pourquoi.

Une chose marche étonnamment bien dès qu'on est coincé : expliquer le bug à voix haute, à un collègue ou même à un canard en plastique. McConnell appelle ça le confessional debugging : on trouve souvent la réponse au milieu de sa phrase, avant que l'autre ait dit un mot. Reconstruire la chaîne causale pour quelqu'un d'autre force le cerveau à passer du mode chercher au mode comprendre.

Programming Pearls·Code Complete

5.3 Versionner, c'est un système adressable par contenu

Le versioning est le filet du flux : on change tout, on tente, on travaille à plusieurs sans se marcher dessus, et on peut toujours revenir en arrière. La preuve par la peur : vous venez d'effacer trois jours de commits d'un git reset --hard mal visé. Panique. Sauf que dans Git, presque rien ne disparaît, et comprendre pourquoi change tout.

Git n'est pas un dossier d'historique : c'est une base d'objets, chacun adressé par son empreinte : un condensé court calculé à partir du contenu (le « hash », produit par la fonction SHA-1). Le même contenu donne toujours le même hash, donc rien ne se perd ni ne se falsifie en silence. Tout s'enchaîne par pointeurs :

git cat-file -p HEAD   # affiche ce commit : son tree, son parent, l'auteur

Avant d'entrer dans ce graphe, un fichier traverse trois zones, ce qui explique pourquoi git add existe :

git add ne sauvegarde rien : il photographie la version exacte d'un fichier pour le prochain commit. Le commit scelle cette photo dans le graphe. C'est pour ça qu'on peut commiter une partie de ses changements seulement.

Vos trois jours effacés ? Le commit d'avant le reset est toujours là, objet immuable dans la base. git reflog liste les hash récents, vous repointez une branche dessus, et tout revient. Le filet n'attrape que ce qui est commité : une modification jamais commitée est, elle, vraiment perdue. D'où le réflexe : commiter souvent. Comprendre le graphe, c'est livrer sans paniquer.

Pro Git

5.4 Le flux bat l'effort

The Phoenix Project enseigne par le roman une loi d'usine implacable : le temps d'attente d'une tâche explose à mesure qu'une ressource (un serveur, une équipe, une personne dont tout dépend) s'approche de 100 % d'occupation.

temps d'attente ≈ % occupé ÷ % libre   // la loi d'attente

Pourquoi ce mur ? À 99 % d'occupation, plus aucune marge n'absorbe l'imprévu : une tâche qui traîne, un pic d'arrivées, et la file enfle sans jamais se résorber. À 50 %, le temps libre lisse ces à-coups au fil de l'eau.

« Tout le monde est à fond » et « rien n'avance » sont donc la même phrase. Le remède est contre-intuitif : limiter le travail en cours, arrêter d'en commencer pour enfin en finir. La marge n'est pas du gâchis : c'est elle qui fait circuler le travail.

Un second flux se cache sous le même mot : celui de votre tête. Concevoir ou coder demande quinze minutes de montée pour s'installer, et une interruption ne coûte pas cinq minutes, elle coûte toute la remontée, près de vingt (DeMarco et Lister l'ont mesuré dans Peopleware). La marge protège ce flux aussi : un agenda calé à 100 %, comme un serveur à 100 %, ne laisse plus rien avancer vraiment.

La donnée tranche : dans les Coding War Games (un tournoi de centaines de développeurs mené par les mêmes auteurs), le plus gros écart de productivité ne tenait ni au langage ni à l'expérience, mais à l'environnement. Au calme et peu interrompus, les meilleurs rendaient un tiers de code sans bug en plus. C'est l'environnement qui fait l'écart, pas le talent.

The Phoenix Project·Peopleware

5.5 Déployer souvent fait moins peur que déployer rarement

Le grand déploiement préparé pendant des mois est un tir de canon : une fois le boulet parti, plus rien ne se corrige, et la cible a eu des mois pour bouger. La rafale de petites livraisons fait l'inverse : chaque coup montre où il atterrit, et le suivant rectifie le tir.

La machine qui rend la rafale possible a un nom : la CI/CD. L'intégration continue (CI) rejoue l'assemblage et tous les tests de 5.1 à chaque commit : personne ne fusionne du rouge. La livraison continue (CD) prolonge le tapis : chaque version au vert est emballée, prête à partir en production d'un clic. Déployer cesse d'être un événement. C'est la sortie normale du tapis.

Reste à prouver que la rafale bat le canon. Accelerate, le livre de Nicole Forsgren, Jez Humble et Gene Kim, le mesure : les équipes qui déploient souvent et avec un couplage faible (livrer sans demander la permission à une autre équipe, le quantum du chapitre 4) sont à la fois plus rapides et plus stables. Le compromis attendu (aller vite = casser plus) n'existe pas dans les données. On y arrive avec des outils qui rendent l'erreur bon marché : les feature flags (livrer du code éteint, qu'on allume d'un interrupteur, qu'on rééteint au premier souci) ou le déploiement blue-green (deux versions prêtes côte à côte, un aiguillage réversible de l'une à l'autre). Les deux reposent sur le même principe, posé dès 2010 par Humble et Farley : déployer (mettre le code en place) n'est pas livrer (le rendre visible aux utilisateurs). C'est ça qui enlève la peur : une bêtise se répare d'une bascule, pas d'une nuit blanche.

Voyons où vous en êtes. Accelerate donne quatre chiffres pour se situer objectivement, les métriques DORA (DevOps Research and Assessment, l'équipe de recherche derrière le livre) :

  • lead time : temps entre un commit et sa mise en prod. Se mesure avec deux horodatages que Git et la CI ont déjà. Les meilleurs : moins d'une heure ;
  • fréquence de déploiement : à quelle cadence on livre. Se compte dans les logs de la CI. Les meilleurs : à la demande, plusieurs fois par jour ;
  • MTTR (Mean Time To Recovery) : temps pour rétablir le service après une panne, du début de l'incident au retour à la normale. Les meilleurs : moins d'une heure ;
  • taux d'échec des changements : déploiements suivis d'un rollback, d'un hotfix ou d'un incident, divisés par le total. Les meilleurs : 0 à 15 %.

Ces quatre chiffres se lisent ensemble, sans formule ni score combiné : les deux premiers mesurent la vitesse, les deux derniers la stabilité. Et quand on trace les quatre chiffres de milliers d'équipes, celles-ci se rangent d'elles-mêmes en trois familles : haute, moyenne, basse performance. Les meilleures gagnent sur les quatre à la fois, là où les plus faibles livrent tous les six mois (enquêtes State of DevOps, 2014-2017). Le gain est aussi humain : la « douleur de déploiement » mène à l'épuisement des équipes si on la laisse s'installer, et c'est exactement elle que ces pratiques (livrer souvent, petit, réversible) font baisser.

Un dernier maillon conditionne tout le reste : voir. Le MTTR suppose qu'on sait qu'il y a une panne. Sans logs (le journal de ce que fait l'application), sans métriques (ses chiffres vitaux : requêtes, erreurs, latence) et sans alerte qui réveille quelqu'un, on l'apprend par un tweet furieux. On ne tient que ce qu'on voit.

Continuous Delivery·Accelerate·The Phoenix Project

5.6 La lecture se scale en copiant, l'écriture en découpant

Le service tient le déploiement. Reste la charge : un million de lecteurs sur une seule base, et chaque requête fait la queue dans la même file. Côté lecture, on se défend en trois temps. D'abord l'index, qui évite de parcourir la table entière, comme celui d'un livre vous évite de feuilleter 500 pages. Puis le cache, qui garde les réponses déjà calculées tout près pour les resservir telles quelles (Redis, un CDN : la pyramide du chapitre 1, à l'échelle du datacenter). Quand ça ne suffit plus, on copie : la réplication duplique la base sur plusieurs machines, et chacune sert sa part des lectures.

Le prix de la copie : les répliques ont toujours un temps de retard. Vous postez un commentaire (écrit sur la machine principale), vous rechargez la page (lue depuis une copie pas encore à jour) : il a disparu.

Copier n'aide pas l'écriture : chaque copie devrait encaisser chaque écriture. Elle, on la découpe. Le sharding répartit les données par tranches (clients A-M ici, N-Z là), et chaque machine n'encaisse que sa part.

C'est ici que les ennuis commencent : une donnée éclatée sur plusieurs machines, c'est exactement la situation où les garanties meurent.

Designing Data-Intensive Applications

5.7 À l'échelle, les garanties se paient

Sur une seule base de données, vous vivez protégé sans le savoir. Une transaction y est tout-ou-rien : le virement débite ET crédite, ou ne fait rien. C'est le contrat ACID (une transaction atomique, cohérente, isolée, durable), et le moteur vous l'offre gratuitement : si l'étape 2 échoue, il annule l'étape 1 tout seul (le rollback).

Puis le service grossit, la donnée se répartit sur plusieurs machines, et ce contrat meurt en silence : plus personne ne peut annuler « tout », chaque machine ne voit que son morceau.

Des anomalies neuves apparaissent, invisibles sur une seule base. La plus vicieuse, le write skew : deux transactions, chacune parfaitement valide, qui cassent une règle ensemble. L'exemple du livre : un hôpital exige au moins un médecin de garde. Alice et Bob, les deux derniers de garde, se désinscrivent au même moment. La transaction d'Alice vérifie « Bob est encore là ? oui » et valide. Celle de Bob vérifie « Alice est encore là ? oui » et valide. Chacune a vu un monde où la règle tenait. Les deux ensemble laissent zéro médecin. Aucune erreur n'a été levée nulle part.

Faute de rollback global, on écrit la marche arrière à la main : c'est la saga. Commander = réserver le stock ①, débiter la carte ②, créer la livraison ③. Si ③ échoue, votre code déclenche lui-même le remboursement de ② puis la libération de ①. Ce que le moteur faisait gratuitement devient votre travail, étape par étape.

Ce piège ne vous attend pas qu'à l'échelle des grandes bases réparties. Deux fils d'exécution dans le même programme (deux goroutines, deux threads) qui lisent et écrivent la même variable rejouent le write skew en miniature : chacun agit sur un monde déjà périmé, et ça s'appelle une race condition. Même parade qu'en base : verrouiller l'accès (un mutex, le verrou qui ne laisse passer qu'un fil à la fois), ou mieux, ne pas partager du tout.

La morale du distribué : le réseau ment, l'horloge ment, et « la suspicion et la paranoïa paient ».

Designing Data-Intensive Applications·The Hard Parts

↳ ce qui mène au chapitre 6

Un système prouvé, livré et tenu sous la charge est enfin prêt à rencontrer ses vrais usagers. Et c'est précisément là que les certitudes techniques se heurtent au réel : un humain pressé qui ne lit pas, une équipe qui grandit, un attaquant qui sonde. La technique n'était que le moyen. Le produit pour des humains, voilà la fin.

« Le design, ce n'est pas seulement l'apparence. Le design, c'est comment ça fonctionne. » — Steve Jobs, 2003

6

Le logiciel est pour des humains

produit, équipe, attaquant

Tout ce qui précède sert un but unique : un humain, devant l'écran, qui veut faire quelque chose. Et autour de lui, d'autres humains : l'autre développeur qui appelle votre API, l'équipe qui construit, et l'attaquant qui cherche la faille. Ce chapitre regarde le code par les yeux de ces gens-là.

Un développeur tend un objet simple et lumineux à une file de personnes variées : une personne âgée avec une canne, une personne aveugle avec une canne blanche, une personne pressée qui regarde sa montre. Chacune le prend en main immédiatement
Le bon logiciel se prend en main sans notice, par tout le monde, y compris ceux qu'on oublie.

6.1 Ne me fais pas réfléchir

Un utilisateur ne lit pas une page, il la scanne, et chaque demi-seconde d'hésitation est une friction qui le fait partir. D'où la loi que l'ergonome Steve Krug a posée en 2000, et qui donne son titre au livre : ne me fais pas réfléchir. Les conventions battent la créativité (la loupe en haut à droite, le panier juste à côté) parce que l'utilisateur les trouve sans y penser, sur tous les sites qu'il connaît déjà.

La navigation n'est pas une fonctionnalité du site : elle est le site, de la même façon que le bâtiment, les rayons et les caisses ne sont pas des options d'un magasin : ils sont le magasin. Sans eux, il ne reste rien à entrer, rien à trouver, nulle part où payer. La raison : le web n'a pas de physique. En magasin, vous savez que l'électroménager est "au fond à gauche" parce que vous l'avez marché. En ligne, il n'y a pas de fond, pas de gauche, pas de haut. La navigation remplace tout ça. Un visiteur qui ne sait pas où il est, d'où il vient et comment chercher ne peut rien faire du tout.

Le test du coffre révèle si ça tient. Imaginez qu'on vous bande les yeux, qu'on vous balade, et qu'on vous lâche sur une page au hasard dans un site. C'est exactement ce que vit quelqu'un qui arrive depuis Google sur votre page 7. Plissez les yeux : quel site est-ce ? Quelle page ? Quelles sections existent ? Où suis-je dedans ? Comment chercher ? Si ça ne saute pas aux yeux en vingt secondes, la navigation a échoué. Et pour trouver ces problèmes : trois utilisateurs, un matin par mois, un débrief au déjeuner. Krug observe que les trois premiers utilisateurs rencontrent déjà la plupart des problèmes sérieux. La fréquence bat la cérémonie.

La navigation n'est qu'une partie du tableau. Chaque visiteur arrive avec un réservoir de bonne volonté que chaque mauvaise décision vide. Vous avez mis vingt minutes à remplir votre panier : 45 €. Vous cliquez « commander ». Frais de livraison : 12 €. Vous fermez l'onglet. Voilà le réservoir à sec, en un seul chiffre caché trop tard. Le remplir, c'est l'inverse : être transparent, pardonner une faute de format, ne jamais lui barrer la route avec une animation.

Don't Make Me Think

6.2 Une API se conçoit pour celui qui l'appelle

Une API (interface de programmation) est le contrat par lequel un programme en appelle un autre, le plus souvent par-dessus le web. Son vocabulaire universel, ce sont les verbes HTTP : GET pour lire, POST pour créer, PUT/PATCH pour modifier, DELETE pour supprimer. Ils disent l'intention sans qu'on lise la doc : GET /users/42 se devine seul. Et la règle d'or d'Arnaud Lauret, l'auteur du livre, est le consumer-first : on ne part pas de sa base de données, on part de ce que l'appelant veut faire, on dessine la réponse idéale pour lui, puis on construit à rebours, jusqu'à la base de données.

Trois règles complètent le contrat :

  • Noms prévisibles : qui a vu une route devine les autres.
  • Erreur généreuse : dire ce qui ne va pas, où, et tout d'un coup, pas un reproche par essai.
  • Donnée minimale : la donnée la plus sûre est celle qu'on n'envoie pas : exposez le strict nécessaire.
// ✗ deviné par personne          // ✓ deviné par tous, et standard
{ "ACTBLNDFPRTF": true }        { "overdraftFacility": { "active": true } }

The Design of Web APIs

6.3 Accessible par construction

Une personne aveugle ne voit pas votre bouton. Son lecteur d'écran le lui annonce, en trois informations : un nom (le texte lu), un rôle (quel genre de chose c'est), un état (coché, ouvert, désactivé). Quand un élément HTML natif ne suffit pas à exprimer l'une de ces trois choses, les attributs ARIA (Accessible Rich Internet Applications), préfixés aria-*, permettent de la renseigner explicitement :

<button aria-pressed="true">Favori</button>   // annoncé : "Favori, bouton, activé"

Un <div> cliquable n'a ni rôle ni état : pour le lecteur d'écran, il n'existe pas. Première règle d'ARIA, donc : utiliser le bon élément HTML, qui fournit les trois gratuitement. Tout ça est cadré par un référentiel mondial, les WCAG (Web Content Accessibility Guidelines) : quatre principes (Perceptible, Utilisable, Compréhensible, Robuste) et trois niveaux d'exigence (A, AA, AAA). Deux gestes concrets qui couvrent l'essentiel : un contraste suffisant (ratio 4,5:1 sur le texte), et la navigation au clavier.

/* ✗ l'erreur la plus répandue : on efface le contour de focus */
button:focus { outline: none; }
/* ✓ un focus visible : l'utilisateur au clavier voit où il est */
button:focus { outline: 2px solid #005fcc; }

Le test ultime, gratuit : posez la souris, parcourez votre page à la touche Tab. Si vous perdez le fil, un utilisateur au clavier aussi.

Web Accessibility Cookbook

6.4 L'organisation se reflète dans le code

Jusqu'ici, on a regardé le logiciel par le dehors : l'utilisateur, même celui qu'on oublie, puis le développeur qui appelle l'API. Voyons maintenant qui le construit. L'organisation elle-même est une interface. Loi de Conway : un système copie la structure de communication de l'organisation qui le construit. Quatre équipes qui se parlent mal produiront quatre modules qui s'emboîtent mal, qu'on le veuille ou non.

Team Topologies retourne la loi en levier : si vous voulez une certaine architecture, organisez d'abord les équipes pour l'obtenir. La contrainte cachée est la charge cognitive : une équipe ne peut tenir qu'une quantité limitée de domaine. On découpe alors le système le long de ses plans de fracture, ses coutures naturelles, le plus souvent le domaine métier. Et le livre donne le mode d'emploi, quatre types d'équipes :

  • alignée sur un flux : une équipe = un produit, livré seule de bout en bout (le cas par défaut) ;
  • plateforme : fournit des outils internes pour que les autres livrent sans attendre ;
  • habilitante : aide une équipe à monter en compétence, puis se retire ;
  • sous-système compliqué : maintient un morceau trop pointu pour être partagé (un moteur de calcul, par exemple).

Ce plan de fracture le long du métier a un jumeau côté modèle : le Bounded Context d'Eric Evans, une frontière à l'intérieur de laquelle chaque mot du domaine garde un seul sens. Deux équipes qui se partagent un objet « Commande » sans cette frontière finissent avec deux définitions incompatibles dans la même table (l'équipe achats pense commande fournisseur, la cuisine pense commande client) et le rapport mensuel qui plante. Tracer la frontière, une équipe, un modèle, une base à elle, c'est se donner le droit que « Commande » signifie autre chose ailleurs, sans rien casser.

Dans les deux sens, la leçon est la même : dessiner les équipes, c'est déjà dessiner le système.

Team Topologies·Domain-Driven Design·Learning DDD

6.5 Ajouter des gens à un projet en retard le retarde

L'intuition dit : projet en retard, on ajoute des développeurs. C'est faux, et ça porte un nom, la loi de Brooks. Le travail se divise mal, chaque nouvelle personne doit être formée (par les anciens, qu'on ralentit donc), et surtout elle multiplie les canaux de communication.

canaux de communication = n × (n − 1) ÷ 2
 5 personnes → 10 canaux        // gérable
15 personnes → 105 canaux       // la moitié du temps part en coordination

« Le mois-homme comme unité de mesure d'un travail est un mythe dangereux et trompeur : il laisse croire que les hommes et les mois sont interchangeables. » Ils ne le sont pas : neuf femmes ne font pas un bébé en un mois.

Si le nombre ne fait pas la performance, qu'est-ce qui la fait ? La cohésion. DeMarco et Lister appellent équipe soudée (jelled team) un groupe si lié que le tout dépasse la somme des parties : peu de départs, une identité commune, une fierté partagée. On ne la fabrique pas sur commande, on crée seulement ses conditions. En revanche, on la tue vite, et ils ont un mot pour ça : le teamicide. Surveiller les gens plutôt que leur faire confiance, les disperser dans des bureaux séparés, imposer des deadlines bidon, casser une équipe qui marche sitôt le projet fini : chacun de ces réflexes de management suffit à la fissurer.

Le Mythe du mois-homme·Peopleware

6.6 Penser comme l'attaquant pour défendre

Dernière figure du chapitre, la moins aimable : l'attaquant. Première surprise : il ne vous a pas choisi. L'attaque est industrielle : des robots scannent le web en continu, sans cible particulière, et on n'est pas attaqué parce qu'on est intéressant, mais parce qu'on est accessible. C'est la thèse de Web Application Security, le livre d'Andrew Hoffman : on ne défend bien que ce qu'on sait attaquer. Le livre s'organise d'ailleurs comme une attaque réelle : la reconnaissance d'abord (cartographier l'application, chercher l'entrée de service plutôt que la porte d'entrée), l'offense ensuite, la défense en dernier.

Que trouve l'attaquant ? Presque toujours le même péché : une donnée venue de l'utilisateur que le code traite comme du code, ou croit sur parole. L'injection SQL montre le mécanisme en entier. Votre moteur de recherche colle ce que tape le visiteur dans une requête. L'attaquant ne tape pas un nom : il tape une apostrophe qui referme votre texte, puis sa propre commande :

recherche tapée  : '; DROP TABLE users --
requête exécutée : SELECT * FROM produits WHERE nom = ''; DROP TABLE users --'
                   // ✗ l'apostrophe referme le texte : la suite devient un ORDRE

La table des utilisateurs vient d'être supprimée par un formulaire de recherche. Les deux cousines jouent la même note. Le XSS (Cross-Site Scripting) injecte non pas du SQL mais du HTML, qui s'exécutera dans le navigateur des autres visiteurs. Et le mass assignment glisse un champ en trop dans la requête, que le serveur enregistre sans se méfier :

POST /api/profil  { "nom": "Alice", "isMember": true }
user.update(req.body)   // ✗ isMember passe → Alice s'auto-promeut

La parade se lit en miroir : ne jamais faire confiance à l'entrée. Contre l'injection, les requêtes préparées : l'entrée voyage à part de la requête et ne peut jamais redevenir du code. Contre le XSS, échapper tout ce qu'on affiche. Contre le mass assignment, une liste blanche des champs acceptés. Et par-dessus, la défense en profondeur : chaque couche se protège elle-même, du navigateur à la base, pour que si l'une cède, les autres tiennent.

Un dernier réflexe à copier sur les gens du métier : ils disent « mitigations », jamais « fixes ». On réduit le risque, on ne l'efface pas. Aucune défense n'est définitive.

Web Application Security

↳ ce qui mène au chapitre 7

Tout ce métier, des bits jusqu'à l'équipe, vient d'être bousculé par un acteur entièrement neuf, capable d'écrire du code à la demande : l'IA. Elle ne remplace pas le savoir d'avant, elle le rend plus nécessaire que jamais : quelqu'un doit juger ce qu'elle produit, et bien juger exige précisément tout ce qu'on vient de gravir.

« La perfection est atteinte non quand il n'y a plus rien à ajouter, mais quand il n'y a plus rien à retirer. » — Saint-Exupéry, Terre des hommes, 1939

7

Coder à l'ère de l'IA

le dernier maillon

Un nouvel étage s'est posé sur les six précédents : une machine qui écrit du code à la demande. La question n'est plus « est-ce que l'IA sait coder ? » (oui, souvent), mais « que reste-t-il à l'humain ? ». On guette une réponse technique, un recoin que la machine ne saurait pas encore coder. La réponse est ailleurs : ce qui reste à l'humain, c'est décider si ce que la machine produit est correct, sûr et à sa place. Et cette capacité à décider, les six étages précédents viennent justement de la construire : vous savez reconnaître un nom honnête, une architecture qui tient et un test qui prouve. Vous n'avez pas fait cette ascension pour rien : c'est ce bagage que l'arrivée de l'IA rend indispensable.

Avant de monter à ce dernier étage, une confusion à lever. « Faire de l'IA », aujourd'hui, ce n'est presque jamais entraîner un modèle : c'est en appeler un déjà entraîné et bâtir autour. Personne ne redessine la carte du monde pour afficher un plan d'accès sur son site : on intègre Google Maps. L'ingénieur IA fait pareil : le modèle, c'est sa carte du monde, dessinée par d'autres. Son travail commence après : ce qu'on envoie au modèle, ce qu'on fait de sa réponse, et la manière de vérifier le tout. C'est le sens du mot engineering dans AI Engineering, le livre de Chip Huyen qui nourrit ce chapitre : tout se joue autour du modèle, pas dedans.

Un chirurgien humain concentré tient seul le scalpel, entouré de bras robotiques qui lui tendent des instruments et d'écrans de code : l'humain décide du geste
L'IA tend les instruments à toute vitesse. L'humain tient le scalpel et décide du geste.

7.1 L'IA génère du probable, pas du vrai

Un modèle de langage ne sait pas ce qui est vrai : il produit, mot après mot, la suite la plus probable de ce qui précède. L'hallucination n'est donc pas un bug à corriger un jour, c'est le mécanisme même qui le fait fonctionner : « tout ce qui a une probabilité non nulle, aussi tiré par les cheveux ou faux soit-il, peut être généré par l'IA ». Demandez-lui l'auteur d'un livre obscur : il vous donnera, avec le même aplomb, un nom plausible et faux.

La confiance se calibre donc selon la nature de ce qu'on lit. La logique d'un raisonnement se juge sur pièce : tout est devant vous. Une référence (nom de fonction, option, API), elle, se vérifie toujours : un nom « qui sonne juste » est exactement ce qu'un générateur de probable sait produire. Bonne nouvelle, l'exécution ne ment pas : appelez une fonction qui n'existe pas et ça plante, langage compilé ou non. Encore faut-il que la ligne s'exécute : une erreur ne se déclare que sur le chemin emprunté, et c'est exactement ce qu'un test garantit. Mais pour les options de configuration, c'est différent : une option inventée est souvent ignorée en silence, car beaucoup de bibliothèques passent les clés inconnues sans broncher. L'erreur d'exécution reste votre meilleure alliée contre l'aplomb du modèle. Le silence, lui, ne prouve rien.

La conséquence gouverne tout le chapitre : on ne fait jamais confiance les yeux fermés, on vérifie, et on conçoit le système autour de cette incertitude plutôt que contre elle.

AI Engineering·Programming Pearls

7.2 Tout se joue dans le contexte

Un modèle se trompe d'abord quand il lui manque l'information. Lui donner le bon contexte au bon moment est devenu la compétence centrale du métier. Le pattern-roi s'appelle RAG (Retrieval-Augmented Generation : générer en s'appuyant sur des documents récupérés). Le principe : chercher les documents pertinents dans une base externe et les coller dans le prompt (le message qu'on envoie au modèle), pour que celui-ci s'appuie sur des faits fournis, pas sur sa mémoire floue.

Concrètement, c'est ce que fait votre assistant de code quand il répond correctement sur un projet entier : le projet ne tient pas dans le prompt, alors l'assistant va chercher les bons morceaux. Chaque outil a sa méthode : Claude Code (l'assistant de code IA d'Anthropic) lance des grep (la recherche du mot exact dans tous les fichiers), Cursor indexe le projet en vecteurs pour chercher par le sens. La recherche par le sens repose sur la fonction embed : elle transforme un texte en vecteur, une liste de nombres qui code son sens, et deux textes qui parlent de la même chose ont des vecteurs voisins :

question = "Où est calculée la TVA dans ce projet ?"

extraits = projet.chercher(embed(question), top_k=3)
# 1. top_k=3 : garde les 3 passages de VOTRE code
#    dont le sens est le plus proche de la question
# → ["function prixTTC(ht) { return ht * 1.2; }", …]
#    trouvé sans le mot « TVA » : la recherche par le sens

prompt = f"Réponds d'après ces extraits : {extraits}\n\n{question}"
# 2. on colle les extraits dans le prompt, avec la question

reponse = modele(prompt)
# 3. le modèle répond en lisant votre code, pas en devinant

Le contexte se soigne aussi dans le prompt lui-même, et la différence est brutale :

# ✗ vague → le modèle devine, réponse imprévisible
"ça marche pas, corrige"

# ✓ rôle + contexte + format imposé → réponse exploitable
"Tu es dev PHP senior. Ce test échoue (message ci-dessous).
 Propose le correctif minimal, en diff, sans réécrire le reste."

Une session avec un assistant recommence de zéro : à chaque fois, il faudrait répéter les conventions du projet, les commandes à lancer, les pièges connus. La parade : écrire ces consignes une fois pour toutes dans un fichier à la racine du projet, le CLAUDE.md de Claude Code, que l'assistant relit au début de chaque session. C'est la différence entre réexpliquer sa mission à un stagiaire chaque matin et lui préparer un bureau où tout est déjà en place. Le métier a baptisé ce travail context engineering : préparer l'environnement de travail de l'assistant, pour que même une question médiocre produise un bon résultat.

Un bon résultat, justement : qui en juge ? Le vrai goulot se cache là, dans l'évaluation, pas dans le modèle. Pour du code, une partie du verdict est automatique : ça compile, les tests passent. Mais ce verdict n'est qu'un plancher, pas une note. Des tests verts ne disent rien de la lisibilité, de la place dans l'architecture, de la faille qui dort. Le vrai jugement du code est multicritère, et c'est l'humain qui le porte.

Pour une réponse en texte, il n'y a même pas de plancher : rien ne plante jamais. La grille est à construire soi-même, question par question :

  • rien d'inventé ?
  • les faits cités sortent-ils des extraits fournis ?
  • le format demandé est-il respecté ?

Sans grille, on itère à l'aveugle, exactement comme du code sans tests (chapitre 5).

Un dernier mot, que vous croiserez dans le métier : le fine-tuning, ré-entraîner un modèle sur ses propres données. Le critère de Chip Huyen : « le fine-tuning corrige la forme, le RAG fournit les faits ». Corriger la forme est un chantier de constructeur de produit IA, coûteux en données, en calcul et en maintenance : celui qui code avec le modèle n'y touchera probablement jamais. Votre remède, à vous, s'appelle toujours contexte : les bons extraits, les bonnes consignes, au bon moment.

AI Engineering·Programming Pearls

7.3 L'IA se pilote en boucle courte

Confiez une fonctionnalité entière à l'IA et vous recevez deux mille lignes plausibles. Si elles ne marchent pas, personne ne sait lequel des dizaines de choix empilés est fautif : ni vous, ni l'IA. Coder avec l'IA n'est pas un sprint, c'est une boucle courte : une tâche atomique (une seule petite chose à la fois), les tests au vert, le diff lu (la liste exacte des lignes que l'IA a ajoutées ou retirées), un commit, puis la suivante.

Chaque maillon de cette boucle descend des étages précédents. Le test d'abord (chapitre 5) devient un contrat exécutable : il transforme « fais un truc qui marche » en « fais passer ces assertions » (les vérifications écrites dans le test), une cible que la machine peut viser et relancer seule. Git (chapitre 5), lui, reprend son rôle de filet : on committe un état propre avant de lâcher l'IA, et le diff dit ce que l'IA a vraiment fait, pas ce qu'elle prétend avoir fait.

L'outil le plus avancé, l'agent autonome (Claude Code en est un), n'est que cette boucle automatisée : récupérer le contexte (le ticket, les fichiers concernés, la dernière erreur), agir, vérifier, recommencer. Tout se joue au troisième temps : la vérification. Elle doit être un signal extérieur et objectif : les tests, le build (la fabrication automatique de l'application), le linter qui relit le code sans l'exécuter. Jamais l'avis de l'agent sur lui-même : il vous dira toujours qu'il a réussi, avec le même aplomb qu'il met à inventer une fonction inexistante. Faire relire le travail par un second agent aux yeux neufs aide, parce qu'il n'a rien à défendre. Mais son verdict reste un avis. Les tests, eux, n'ont pas d'avis.

AI Engineering·Cours · Coder avec l'IA

7.4 L'équipe chirurgicale, enfin réalisable

L'IA ne change pas que la façon d'écrire : elle redessine l'équipe. Brooks rêvait en 1975 d'une équipe chirurgicale : un cerveau qui tient le scalpel (conçoit, décide), entouré d'assistants spécialisés. L'un connaît tout le code, un autre retient chaque recoin du langage, un troisième forge les scripts et les outils.

Le rêve butait sur un dilemme que Brooks jugeait cruel : une poignée de bons esprits garde la cohérence du système, mais avance trop lentement pour les grands projets. L'IA dissout le dilemme : vous restez le seul cerveau, elle fournit les bras. L'assistant connaît tout le code, retient chaque recoin du langage, forge les scripts, et il joue ces trois rôles à la fois. Le modèle imaginé il y a cinquante ans devient praticable le jour où les bras sont une machine. La loi de Brooks (chapitre 6) n'y perd rien : ces bras-là ne coûtent ni formation ni canal de communication. Leur formation tient dans le contexte que vous leur donnez (section 7.2), et ils n'ajoutent personne en réunion.

Souvenez-vous de Conway (chapitre 6) : la forme de l'équipe se copie dans le système. Une équipe redessinée en un cerveau et des bras-machine produira un autre logiciel, et c'est vous qui décidez lequel. Vous êtes le chirurgien en chef, et la métaphore se traduit geste par geste. Les instruments qu'on vous tend, ce sont les diffs générés : vous en acceptez un, vous en refusez deux, vous renvoyez le troisième en précisant la demande. Les gestes que vous décidez, ce sont l'architecture, le découpage, le nom des choses : l'assistant propose, il ne tranche jamais. Et le compte-rendu que vous signez, c'est le commit : votre nom dessus, pas celui de la machine. On ne signe jamais un code qu'on n'a pas relu.

Le Mythe du mois-homme·AI Engineering

7.5 Le jugement humain est le dernier maillon

Si l'IA écrit le code, qu'est-ce qui reste à l'humain ? Tout ce qui précède. Pour juger si le code généré est juste, il faut :

  • comprendre ce qu'il coûte (chapitre 1) ;
  • savoir si le langage l'exprime bien (chapitre 2) ;
  • juger s'il est lisible par le prochain humain (chapitre 3) ;
  • voir s'il tient dans l'architecture (chapitre 4) ;
  • pouvoir le prouver et le livrer (chapitre 5) ;
  • vérifier qu'il sert l'humain, et résiste à l'attaquant (chapitre 6).

L'IA produit le probable. Vous décidez du juste. Plus elle écrit vite, plus votre jugement devient le maillon rare. La frontière du métier passe désormais là : d'un côté, celui qui comprend, teste et sait expliquer chaque ligne qu'il livre. De l'autre, celui qui accepte sans lire et croise les doigts : le métier a déjà un nom pour ça, le vibe coding (coder au feeling). Une seule règle pour rester du bon côté : ne livrez jamais une ligne que vous ne sauriez pas expliquer.

Ce jugement commence même avant la première ligne, dans la façon de poser le problème. Quand un programmeur demanda un jour à Jon Bentley comment trier un fichier sur disque, quinze minutes de questions ont remplacé une semaine de code : le vrai besoin tenait en dix millions de petits entiers distincts dans un mégaoctet de mémoire. La bonne réponse cochait des cases en mémoire au lieu de trier quoi que ce soit. Définir le vrai problème, c'était quatre-vingt-dix pour cent de la bataille. Une IA ne pose pas ces questions à votre place : lâchez-lui « trie ce fichier » et elle livrera consciencieusement le tri d'une semaine, jamais le raccourci de dix secondes.

Ce livre entier n'apprend pas à coder à la place de l'IA : il apprend à savoir quand elle a tort. Rien ne garantit qu'elle pensera, seule, à stocker un montant en centimes (des entiers exacts) plutôt qu'en flottant (des approximations) (chapitre 1) : il faut que vous, vous le sachiez.

AI Engineering·Programming Pearls

↻ la boucle se referme

Voici le vertige : ce jugement-là, l'IA ne peut pas vous le transmettre. Elle imite les réponses de ceux qui savent. Elle ne sait pas quand une réponse est juste. Ce discernement se gagne en faisant ses propres erreurs et en les corrigeant : c'est le seul apprentissage du métier qu'aucun assistant ne raccourcira pour vous. Il faut gravir les sept étages soi-même.

« La question de savoir si les machines peuvent penser est à peu près aussi pertinente que celle de savoir si les sous-marins peuvent nager. » — Edsger Dijkstra

Sept niveaux, un seul fil : des nombres dans le silicium jusqu'au jugement qu'aucun modèle ne remplace. À l'arrivée, on ne décide plus à l'aveugle : on sait quoi faire, pourquoi, , quand et comment. C'est tout le métier, et les fiches ci-dessous tiennent le détail de chaque marche.

Et maintenant ?

Ne relisez pas tout. Si vous débutez, retournez à l'étage où le sol s'est dérobé : chaque chapitre s'adosse à une poignée de fiches (la liste est juste en dessous), et ce sont elles qui tiennent le détail. Si vous codez depuis des années, deux chapitres changeront vos prochaines semaines plus que les autres : l'architecture (chapitre 4), parce qu'elle se décide tôt et se paie longtemps, et l'IA (chapitre 7), parce qu'elle se pilote.

Lire ne suffit pas : ces idées s'apprennent dans les doigts, et chaque étage a son terrain d'entraînement dans les cours interactifs du site, gratuits.

Gardez cette page sous la main : elle ne se lit pas deux fois pareil. Le chapitre sur l'architecture ne dira pas la même chose avant et après votre première vraie refonte. Le jour où une section vous semble évidente, c'est que vous avez monté d'un étage.