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.
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, où, 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 de mémoire, les étages où vivent les données, l'unité qui mesure le coût, 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.
Toute la mémoire tient dans cette image : un ruban de cases de 8 interrupteurs, et un numéro pour aller droit à chacune.
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.
Le plus troublant reste votre programme lui-même : il subit le même sort. Les instructions sont des nombres, rangés dans la même mémoire que les données, et la machine ne fait aucune différence entre les deux. Retenez ça : au chapitre 7, un attaquant s'en servira pour glisser un ordre là où on attendait une donnée.
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 0 et de 1 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, avec deux chiffres possibles au lieu de dix : 0 et 1. En partant de la droite, les colonnes ne valent donc plus 1, 10, 100, mais 1, 2, 4, 8, 16… : les puissances de 2. Lire un nombre binaire revient alors à additionner les colonnes qui portent un 1, en sautant celles qui portent un 0. Dans 101, la colonne de droite (1) et celle de gauche (4) portent un 1, celle du milieu (2) porte un 0 : 4 + 0 + 1 = 5.
Chaque colonne vaut une puissance de 2. On additionne celles dont le bit est à 1.Le mot « Hi! » en mémoire : une lettre par octet. On lit le nombre en additionnant les colonnes dont le bit vaut 1.
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, lui, n'y arrive pas. 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 ? En déplaçant la virgule. Restons en base 10 pour voir le procédé : 1234,5 peut s'écrire 1,2345 × 10³, et 0,00012345 s'écrit 1,2345 × 10⁻⁴. Les mêmes chiffres dans les deux cas, et un exposant qui dit de combien de rangs la virgule a bougé. La machine fait pareil en binaire : elle range d'un côté les chiffres, la mantisse, de l'autre l'exposant, la puissance de 2 par laquelle il faut les multiplier. C'est ça, la « virgule flottante » : la virgule n'a pas de place fixe, l'exposant la fait glisser. Avec le même nombre de chiffres, on code ainsi l'immense comme l'infime.
Un nombre à virgule en mémoire : 64 interrupteurs répartis en trois rôles. Un pour le signe, onze pour l'exposant, cinquante-deux pour les chiffres. C'est là que la place manque et que le motif se coupe (ici, celui de 0.1).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 place réservée aux chiffres est finie : une cinquantaine, pas un de plus. On coupe donc le motif infini, et le dernier chiffre gardé est arrondi. 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 un test d'égalité, 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.
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, quelques 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. Entre la RAM et le disque, en revanche, ce n'est plus un palier, c'est un gouffre (×100 000).
La pyramide ne s'arrête d'ailleurs pas au bord de la machine : en dessous du disque, il y a le réseau, encore dix fois plus lent quand la donnée traverse un océan. Cet étage-là a son chapitre (chapitre 5).
La machine vue en plan : plus la donnée vit loin du centre qui calcule, plus le coursier marche longtemps.La pyramide mémoire : en haut, petit et quasi instantané. En bas, immense et lent. Une donnée qu'on relit souvent doit rester le plus haut possible. Sous le pointillé, on a quitté la machine.
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 à deux dimensions : une grille de cases, en lignes et en colonnes. Vous en croisez tous les jours sans le nommer, une feuille de calcul, un damier, ou la photo du 1.1, qui n'est qu'une grille de pixels. La mémoire, elle, reste une seule longue rangée d'octets, sans lignes ni colonnes : la grille y est donc aplatie, la première ligne de pixels, puis la deuxième posée juste derrière, et ainsi de suite. Demander la case ligne i, colonne j (le a[i][j] des langages) n'est donc 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, 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, c'est sauter une largeur entière de grille à 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.)
Le cadre noir = le lecteur, la zone colorée = la ligne de cache chargée. En ligne, le lecteur reste dedans (1 chargement). En colonne, il en sort à chaque pas et force un rechargement (3).
Même tableau, même calcul, juste l'ordre des deux boucles qui change :
// même logique, deux ordres de boucles sur un tableau 2Dfor (i...) for (j...) a[i][j] // ✓ contigu : la ligne de cache resertfor (j...) for (i...) a[i][j] // ✗ saute une ligne à chaque fois → jusqu'à 10× plus lent
Le même piège se referme tous les jours sur du code ordinaire, sous la forme d'un « je charge tout en mémoire » anodin. Ramener 100 000 lignes d'une base de données pour n'en afficher que dix gonfle les données bien au-delà du cache, et chaque accès repart alors les chercher loin, en RAM ou sur le disque. La performance s'écroule pour la même raison que le parcours en colonne. Déplacer des données coûte plus cher que calculer.
É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). L'écriture se lit sans mathématiques : le O veut dire « de l'ordre de », et ce qui suit entre parenthèses donne la forme de la croissance. O(n) se lit « de l'ordre de n étapes », soit dix fois plus de données, dix fois plus de travail. Ce n'est donc pas une durée en secondes, c'est une forme de courbe. Quatre familles reviennent partout :
O(1) : temps constant, quelle que soit la taille (le plus rapide) ;
O(log n) : temps logarithmique, qui grimpe de moins en moins vite, on coupe le problème en deux à chaque étape ;
O(n) : temps linéaire, deux fois plus de données font deux fois plus de travail ;
O(n²) : temps quadratique, deux fois plus de données en font quatre fois plus (le pire ici).
Le mot « exponentiel » s'emploie souvent à tort pour ce dernier. Le vrai temps exponentiel, O(2ⁿ), est bien pire : chaque élément ajouté double le travail, et cinquante éléments suffisent à dépasser le millier de milliards d'opérations. On le croise rarement, mais les deux ne sont pas la même chose.
Quand l'entrée grandit, c'est la famille qui décide tout : O(n²) explose là où O(1) ne bouge pas. Aucune machine ne rattrape ça.
Le mot logarithme fait peur, l'idée non. log n, c'est le nombre de fois qu'on peut couper n en deux avant de tomber sur 1. Un million coupé en deux vingt fois de suite arrive à 1, donc log d'un million vaut environ 20. D'où le tour de force de la recherche dans une liste triée, où l'on ouvre toujours au milieu : un annuaire d'un milliard de noms se fouille en une trentaine d'étapes. C'est pour ça que la courbe s'aplatit, doubler les données n'ajoute qu'une seule étape.
Le O(1) est l'idée la plus rentable du quotidien. Une table de hachage associe une clé à une valeur, le nom d'un client à sa fiche par exemple, et retrouve la fiche par le nom en temps constant, même sur un million d'entrées. Vous en manipulez déjà sans le savoir : le dict Python, l'objet JS, l'array associatif PHP. Le mécanisme est simple. La clé passe dans une fonction de hachage qui la transforme en numéro de case, alors on ne cherche rien : on calcule où la valeur est rangée et on y va directement, quel que soit le nombre d'entrées.
if (nom in liste_de_1000) // ✗ O(n) : au pire, on parcourt les 1000 nomsif (dictionnaire[nom]) // ✓ O(1) : un seul accès direct, calculé
Ces écarts entre familles ne sont pas des vues de l'esprit. Un ordinateur des années 1970, des milliers de fois plus lent que le vôtre, bat une machine moderne ultra-rapide dès que les données grossissent, si le vieux tourne en O(n) et le neuf en O(n³), un cran au-dessus du quadratique. Aucune puissance de calcul ne rattrape un mauvais algorithme.
D'où 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, la façon dont on range une collection en mémoire. Un tableau pose ses éléments côte à côte, comme des casiers numérotés : atteindre le 500e est instantané, on calcule son adresse et on y va, mais insérer au milieu oblige à décaler tous les suivants d'une case. Une liste chaînée ressemble à une chasse au trésor, où chaque élément dit où trouver le suivant : insérer ne coûte que de rebrancher deux indices une fois qu'on est au bon endroit, mais pour atteindre le 500e il faut suivre la piste depuis le départ. Aucune des deux n'est « meilleure », on choisit selon ce qu'on fait le plus, lire ou insérer.
Insérer au milieu : le tableau décale tout ce qui suit. La liste ne rebranche que 2 liens. Pour atteindre directement le n-ième, c'est l'inverse (le tableau gagne).
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. Prenons une boucle, l'instruction qui répète le même bloc de code un certain nombre de fois. Si on lui fait recalculer la longueur d'un texte à chaque tour, 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 tourconst n = longueur(texte); // ✓ comptée une foisfor (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, ces « si telle condition, alors… » qui s'écrivent if dans le code. 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.
Personne ne devinerait ça, et des effets cachés de ce genre, il y en a beaucoup. Sur la performance, l'intuition se trompe souvent. Le seul moyen fiable de savoir si un code est rapide, c'est de le mesurer, c'est-à-dire le chronométrer pour de vrai, jamais de le deviner.
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 à la plus profonde. La dernière dit tout : nommer une chose, c'est étendre ce qu'on peut penser.
Deux mots vont revenir à chaque paragraphe, autant les poser tout de suite. Une variable est un nom donné à une case de la mémoire, pour y ranger une valeur et la retrouver plus tard. Une fonction est une recette écrite une fois pour toutes : on lui donne des ingrédients, ses arguments, elle rend un plat, sa sortie. total(prix, quantite) reçoit deux arguments et renvoie leur produit. On l'appelle autant de fois qu'on veut, elle refait le même travail.
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 (vrai ou faux), la variable est la donnée. La copier dans une autre variable duplique la valeur, et les deux vivent séparément.
Un objet, lui, est autre chose, et le mot mérite qu'on s'y arrête : tout le chapitre 4 reposera dessus. L'idée est simple, garder ensemble ce qui va ensemble. Plutôt que de trimballer séparément le nom d'un client, son adresse et son solde, on réunit le tout dans un seul paquet, avec les actions qu'on peut lui demander. Les données qu'il porte sont ses champs (nom, adresse, solde), les actions qu'il sait faire sont ses méthodes (envoyerFacture()).
Pour un objet, comme pour une liste ou un dictionnaire, la variable ne tient qu'une poignée vers la donnée, un pointeur, c'est-à-dire l'adresse mémoire du chapitre 1. La copier copie la poignée, pas la donnée. En mémoire, ce client n'existe qu'une seule fois, un seul paquet de 0 et de 1 quelque part dans la rangée d'octets, et les deux variables ne contiennent chacune que le numéro qui y mène : deux noms pour une seule donnée.
En haut, la valeur se recopie (cases indépendantes). En bas, a et b partagent la même donnée, donc modifier par b se voit par a.
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
Dans la plupart des langages (PHP, JavaScript, Python, Java), un objet passé à une fonction n'est pas recopié : la fonction modifie l'original. Confondre valeur et référence est une source inépuisable de bugs « j'ai changé une copie et l'original a bougé ». Les distinguer 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.
Avant de dire ce qu'est un type, disons à quoi il sert. Si vous écrivez surtout du JavaScript, du Python ou du PHP sans déclarer de types, vous vivez très bien sans en écrire un seul. Le langage accepte tout, et vous découvrez la faute quand le code tourne.
function total(prix, quantite) { return prix * quantite; }
total("12", 3) // 36 : le "12" venu du formulaire marche par accident
total("douze", 3) // NaN, « pas un nombre », qui voyage jusqu'à la facture
Un type, c'est cet engagement écrit noir sur blanc : « ici, il y aura un nombre ». Le compilateur la relit avant que le code tourne et refuse de démarrer si elle est trahie. C'est exactement ce que TypeScript ajoute à JavaScript : la relecture que le langage n'avait pas. Vous ne gagnez pas en vitesse de frappe, vous gagnez le moment où la faute apparaît : devant vous, pas devant l'utilisateur.
Un type rend un second service, moins visible et plus rentable au quotidien : il documente. envoyer(destinataire, message) ne dit pas ce qu'il attend. envoyer(client: Client, message: string) le dit, et l'éditeur de code, qui connaît Client, vous propose ses champs pendant que vous tapez. Cette ligne d'en-tête, la signature de la fonction, devient la doc, et cette doc ne peut pas mentir puisqu'elle est vérifiée.
Reste à savoir ce qu'est vraiment un type. On les croise partout sous des noms courts : int pour les nombres entiers, string pour le texte, boolean pour vrai ou faux. 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, où la barre se lit « ou » (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 des deux. L'intersection (A & B) ne garde que celles qui appartiennent aux deux, ce qui sert à exiger un objet qui coche deux exigences à la fois, par exemple avoir les champs d'un client et ceux d'un compte connecté.
Un type, c'est l'ensemble des valeurs permises. Combiner deux types = les unir (tout) ou les intersecter (le chevauchement).
Cette vision explique aussi le typage structurel : pour savoir si un objet a le droit d'entrer dans un type, on ne regarde pas l'étiquette qu'il porte, mais ce qu'il contient. En TypeScript, ce sont ses champs : tout objet qui a un x et un y est un Point, même s'il n'a jamais entendu parler de Point. En Go, ce sont ses méthodes, les actions qu'il sait faire. Une interface y est une liste d'actions exigées : si elle demande Lire() et Fermer(), tout type qui sait faire ces deux-là y répond, sans avoir jamais à le déclarer.
interface Point { x: number; y: number }
function longueur(p: Point) {…} // longueur attend un Point : un x et un yconst p = { x: 3, y: 4, z: 5 };
longueur(p) // ✓ ok : la forme {x, y} y est, le z en trop est ignoré
C'est l'inverse du typage nominal (Java, PHP), où l'objet doit déclarer noir sur blanc implements Point, « je suis un Point », pour être accepté. Ici, la forme suffit.
Reste la meilleure idée d'Effective TypeScript, et elle dépasse de loin TypeScript. Le décor : JSON.parse transforme en objet un texte venu du dehors, la réponse d'un serveur ou un fichier, et personne ne garantit ce qu'il contient. Face à une donnée dont on ignore la forme, il n'y a que deux postures. Prétendre qu'on sait, et le langage se tait. Admettre qu'on ne sait pas, et il exige une vérification avant le premier usage.
let a: any = JSON.parse(txt); a.client.nom // ✗ « fais-moi confiance » : aucune vérification, ça plante à l'exécutionlet b: unknown = JSON.parse(txt); b.client // ✓ erreur à la compilation : 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. C'est exactement la posture qu'on retrouvera devant une saisie d'utilisateur au chapitre 7, puis devant une réponse d'IA au chapitre 8 : ne jamais traiter comme sûr ce qu'on n'a pas vérifié.
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. C'est plus banal qu'il n'y paraît. bouton.addEventListener('click', maFonction) veut dire « quand on clique, exécute cette fonction-là », et [1, 2, 3].map(doubler) veut dire « applique cette fonction-là à chaque élément de la liste ». Dans les deux cas, une fonction voyage comme une donnée. Cette seule idée débloque trois outils, qui paraissent magiques tant qu'on n'en a pas vu le mécanisme :
la closure : une recette qui repart avec son propre carnet. Elle se souvient des variables de l'endroit où elle est née, d'un appel à l'autre (un compteur qui garde son total privé) ;
le décorateur : un post-it agrafé sur une recette. Il ajoute un geste sans raturer une seule ligne de l'original (chronométrer, mettre en cache, vérifier des droits) ;
le générateur (yield) : un cuisinier qui vous tend une crêpe, s'arrête, et reprend à la suivante quand vous en redemandez. Il n'a jamais toute la pile dans les mains (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 # n vit à l'intérieur de compteur()def inc():
nonlocal n # « le n du dessus, pas un nouveau »
n += 1
return n
return inc # on renvoie la FONCTION, pas son résultat
c = compteur()
c() # 1
c() # 2 ← n a survécu d'un appel à l'autre
inc emporte son n : à chaque appel il l'incrémente et s'en souvient, d'où 1, 2, 3… C'est ça, une closure.
Ce mécanisme est partout : JavaScript, Python et Go capturent la variable tout seuls, PHP demande de le dire explicitement (use (&$n)). Son intérêt se mesure à ce qu'il remplace. Pour qu'une fonction se souvienne d'une valeur d'un appel au suivant, il n'y a sinon qu'une variable globale ou un static :
avec un static : un seul état, partagé par tous les appels de la fonction ;
avec une closure : un état neuf à chaque appel de compteur(), donc autant de compteurs indépendants qu'on veut.
Au fond, une closure, c'est une machine à distribuer des carnets privés, autant qu'on en demande.
Le décorateur en découle directement. Chronométrer une fonction sans toucher à son code, par exemple :
def chrono(f): # prend une fonction, en renvoie une autredef emballage(*a): # *a : accepte les arguments de f, quels qu'ils soient
t = time()
r = f(*a) # on exécute la fonction d'origine
print(time() - t) # et on affiche le temps qu'elle a prisreturn r
return emballage
@chrono # @ : emballe lent(), qui est maintenant chronométrédef lent(): ...
lent() # s'exécute normalement, et affiche sa durée au passage
Le générateur est le plus contre-intuitif des trois. Il é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 suivantefor l in lignes(mon_fichier): # les lignes arrivent une par une, à la demande
traiter(l)
# → on lit un fichier de 50 Go sans jamais le charger entier en mémoire
Le générateur produit une ligne, se met en pause en gardant où il en est, puis reprend à la suivante. Les valeurs sortent au compte-gouttes, jamais toutes d'un coup.
Fluent Python pousse l'idée plus loin : ce ne sont pas seulement les fonctions qui circulent comme des valeurs, ce sont vos objets qui peuvent répondre à la syntaxe du langage. Donnez à l'un d'eux une méthode nommée __len__, et len(mon_objet) marche. Une méthode __iter__, et for x in mon_objet marche, sans rien hériter de personne. Python appelle ça son data model, et la leçon dépasse Python : on ne configure pas un langage, on s'y branche.
2.4 La récursivité : résoudre un problème avec une version plus petite de lui-même
Si une fonction est une recette, la récursivité est une recette qui se cite elle-même. « Pour vider un carton : sortez les objets. Si vous trouvez un carton à l'intérieur, appliquez cette recette au carton trouvé. S'il n'y en a pas, vous avez fini. » Une fonction qui s'appelle elle-même, donc. On croit d'abord à une boucle infinie, mais non. Tout tient en deux temps :
le cas d'arrêt : le carton qui ne contient plus de carton. On le vide, c'est réglé, sans relancer d'appel ;
le cas général : le carton qui en contient un autre. On ramène le problème à une version plus petite de lui-même.
Comme chaque carton trouvé est plus petit que celui d'où il sort, on retombe toujours sur le cas d'arrêt. Sans lui, on ouvrirait des cartons pour l'éternité, comme deux miroirs face à face.
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. Vous n'avez pas à imaginer toute la cascade de cartons emboîtés, seulement celui que vous tenez. L'exemple classique est la factorielle, le produit de tous les entiers jusqu'à n (fact(4) vaut 4 × 3 × 2 × 1). Ce n'est pas le cas d'usage le plus utile, c'est le plus petit où le mécanisme se voit en entier :
def fact(n):
if n <= 1: return 1 # cas d'arrêtreturn n * fact(n - 1) # réduit à un problème plus petit# fact(3) = 3 × fact(2) = 3 × 2 × fact(1) = 6
fact(3) appelle fact(2) qui appelle fact(1) : on descend jusqu'au cas d'arrêt, puis chaque appel rend son résultat en remontant : 1, puis 2, puis 6.
Sous le capot, chaque appel attend le résultat du suivant. Les cartons ouverts s'accumulent autour de vous tant que le plus petit n'est pas vidé : c'est la pile d'appels, qui se dépile ensuite 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, le carton du début en numérique ;
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 : le parcours de dossiers ci-dessus tient en trois lignes récursives, là où une boucle oblige à gérer soi-même la liste des dossiers restant à visiter. La leçon dépasse la récursivité : on écrit bien quand la forme du code épouse celle du problème. Une donnée qui se contient elle-même appelle une fonction qui s'appelle elle-même.
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. Et le "5" qui suit n'a rien d'artificiel : un champ de formulaire rend toujours du texte, même quand l'utilisateur y a tapé un nombre.
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 ===, qui compare sans rien convertir, jamais ==, qui convertit d'abord et compare ensuite. 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.
Le vrai pouvoir d'un langage, c'est de donner un nom à une idée compliquée. 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, puis montons d'un cran.
Premier nom : l'interface, déjà croisée à la section 2.2. Une prise électrique : le mur ne dit pas quel appareil viendra, seulement la forme des trous, et tout ce qui a la bonne fiche se branche, lampe ou aspirateur.
Ce seul mot change la relation entre deux morceaux de code. 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// (r1 => … : une fonction anonyme, appelée quand r1 arrive)
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ésultatconst 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, c'est-à-dire l'activité réelle de l'entreprise. Dans une compagnie maritime, celui qui connaît le métier dit « cargo », « itinéraire », « route », pendant que le code dit « ligne », « booléen », « flag ». Chaque réunion devient alors 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.
Pour la machine, rien n'a bougé : mêmes appels, même attente du réseau. Ce qui a changé, c'est le nom. Un nom vous dispense de repenser aux rouages en dessous, et parfois de traduire pour ceux qui connaissent le problème. Un langage ne se contente donc pas de traduire votre pensée : il décide de ce que vous pouvez penser sans effort.
On sait maintenant tout exprimer. Mais connaître tous les mots d'une langue n'a jamais fait un bon écrivain : une phrase peut être grammaticalement parfaite et rester illisible, comme ces courriers administratifs qu'on relit trois fois. Un programme aussi peut être exact et rester un casse-tête. 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, puisque pour elle tout est déjà des octets dans une rangée de cases (chapitre 1). La propreté n'est donc pas pour la machine, c'est de la communication avec le prochain humain qui ouvrira le fichier. Ce prochain humain, c'est souvent vous. Une recette griffonnée pour soi marche très bien, jusqu'au jour où quelqu'un d'autre doit la suivre, ou qu'on la relit six mois plus tard. Cette propreté se construit en six gestes, qui vont du choix d'un nom de variable à la refonte d'un système entier sans le casser.
Le prochain humain qui ouvrira ce fichier, c'est souvent vous, six mois plus tard.
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 :
// ✗ à décoder à chaque lecture : majeur, ET solde positif, ET pas suspenduif (age >= 18 && solde > 0 && !suspendu) { ... }
// ✓ le nom EST l'explication, et la règle vit à un seul endroitif (peutPasserCommande(client)) { ... }
C'est le pouvoir de nommer du chapitre 2, redescendu à l'échelle d'une seule ligne.
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 l'appelant ? », c'est-à-dire pour le code qui se sert de votre fonction (souvent vous, dans trois mois). Une carte de restaurant le dit mieux qu'un long discours : vous commandez « un tiramisu », pas « des œufs, du sucre, du mascarpone, et je monterai le tout moi-même ». Prenons un prix à calculer : HT, TVA, remise, port.
// ✗ sur-découpé : 4 micro-fonctions que l'appelant doit enchaîner à la mainconst 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 dedansconst 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, comme la cuisine enchaîne ses quinze gestes derrière la porte. 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é : une ligne sur la carte, tout le travail en cuisine. 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.
À taille égale, la fonction profonde cache le plus : une surface minuscule (prixFinal(c)) pour tout le calcul derrière. La peu profonde oblige à connaître ses quatre rouages.
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. Ce sont les notes dans la marge d'une recette : « ne pas ouvrir le four avant 20 minutes » vaut de l'or, « mélanger les œufs » écrit à côté de la ligne qui dit de mélanger les œufs ne sert à rien.
user.deactivate(); // ✗ désactive l'utilisateur (le code le dit déjà)
timeout = 29_000; // ✓ 29 s : juste sous les 30 s du load balancer, qui coupe au-delà
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, pour ne pas avoir à lire son corps. Les types en disent déjà une partie, et gratuitement (section 2.2), ce qu'elle attend et ce qu'elle renvoie. Le commentaire ne prend le relais que sur ce qu'ils ne savent pas dire. // écrit en base, suppose la liste triée
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.
Une carte SIM a un coin coupé. Elle ne rentre que dans un sens, et aucun message ne vient prévenir qu'on s'y prend mal : il n'y a rien à signaler, puisqu'il n'y a rien à rater. C'est l'idée la plus sous-estimée du code : la meilleure gestion d'erreur, c'est l'erreur qui n'existe pas. Quand une fonction n'y arrive pas, la plupart des langages lui font lever une exception : elle s'arrête net et fait remonter l'erreur à celui qui l'a appelée, à charge pour lui de l'attraper. Plutôt que d'attraper une exception partout, on change la définition de l'opération : « prends les 10 premiers caractères » devient « prends jusqu'à 10 caractères ». Le cas qui faisait exploser n'est plus une erreur, c'est 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 — null, c'est la valeur qui dit « rien ici » : la variable existe bien, mais elle ne tient la poignée d'aucun objet (section 2.1) — renvoyez un objet « invité » qui répond comme un vrai utilisateur :
// ✗ chaque appelant doit se rappeler de tester le nullif (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.
DRY (« Don'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 : les voilà attachés l'un à l'autre, et toucher à l'un oblige désormais à se soucier de l'autre.
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.
On se répète aussi 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.
Tout le monde a déjà vécu ça : l'étiquette du rayon annonce 4,90 €, la caisse en réclame 5,40. Deux endroits prétendaient connaître le prix, un seul a été mis à jour. La parade est la même au magasin et dans un programme : chaque savoir a un seul endroit qui fait foi. Tout le reste va l'y chercher, au lieu d'en garder sa propre copie 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 ? ».
// ✗ chaque nouveau canal rouvre la fonctionif (canal == "email") ... else if (canal == "sms") ...
// ✓ un dictionnaire {canal → notifieur} : un nouveau canal = une entrée, la logique ne bouge pas
notifieurs[canal].envoyer(message)
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.
3.6 Refactorer, c'est changer la forme sans changer le comportement
Refactorer, c'est réécrire une phrase pour qu'elle se lise mieux sans changer ce qu'elle dit. En code : transformer la structure interne sans toucher au comportement observable, c'est-à-dire à ce que voit quelqu'un resté dehors. 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 d'arguments qui voyagent toujours ensemble réclame d'être réuni en un seul objet.
livrer(nom, rue, ville, cp) // ✗ 4 arguments collés partout
livrer(adresse) // ✓ ils formaient un concept : une Adresse
Quatre mots qui ne se quittent jamais finissent par en appeler un seul : la langue l'a fait bien avant nous en inventant le mot adresse. Le code ne fait que rattraper son retard.
Le geste clé tient dans une phrase de Kent Beck : rendre le changement facile, puis faire ce changement devenu facile. Contre-intuitif, parce qu'on commence par ne rien livrer de ce qui était demandé. 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 6).
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 habitude, au lieu d'un grand ménage qu'on repousse sans fin.
On sait écrire une fonction, un fichier propre. Mais mille fonctions propres ne font pas un système clair, comme mille phrases justes ne font pas un livre : il leur manque le plan, celui qui décide quoi dépend de quoi et où passent les frontières. Ce plan, 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
Le chapitre 1 regardait la machine d'en haut, comme une ville vue du ciel. Ici, c'est votre programme qu'on regarde d'en haut : ses quartiers, c'est-à-dire ses dossiers et ses fichiers, ce qui passe de l'un à l'autre, et ce qu'on peut démolir sans que le reste s'écroule.
À 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 questions la découpent. Comment un morceau de code réutilise-t-il le travail d'un autre ? Comment reconnaître les problèmes que d'autres ont résolus mille fois avant nous ? Dans quel sens faire pointer les dépendances ? Comment trancher quand deux bonnes idées s'opposent ? Et au sommet, un seul esprit qui garde la forme.
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
Un mot d'abord, tout le chapitre s'appuie dessus. Une classe est un moule : la classe Utilisateur fixe qu'un utilisateur a un nom, un e-mail, et qu'il sait se connecter. Elle ne contient personne, comme un moule à gâteau ne contient pas de gâteau. Fabriquer un exemplaire à partir du moule s'appelle instancier la classe, ce que fait le mot-clé new dans la plupart des langages. Cet exemplaire, une instance, est l'objet du chapitre 2, avec ses valeurs à lui : Dupont, dupont@exemple.fr.
class Utilisateur { nom; email; seConnecter() }
// le moule : il décrit, il ne contient personne
u = new Utilisateur("Dupont", "dupont@exemple.fr")
// une instance : quelqu'un, avec ses valeurs
Écrire deux fois le même moule serait absurde, d'où la première question de l'architecture : comment un moule réutilise-t-il le travail d'un autre ? Il n'y a que deux réponses. Hériter, c'est dire « un administrateur est un utilisateur », ce qu'écrit le mot-clé extends : le moule enfant reprend tout du parent d'un coup, sans rien demander. Composer, c'est dire « un utilisateur a une façon de se connecter » : un objet à part, qu'on branche et qu'on remplace comme un appareil sur la prise de la section 2.6.
Le premier réflexe pour réutiliser, c'est l'héritage, parce qu'il ne coûte qu'un mot-clé : ni interface à écrire, ni objet à brancher. Le piège se voit sur l'exemple culte de Head First, cette fois avec des canards plutôt que des utilisateurs : tous héritent de voler()… puis débarque le canard de bain, qui en hérite aussi alors qu'il ne vole pas. Le jour où quelqu'un améliore voler() chez le parent, tous les canards changent d'un coup, y compris celui qu'on n'avait pas en tête en écrivant la ligne.
Tous sortent du même moule, donc tous héritent du vol. Le canard de bain aussi, et il y croit.
La composition règle ça : voler() devient un objet à part, 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érarchieclass CanardDeBain extends Canard { } // hérite voler()… mais ne vole pas !// ✓ composition : le comportement est un objet branché de l'extérieur, changeable
canard.comportementVol = new NePeutPasVoler()
« Préférez la composition à l'héritage » ouvre le catalogue des solutions déjà éprouvées, les patterns, qui fait la section suivante.
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. Un menuisier n'invente pas une façon d'assembler deux planches à chaque meuble : il connaît la queue d'aronde, le tenon-mortaise, et il prend celle que la contrainte réclame. Un pattern, c'est ça : un assemblage nommé, éprouvé par d'autres avant vous.
Stratégie : changer d'algorithme à la volée, exactement ce qu'on vient de faire en remplaçant le vol du canard (section 4.1) ;
Observateur : prévenir une liste d'abonnés quand un état change ;
Décorateur : empiler des responsabilités sans hériter.
Aucun n'est sorcier. Une fonction est une valeur comme une autre (section 2.3), donc rien n'empêche d'en ranger plusieurs dans une liste. L'Observateur n'est que ça : 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 ces trois familles valent mieux que les 23 noms appris par cœur :
Création (comment instancier les objets) : Fabrique (Factory), Singleton, Monteur (Builder) ;
Vous ne mémorisez pas 23 solutions toutes faites : vous posez une question, « mon problème est de créer, d'assembler ou de communiquer ? », et il ne reste que deux ou trois assemblages possibles.
Au-dessus de tous, le principe fondateur du catalogue : programmer pour une interface, pas pour une implémentation. L'interface, c'est la liste de ce qu'un objet sait faire ; l'implémentation, c'est la classe précise qui le fait vraiment. On retrouve la prise électrique de la section 2.6 : le mur ne connaît pas l'appareil, seulement la forme des trous. Votre code travaille avec ce qu'un objet sait faire, jamais avec ce qu'il est.
// ✗ pour une implémentation : le code est soudé à une classe précise
exporter(doc) { new PDF().écrire(doc) } // ne sait faire que du PDF// ✓ pour une interface : « quelque chose qui sait écrire() »
exporter(doc, sortie) { sortie.écrire(doc) } // PDF, CSV, HTML… : ce qu'elle FAIT, pas ce qu'elle EST
Le jour où il faudra écrire ailleurs que dans un PDF, vous ne rouvrez pas exporter() : vous lui passez autre chose. La fonction n'a jamais eu besoin de savoir dans quoi elle écrivait.
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 : une queue d'aronde là où il n'y a qu'une seule planche.
Voir les 23 patterns un par un, pour les curieux (facultatif)
Ce livre n'en détaille que trois, et c'est volontaire : les trois familles servent plus souvent que le catalogue. Si vous voulez le catalogue entier, chaque pattern avec son problème, son schéma et le moment où il sert, le plus clair est celui de Refactoring Guru, en français et gratuit à lire.
4.3 Dépendre d'abstractions, inverser les dépendances
Ici, tout se joue sur le sens des dépendances. Dépendre de quelque chose, c'est en avoir besoin pour fonctionner, donc bouger quand ça bouge. D'ordinaire, le code métier appelle la base de données : c'est lui qui dépend d'elle.
Clean Architecture partage le code en deux camps. D'un côté les règles métier, ce qui est propre à votre activité : comment se calcule une remise, quand une commande devient valide, qui a le droit d'annuler. Ça change peu, et c'est là qu'est la valeur. De l'autre la base de données, le framework (l'ossature de code toute prête sur laquelle on bâtit l'application), l'écran. Ça change souvent.
Sa règle de dépendance interdit une seule chose : que le premier camp dépende du second. Sinon, ce qui devait être stable bouge à chaque fois que la technique bouge. D'où l'inversion : le métier déclare une interface, l'infrastructure s'y plie.
Chez vous, ce n'est pas le fabricant d'aspirateurs qui décide de la forme des prises : la maison fixe la forme, et tous les appareils s'y plient. Si chaque aspirateur imposait la sienne, il faudrait casser le mur à chaque achat. Le métier tient le rôle du mur, la base de données celui de l'aspirateur.
// le métier déclare CE DONT il a besoin : une interface, c'est ça, l'abstraction du titreinterface DépôtCommande { sauvegarder(commande) }
// l'infra (base de données, 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
Le métier déclare l'interface, la base s'y plie : la flèche de dépendance se retourne. Le code métier ne dépend plus de rien de volatil.
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. 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. Le cercle central, c'est le cœur métier : les règles que l'entreprise appliquerait avec un carnet et un stylo si le logiciel n'existait pas. Autour, les règles applicatives, l'enchaînement des étapes d'une action complète : vérifier le stock, débiter, envoyer la confirmation. 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).
Une seule règle les tient : un cercle extérieur peut dépendre d'un cercle intérieur, jamais l'inverse, et jamais autrement qu'à travers une interface.
Quatre cercles, une règle : l'extérieur dépend de l'intérieur, jamais l'inverse. Le port (la prise blanche) se pose sur la frontière du dedans (les cercles verts), et l'adaptateur, juste dehors, s'y branche. Un vrai système en expose plusieurs, un par besoin.
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 une simple liste en mémoire. Le service du cœur appelle depot.sauvegarder(commande) sans jamais savoir ce qui est branché. Passer de MySQL à PostgreSQL : brancher un autre adaptateur. Le cœur ne bouge pas.
L'inversion qu'on vient de voir porte une lettre : c'est le D de SOLID, cinq principes de conception réunis sous un acronyme.
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) : ouvert à l'extension, fermé à la modification, comme l'exporter() de la section 4.2 qu'on n'a pas eu à rouvrir pour écrire ailleurs ;
L (Liskov Substitution) : un enfant doit pouvoir remplacer son parent sans surprise, ce que le canard de bain de la section 4.1 ne fait pas ;
I (Interface Segregation) : mieux vaut plusieurs petites interfaces qu'une énorme, sinon c'est une prise à trente trous pour un appareil qui n'en utilise que deux ;
D (Dependency Inversion) : dépendre de l'abstraction, jamais du concret.
L'« acteur », dans le S, c'est le groupe qui réclame un changement. L'exemple est de Martin lui-même : une classe Employé qui calcule la paie, produit le rapport d'heures et s'enregistre en base. Trois tâches, mais trois donneurs d'ordre : la compta pour la paie, les RH pour le rapport, les admins pour l'enregistrement. Le jour où la compta fait modifier un calcul que le rapport utilise aussi, le rapport des RH casse sans que personne ne l'ait demandé.
C'est le principe le plus mal cité des cinq. Beaucoup le réduisent à « une fonction fait une chose », qui est un autre principe, plus bas niveau, venu de Clean Code. Martin corrige lui-même dans Clean Architecture, chapitre 7 : ce qu'on compte, c'est le nombre de groupes servis, pas le nombre de lignes.
La même hygiène de dépendances 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, ces méthodes qui ne font que rendre une valeur, 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. À l'échelle du système comme entre deux objets, la règle ne change pas : ne dépendre que de ce qu'on a le droit de connaître.
// ✗ une chaîne qui traverse trois inconnus
commande.getClient().getAdresse().getVille()
// ✓ on demande ce qu'on veut, pas le chemin pour l'obtenir
commande.villeDeLivraison()
4.4 Pas de « best practice », seulement le moins mauvais des compromis
Plus on monte, moins il y a de réponses universelles. Le livre Software Architecture: The Hard Parts le pose net : « pour les architectes, chaque problème est un flocon de neige », il n'y en a pas deux pareils. 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 mettre en ligne et tester seul, et qui peut tomber sans rien entraîner d'autre. Un service, c'est un morceau de l'application mis en ligne de son côté. Deux services qui partagent la même base de données, 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. La question ne demande pas un gros système pour être utile : sur un premier projet, elle se pose déjà devant un fichier que trois pages appellent. Vingt « microservices » sur une base commune ? Un seul quantum, là encore.
Le quantum, c'est le rayon de la panne : ce qui partage une base tombe d'un bloc.
Le second, « la réutilisation, c'est du couplage ». Reprenez le fichier que trois pages appellent : le quantum demandait ce qui tombe avec lui, ici la question devient ce qui change avec lui. Modifié pour la première page, les deux autres changent sans qu'on l'ait demandé. À l'échelle d'un système, c'est pareil : 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 appels réseau, puisque des morceaux séparés doivent se parler à distance (chapitre 5), l'autre en couplage. On choisit au cas par cas, selon ce qui compte le plus ici : pas de règle, seulement le moins mauvais des compromis.
La plus belle architecture meurt si quarante équipes empilent leurs idées sans coordination. Le cas type, c'est l'API : le guichet de votre application, où les autres programmes déposent leurs demandes, formulaire à remplir et format de réponse compris (le chapitre 7 y revient). Quarante équipes sans se parler, et ce guichet devient la maison qui rend fou d'Astérix, celle du laissez-passer A38 : chaque bureau réclame son propre formulaire, ici l'identifiant change de nom, là la date change de format. Chaque bureau marche. L'ensemble rend fou.
// ✗ 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 laissez-passer A38 : chaque bureau vous renvoie au suivant (Les Douze Travaux d'Astérix, 1976).
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 : ce petit groupe 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 rangé dans la documentation de l'équipe dérive dès les premières semaines, parce que c'est le code qui bouge, pas le diagramme. La vision doit donc vivre dans le code : c'est le Model-Driven Design d'Eric Evans. Reprenez une règle métier de la section 4.3 : « une commande payée ne s'annule plus ». Tant qu'elle est éparpillée, chaque écran qui affiche un bouton Annuler refait la vérification dans son coin, et l'un d'eux finira par l'oublier. Chez Evans, la règle vit dans l'objet Commande lui-même : demandez commande.annuler() sur une commande payée, la réponse est non. Cet objet est une commande du métier, pas une ligne de base de données déguisée. Le modèle n'est plus à côté du code : il est le code.
Un modèle fait d'objets, ça se trie. Evans les range avec une seule question : celui-là, je le suis dans le temps ou je le remplace ? C'est la distinction valeur/référence de la section 2.1, remontée au niveau métier. Une entité a une identité qui lui survit : le client 42 reste le client 42 s'il déménage et change de nom, on le suit dans le temps. Un objet-valeur n'est que son contenu : deux billets de 10 € sont interchangeables, deux adresses identiques sont la même adresse. Posez-la à chaque objet de l'application : la facture, on la suit dans le temps, c'est une entité ; son montant, on le remplace, c'est un objet-valeur. Une question, et chaque objet trouve sa case.
Et ce tri commande deux gestes concrets. Comparer : deux entités se comparent par leur identité, deux clients qui portent le même nom restent deux clients ; deux objets-valeurs se comparent par leur contenu. Modifier : un objet-valeur ne se modifie pas, il se remplace. Retoucher une adresse partagée par deux commandes, c'est le piège de la référence de la section 2.1, les deux commandes déménagent d'un coup ; donner une adresse neuve à la bonne commande, et le piège disparaît.
Reste une conséquence qui dérange. Beaucoup d'équipes séparent les rôles : l'architecte dessine les plans, les développeurs construisent. Si le modèle est le code, cette séparation ne tient plus, concevoir c'est coder. L'architecte qui ne touche jamais au clavier finit par dessiner des plans impossibles à construire. Evans appelle ça les Hands-on Modelers, les concepteurs qui gardent les mains dans le code : la même tête tient le crayon et le clavier.
La forme est posée, les frontières tracées. Mais ce système n'habitera pas une seule machine : le navigateur est ici, le serveur ailleurs, la base parfois sur un autre continent. Entre eux s'étend un monde qu'on n'a pas encore regardé, avec ses lois physiques à lui : le réseau. Avant de livrer quoi que ce soit, il faut savoir comment deux machines se parlent.
« Ne sous-estimez jamais la bande passante d'un break rempli de cassettes fonçant sur l'autoroute. » — Andrew Tanenbaum, Computer Networks
5
Deux machines se parlent
le réseau
Jusqu'ici, tout se passait dans une seule machine. La pyramide de la section 1.2 l'annonçait déjà : sous le disque, il reste un étage, le plus lent de tous. Nous y voici. Un clic suffit : la requête, votre demande de page, part du navigateur, traverse des câbles, des routeurs et parfois un océan, et revient. Ce voyage a ses lois, et elles ne ressemblent à rien de ce qu'on a vu : la distance se paie, les messages se perdent, et personne ne dirige la circulation. Le voyage se raconte en cinq étapes : ce que coûte vraiment la distance, le contrat minuscule qui fait tenir Internet, la fiabilité fabriquée par-dessus, le partage sans chef d'orchestre, et ce que le web paie à chaque page.
Le réseau découpe, numérote, et perd des morceaux en route. Tout l'art du chapitre : faire comme si de rien n'était, en renvoyant une copie.
5.1 La distance ne se compresse pas
Deux nombres décrivent un réseau, et on les confond tout le temps. Imaginez une autoroute. La bande passante, c'est le nombre de voies : combien de données passent de front chaque seconde. La latence, c'est la longueur du trajet : le temps qu'un message met à faire l'aller-retour. Relisez le break de Tanenbaum, il roule déjà sur cette autoroute : ses milliers de cassettes lui font une bande passante énorme, et ses six heures de route une latence terrible. Les pubs de votre fournisseur d'accès ne vantent que la bande passante, à grands renforts de « fibre 2 Gb/s ». Pourtant, c'est la latence qui vous fait attendre.
La différence est physique. La bande passante s'achète : une fibre de plus, des fréquences de plus, des voies en plus. La latence, elle, ne s'achète pas, on ne rapproche pas les villes : elle est bornée par la vitesse de la lumière. Dans la fibre, le signal avance à environ deux tiers de sa vitesse, et un aller-retour Paris–New York ne descendra jamais très en dessous de 60 ms. Aucun abonnement ne raccourcit l'Atlantique.
Or c'est la latence qui commande le web. La mesure fondatrice de Mike Belshe, un des pères du futur HTTP/2, reprise par High Performance Browser Networking : passé quelques mégabits par seconde, doubler la bande passante ne change presque rien au temps de chargement d'une page. Réduire la latence l'accélère presque linéairement. La raison : une page moyenne, c'est des dizaines de petites ressources, images, styles, polices, scripts, donc des dizaines d'allers-retours qui s'additionnent. Le web n'est pas limité par la largeur de la route, il est limité par sa longueur.
La mesure de Belshe : passé quelques mégabits, élargir la route ne sert plus à rien. La raccourcir, toujours.
Comment ce voyage s'organise-t-il ? D'abord, le réseau ne transporte jamais votre message d'un bloc. Il le découpe en paquets : des petits morceaux indépendants, portant chacun l'adresse de destination, aiguillés de proche en proche par des routeurs, ces aiguilleurs dont votre box est le premier. Deux paquets du même message peuvent prendre des routes différentes et arriver dans le désordre.
Ensuite, le génie d'Internet tient dans ce qu'il ne promet PAS. Au milieu de tout, un seul protocole commun, une règle de conversation que toutes les machines acceptent : IP (Internet Protocol). Son contrat est minuscule : « je fais de mon mieux » (best-effort). Un paquet peut être perdu, dupliqué, retardé, réordonné, et IP n'a rien promis d'autre que d'essayer.
Ce contrat remonte jusque dans votre code. Un appel réseau n'est pas un appel de fonction : il peut échouer, traîner, ne jamais répondre, et ce n'est pas un accident, c'est le contrat signé à la base. D'où trois réflexes de développeur, dès la première ligne qui traverse le réseau : fixer un délai au-delà duquel on n'attend plus (le timeout), prévoir de réessayer, et prévoir le message d'erreur, plutôt qu'une page qui tourne sans fin.
En bas, les supports changent depuis 50 ans. En haut, les applications aussi. Le col, lui, n'a pas bougé : IP et son « je fais de mon mieux ».
Pourquoi promettre si peu ? Parce qu'un milieu simple est un milieu universel. La fiabilité se construit aux extrémités, chez l'expéditeur et le destinataire, jamais au milieu (c'est l'argument dit end-to-end). Le col du sablier reste assez pauvre pour courir sur n'importe quel support, y compris ceux qui n'existaient pas à sa naissance : la fibre, la 5G et le satellite sont arrivés sans qu'IP change. Vous y reconnaîtrez le chapitre 4 et son « programmer pour une interface, pas pour une implémentation » : IP est l'interface, et des deux côtés, applications comme supports, tout le monde reste libre de varier.
IP fait de son mieux, mais son mieux ne suffit pas à tout : un virement bancaire ne peut pas se permettre de se perdre en route. Il faut donc combler l'écart entre ce qu'IP promet et ce dont une application a vraiment besoin. C'est le rôle de TCP, posé juste au-dessus d'IP. Son mécanisme copie le recommandé postal. Chaque octet envoyé porte un numéro de suivi. Le destinataire renvoie un accusé de réception pour dire ce qui lui est bien parvenu. Et si un accusé n'arrive pas dans le délai prévu, l'expéditeur renvoie le paquet, sans chercher à savoir pourquoi il s'est perdu. Un paquet perdu, un paquet arrivé en double, des paquets arrivés dans le désordre : ces trois gestes suffisent à tout réparer. L'application au-dessus, elle, n'en voit rien. Elle croit rouler sur une route parfaite, alors que le réseau en dessous ne l'est pas du tout. C'est la fiabilité fabriquée aux extrémités, exactement comme le voulait le sablier.
Ce service a deux prix. Le premier se paie à l'ouverture. Avant le moindre octet utile, les deux machines échangent une poignée de main en trois temps : « je veux parler », « reçu, moi aussi », « reçu ». Cela fait un aller-retour complet, donc 60 ms perdues avant même de commencer. Et il faut le repayer à chaque nouvelle connexion.
Le second prix est plus subtil : à quel rythme envoyer ? Attendre l'accusé de chaque paquet laisserait la route à moitié vide. TCP envoie donc toute une file de camions avant d'avoir des nouvelles du premier, plutôt qu'un camion, silence, puis le suivant. Reste à trouver la bonne longueur pour cette file, qu'on appelle la fenêtre. C'est le produit bande passante × délai : on multiplie le débit par le temps d'aller-retour, et on obtient ce que la route contient à cet instant, tous les camions partis mais pas encore arrivés. Sur 100 Mbit/s avec 60 ms d'aller-retour, presque un mégaoctet voyage en permanence, déjà parti, pas encore confirmé.
La poignée de main coûte un aller-retour avant le premier octet utile. Ensuite, TCP n'attend pas un accusé par paquet : plusieurs voyagent de front, c'est la fenêtre.
Grâce à TCP, vous ne gérez jamais un paquet perdu ou arrivé dans le désordre : tout est déjà réparé avant que votre code ne voie le premier octet. En échange, il vous reste deux délais qu'aucune ligne de code n'effacera : la poignée de main et la fenêtre.
Des millions de connexions se partagent les mêmes câbles, et personne ne distribue les tours de parole. Chaque routeur garde les paquets en attente dans une file, comme les voitures avant un péage. Si tout le monde envoie à fond, la file déborde et le routeur jette les paquets en trop. Chacun retransmet alors ce qu'il a perdu, ce qui rallonge encore le bouchon. Ce cercle vicieux a un nom, l'effondrement de congestion, et il est arrivé pour de vrai : au milieu des années 80, un lien entre Berkeley et un labo voisin est passé de 32 kbit/s utiles à 40 bit/s, soit 800 fois moins. Aucun câble n'était coupé, aucune machine n'était en panne, et pourtant plus rien n'arrivait.
La parade est une règle de conduite que chaque machine s'applique à elle-même, sans arbitre. Accélérer doucement, un cran de fenêtre de plus à chaque aller-retour. Puis, au premier paquet perdu, diviser sa vitesse par deux d'un coup. C'est le comportement d'un conducteur prudent : on reprend de la vitesse petit à petit, et on lève le pied dès qu'on voit des feux stop devant, sans savoir ce qui bloque plus loin. La perte n'est plus seulement un accident à réparer : elle devient le signal que la route sature, et chacun freine tout seul en la voyant.
Monter doucement, diviser par deux à la perte : les dents de scie frôlent la capacité sans s'effondrer. Internet tient parce que chaque machine freine toute seule.
Cette politesse codée s'appelle AIMD (Additive Increase, Multiplicative Decrease : hausse par addition, baisse par division). Les variantes modernes affinent la courbe (CUBIC remonte plus vite, BBR mesure la route au lieu d'attendre la perte), mais le principe n'a pas bougé depuis 1988 : un bien commun sans gendarme, tenu par la retenue de chacun.
Cette prudence, vous l'avez déjà vue à l'œuvre. Lancez un gros téléchargement : la vitesse affichée grimpe pendant les premières secondes au lieu de démarrer au maximum. La connexion ne connaît pas encore la capacité de la route, alors elle commence petit et monte cran par cran. Une connexion ouverte depuis un moment, qui a eu le temps d'élargir sa fenêtre, démarre bien plus vite : d'où l'intérêt d'en ouvrir le moins possible, ce que la section suivante va chiffrer.
HTTP est la langue que parlent navigateurs et serveurs : demander une page, répondre avec son contenu. Mais une requête HTTP ne voyage jamais seule : c'est du texte confié à une connexion TCP, elle-même acheminée paquet par paquet par IP. Redescendons à votre quotidien : un visiteur tape l'adresse de votre site. Avant le premier octet de la page, tout ce qu'on vient de voir se paie, aller-retour par aller-retour. Quatre étapes se succèdent, chacune attendant la précédente.
Le DNS : l'annuaire du web traduit le nom du site en adresse IP, parce qu'un routeur sait acheminer vers une adresse, jamais vers un nom. Un aller-retour.
La poignée de main TCP : les deux machines se mettent d'accord pour ouvrir une connexion fiable, celle de la section 5.3. Un aller-retour.
TLS : le chiffrement, le cadenas du https. Les deux machines échangent leurs clés pour que personne, sur tout le trajet, ne puisse lire ni modifier la suite. Un à deux allers-retours.
La requête : enfin, le navigateur demande la page. Un dernier aller-retour avant le premier octet utile.
Sur 60 ms de latence, comptez autour de 300 ms avant la première réponse. Sur mobile, ajoutez le réveil de la radio : pour économiser la batterie, la puce du téléphone coupe sa liaison avec l'antenne dès qu'elle ne sert plus, et doit la rétablir avant d'envoyer quoi que ce soit, ce qui coûte des dizaines à des centaines de millisecondes de plus.
Chaque étage attend le précédent : le péage s'additionne en allers-retours. C'est lui que HTTP/2 puis HTTP/3 s'emploient à raboter.
Ce péage explique une décennie de bricolages. HTTP/1 ne sait demander qu'une ressource à la fois par connexion, et un navigateur n'en ouvre que six par domaine. Les développeurs ont donc rusé dans deux directions. Réduire le nombre de requêtes d'abord, en collant les fichiers bout à bout et toutes les icônes dans une seule image. Contourner la limite ensuite, en éparpillant les ressources sur plusieurs domaines pour avoir droit à six connexions de plus à chaque fois. HTTP/2 a réglé le problème à la racine : toutes les requêtes voyagent entrelacées sur une seule connexion, donc une seule poignée de main, et une fenêtre qui reste ouverte et large. Ces vieilles ruses sont devenues des anti-patterns : éparpiller sur plusieurs domaines rouvre précisément les connexions que le protocole venait d'économiser. HTTP/3 va plus loin encore : il abandonne TCP pour un transport neuf, QUIC, qui fond la poignée de main et le chiffrement en un seul aller-retour au lieu de deux ou trois.
La morale du chapitre est la règle d'or de la performance web : la requête la plus rapide est celle qu'on ne fait pas. Trois gestes en découlent, et chacun répond à une leçon de ces cinq sections. Mettre en cache, pour ne pas redemander ce qu'on a déjà. Regrouper, pour payer moins d'allers-retours. Rapprocher le contenu du visiteur, enfin, puisque aucun abonnement ne raccourcit l'Atlantique. Vous retrouverez ces trois gestes au chapitre 6, à l'échelle du service entier.
Deux machines savent se parler, à un prix désormais connu : des allers-retours qui s'additionnent, des paquets qui se perdent. Reste à en faire un service qui vit. Car 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 du chapitre 4 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
6
Prouver, livrer, tenir la charge
le flux
Du code qui « marche sur ma machine » à un service qui tient en production, la machine ouverte au public où des milliers de gens l'utilisent pour de vrai, 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.
Sept maillons composent ce flux. Prouver d'abord : écrire le test avant le code, puis traquer les bugs sans rien croire sur parole. Livrer ensuite : garder l'historique de tout ce qu'on écrit, faire circuler le travail au lieu de le stocker, et livrer si souvent que ça n'a plus rien d'un événement. Tenir la charge enfin : encaisser un million de lecteurs, et découvrir ce que coûtent les garanties quand les données ne tiennent plus sur une machine.
Le flux régulier livre sans effort. La file saturée fige tout, même quand chacun est à fond.
6.1 Le test d'abord
Un test est un petit programme qui appelle votre code et vérifie sa réponse : « somme(5, 3) doit rendre 8 ». Écrire ce 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éralisefunction somme(a, b) { return a + b } // ✓ propre, toujours vert
On boucle sans fin : un test qui échoue, le code minimal qui le fait passer, puis on nettoie, et on enchaîne le suivant.
On ne nettoie jamais le code tant qu'un test échoue : d'abord le vert, ensuite le ménage. Le but tient en quatre mots : 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, ce guichet de la section 4.5, du point de vue de celui qui vient y déposer sa demande, pas de celui qui tient le bureau. 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. Les fonctions courtes du chapitre 3 et les dépendances propres du chapitre 4 deviennent alors des conséquences, pas des efforts de volonté.
L'économie confirme la discipline : le même défaut coûte dix à cent fois plus cher quand on le découvre en production plutôt qu'ici, au clavier (Code Complete). Tester tôt n'est pas du zèle, c'est le chemin le moins cher.
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 ». Si un bug a l'air impossible, c'est qu'une de vos certitudes est fausse, et vous ne savez pas encore laquelle. C'est tout ce que dit la règle de Bentley : « déboguer, c'est le plus souvent refuser de croire ». Refuser de croire ce qu'on tient pour acquis, la ligne qu'on a relue dix fois, la configuration qu'on est sûr d'avoir déployée, la donnée qu'on suppose propre. Tant que ces certitudes ne sont pas vérifiées une par une, l'explication reste cachée derrière l'une d'elles.
Rick Lemons, cité par Bentley, racontait que sa meilleure leçon de débogage avait été un spectacle de magie. Six tours impossibles d'affilée, et pas un seul qui le soit vraiment : le magicien attirait simplement le regard du côté où il ne se passait rien. Un bug impossible marche pareil, il suffit de regarder l'autre main.
Nos deux exemples se dénouent alors tout seuls. Le programmeur, assis, tapait son mot de passe sans regarder le clavier ; debout, il regardait les touches, et deux d'entre elles avaient été interverties. Quant au terminal, il vérifiait si la saisie commençait par quit, sa commande pour quitter : « Quito » commence par « quit ».
Votre bug impossible à vous ressemblera plutôt à ceci : tout marche sur votre machine, et rien ne marche en production. Arrêtez de fixer le code qui « ne peut pas » être faux, et posez la seule question utile : qu'est-ce qui change entre les deux ? La réponse est presque toujours ailleurs que dans le code. Un réglage qui n'a pas la même valeur sur les deux machines, un cache qui ressert une vieille réponse, deux requêtes qui arrivent dans l'ordre inverse, une ligne de configuration oubliée au moment de la mise en ligne.
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.
6.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 (un commit : une photo datée et signée de tout le projet, l'unité de base de Git) 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 :
Tout est un objet immuable, nommé par son empreinte. Une branche n'est qu'un pointeur qu'on déplace. Rien ne s'efface vraiment.
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 photographie une version pour le prochain commit. git commit la scelle dans le graphe. D'où la possibilité de ne commiter qu'une partie.
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.
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
Presque plat, puis vertical : 50/50 = 1×, 90/10 = 9×, 99/1 = 99×. La marge n'est pas du gâchis, c'est elle qui fait circuler le travail.
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.
6.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 6.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.
Déployer a déjà eu lieu : les deux versions tournent. Livrer n'est qu'un coup d'aiguillage, et une bêtise se répare dans l'autre sens, d'une bascule.
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 (retour à la version d'avant), d'un hotfix (correctif poussé en urgence) 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.
6.6 La lecture tient 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.
Un mot sur la façon dont ces données sont rangées, tout le reste en dépend. Une base relationnelle les range en tables, des grilles à colonnes fixes, une par sorte de chose : une table Clients, une table Commandes. Chaque ligne porte un numéro à elle, sa clé. Une commande ne recopie donc pas le client, elle garde son numéro. Recoller les deux à la lecture, c'est une jointure : pour chaque commande, aller chercher la ligne du client qui porte ce numéro. Rien n'est écrit deux fois, et c'est ce recollage qui coûte quand les tables grossissent. (Rien à voir avec la table de hachage du 1.3 : même mot, autre objet.)
La commande ne stocke que le numéro du client. La jointure va chercher la ligne correspondante et recolle les deux à la lecture.
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.
L'asymétrie en une image : en copiant, chaque écriture doit toucher toutes les copies. En découpant, elle n'atterrit que sur sa tranche.
C'est ici que les ennuis commencent : une donnée éclatée sur plusieurs machines, c'est exactement la situation où les garanties meurent.
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.
Vert + vert = rouge : chaque transaction a validé un monde où la règle tenait encore. Ensemble, elles la cassent sans déclencher la moindre erreur.
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 ».
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
7
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à.
Le bon logiciel se prend en main sans notice, par tout le monde, y compris ceux qu'on oublie.
7.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.
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 (une adresse de l'API, comme /users/42) devine toutes 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 } }
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 :
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.
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.
Le même dessin que le quantum (ch. 4), côté humains : une table partagée couple les trois équipes de force. Trois bases, et chacune livre seule.
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.
7.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
De 3 à 6 personnes, les canaux passent de 3 à 15 : chaque arrivée multiplie les traits, pas seulement les bras. À 15 personnes : 105 canaux.
« 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.
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 : transformer les caractères spéciaux en texte inoffensif (le < d'une balise devient <, que le navigateur affiche au lieu de l'exécuter). 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.
La même entrée piégée, la même couche qui cède : seul l'empilement change l'issue. C'est tout le pari de la défense en profondeur.
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.
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
8
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.
L'IA tend les instruments à toute vitesse. L'humain tient le scalpel et décide du geste.
8.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.
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 (un éditeur de code dopé à l'IA) 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 modèle ne pioche plus dans sa mémoire floue : il lit les extraits qu'on vient de lui tendre, puis répond. D'un outil à l'autre, seule l'étape ① change : grep cherche le mot exact, embed cherche le sens.
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 6).
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.
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 6) 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 6), 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.
Tant que c'est rouge, la boucle rejoue. Au vert seulement, on lit le diff, on committe, et on prend la tâche suivante.
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 7) 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 8.2), et ils n'ajoutent personne en réunion.
Souvenez-vous de Conway (chapitre 7) : 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.
vérifier qu'il sert l'humain, et résiste à l'attaquant (chapitre 7).
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.
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 huit é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
Huit 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, où, 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 8), 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.
Coder avec l'IA (chapitre 8) : le cours du même nom, dix-neuf leçons de méthode.
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.