Bibliothèque · Résumé et avis

Code Complete

Steve McConnell. Le manuel qui transforme l'instinct de construction en pratique vérifiée.

FR EN
Couverture de Code Complete, Steve McConnell, 2e édition

Code Complete

Code Complete: A Practical Handbook of Software Construction

7.4 /10

« Presque mille pages sur l'acte d'écrire du code : daté dans les exemples, intemporel dans les principes. »

  • AuteurSteve McConnell
  • VOMicrosoft Press, 2004 · 960 pages
  • Édition FREn anglais uniquement
  • Fiche~10 min de lecture
Notation du livre sur 5 dimensionsIdées9/10Applicable8/10Lisibilité6/10Actualité7/10Exemples7/10

L'encyclopédie praticienne de la construction logicielle : les pratiques qui réduisent la complexité, données empiriques à l'appui.

Pourquoi ce livre

Il y a des livres de développement qu'on lit parce qu'ils parlent de technologies, et il y a ceux qu'on lit parce qu'ils parlent du métier. Code Complete appartient à la seconde catégorie, et c'est pour ça qu'il trône encore dans les listes de lecture obligatoires vingt ans après sa sortie.

Steve McConnell, en 2004, avait un objectif déclaré : réduire l'écart entre les pratiques des chercheurs et des professeurs d'un côté, et les pratiques communes de l'autre. Les études existaient. Les résultats empiriques existaient. Mais ils restaient enfermés dans des journaux académiques que personne ne lisait. Code Complete a fait le travail de traduction : 960 pages de données converties en conseils applicables le lendemain matin.

Le livre ne couvre pas un langage, pas un framework, pas une méthode. Il couvre la construction logicielle : l'acte même d'écrire du code correct, lisible et maintenable. C'est à la fois son atout principal et la source de son seul vrai défaut : 960 pages, c'est long, surtout quand certains chapitres traitent de Visual Basic 6 comme d'une technologie d'avenir.

Les idées qui restent

1La construction, c'est presque tout

Le mot "construction" revient partout dans le livre parce que McConnell lui donne un sens précis : la construction, c'est l'ensemble des activités liées à l'écriture du code proprement dit. Le débogage, les tests unitaires, la conception détaillée, l'intégration. Pas l'architecture de haut niveau, pas la gestion de projet, pas les exigences clients.

C'est la seule activité garantie d'exister sur n'importe quel projet. On peut zapper l'architecture formelle. On peut zapper les réunions de design. On ne peut pas zapper l'écriture du code. Et selon la taille du projet, cette phase représente entre 30 et 80 % du temps total (ch. 1).

La plupart des formations et des livres techniques parlent d'architecture, de patterns, de processus, et laissent le moment effectif d'écrire une fonction sans aucun encadrement. Code Complete comble ce vide. Quand McConnell parle de "Software's Primary Technical Imperative" (l'impératif technique primaire du logiciel), il ne parle pas de microservices ou de clean architecture. Il parle de ce qui se passe ligne par ligne, routine par routine.

2Dompter la complexité : l'impératif central

Edsger Dijkstra, informaticien néerlandais et prix Turing 1972, formulait le problème en 1989 : le ratio de complexité entre le plus petit et le plus grand programme qu'un être humain puisse concevoir est de 1 à 10⁹. Aujourd'hui, ce ratio avoisine 1 à 10¹⁵. Aucun cerveau humain ne peut tenir un logiciel complet en tête.

C'est la thèse centrale de Code Complete, formulée sans détour au chapitre 5 : "gérer la complexité est le sujet technique le plus important du développement logiciel." Tout le reste du livre en découle. Les conseils sur les noms de variables, sur la taille des routines, sur l'encapsulation n'ont pas d'autre justification que de réduire la quantité de choses qu'un développeur doit garder simultanément en mémoire de travail.

Fred Brooks distinguait deux types de difficultés. Les difficultés "accidentelles" ont en grande partie disparu : les compilateurs cryptiques des années 70, la programmation en lot où on attendait les résultats du lendemain, la gestion manuelle de chaque octet de mémoire. Les difficultés "essentielles" restent intactes : la complexité du monde réel qu'on essaie de modéliser dans du code. Ce sont elles que vise le livre.

La conséquence pratique : toute décision de design se juge d'abord à cette aune. Est-ce que ça réduit ce que je dois garder en tête pour comprendre ce bout de code ? Si oui, c'est probablement bon.

Un développeur calme tient un fil démêlé entre deux doigts ; juste derrière lui, un collègue est entièrement englouti dans une pelote géante de câbles emmêlés, une main qui dépasse
Tout le livre tient là : chaque règle ne sert qu'à réduire ce qu'on doit garder en tête. Le fil démêlé contre la pelote géante.

3ADT : parler le langage du problème, pas de la machine

Imaginez un module qui gère la police de caractères d'un éditeur de texte. La version naïve ressemble à ça :

// Activer le gras
$currentFont['attribute'] = $currentFont['attribute'] | 0x02;

Le développeur qui lit ce code doit savoir que 0x02 est le bit du gras, que le | est un OR binaire, et que attribute encode plusieurs propriétés en même temps. Il doit raisonner machine.

La version avec un ADT (Abstract Data Type, type de données abstrait : une structure dont on n'expose que les opérations, pas la représentation interne) ressemble à ça :

$currentFont->SetBoldOn();

Même résultat, zéro bit à comprendre. Le code parle le langage du problème : "activer le gras". La représentation interne peut changer, passer d'un entier à un tableau de propriétés ou à un objet dédié, sans toucher une seule ligne d'appelant.

Une étude de Woodfield, Dunsmore et Shen (1981) a mesuré l'impact : des développeurs travaillant sur du code organisé en ADT obtenaient des scores de compréhension 30 % plus élevés que sur du code fonctionnel équivalent. P. J. Plauger, auteur de référence sur les bibliothèques C, résume le principe : "Ce n'est pas une abstraction si vous devez regarder l'implémentation sous-jacente pour comprendre ce qui se passe." (ch. 6)

4Une routine, une seule raison

McConnell présente au chapitre 7 une routine nommée HandleStuff(). Elle a 11 paramètres, ne fait rien de précis, lit et modifie des variables globales, ne se défend pas contre une division par zéro, contient des constantes magiques dispersées et deux paramètres qui ne servent à rien. Il pose la question : "combien de problèmes voyez-vous ?" La réponse attendue était 10. Il en a listé 12.

La raison principale de créer une routine, selon lui, n'est pas la réutilisation. C'est réduire la complexité intellectuelle : le fait de pouvoir appeler DeviceUnitsToPoints(x) au lieu de réécrire la formule de conversion partout. Quand la formule doit changer, il suffit de toucher un seul endroit plutôt que les 12 endroits où elle était dupliquée.

Les données empiriques sont parlantes. Une routine à cohésion fonctionnelle (elle fait une seule chose, et son nom le dit exactement) tient bien mieux dans le temps. Il faut lui ajouter une deuxième notion, le couplage : à quel point une routine est accrochée aux autres pour fonctionner. On veut donc beaucoup de cohésion et peu de couplage.

Une étude de Selby et Basili (1991) sur 450 routines l'a chiffré : celles qui mêlaient le plus de couplage au moins de cohésion avaient 7 fois plus d'erreurs et coûtaient 20 fois plus cher à corriger que celles au profil inverse. Pas une règle de style : un facteur de risque mesuré.

La règle pratique : si vous ne pouvez pas nommer une routine avec un verbe et un complément précis (GetCurrentFont(), CalculateMonthlyPayment()), c'est que la routine essaie de faire trop de choses.

Le nommage des variables obéit au même principe. McConnell formule la règle au chapitre 11 : "Les variables temporaires sont un signe que le développeur n'a pas encore complètement compris le problème." Une variable nommée temp dans un calcul de racines d'équation du second degré n'est pas neutre : c'est la preuve que celui qui l'a écrite n'a pas encore mis un mot sur ce que la valeur représente. Renommer en discriminant, et l'algorithme se lit tout seul.

Quadrant cohésion-couplage : la routine à faible cohésion et fort couplage cumule 7 fois plus d'erreurs et 20 fois plus de coût de correction (Selby et Basili, 1991). COHÉSION forte faible forte cohésion + faible couplage ✓ le profil robuste une chose, peu de liens forte cohésion + fort couplage faible cohésion + faible couplage faible cohésion + fort couplage 7× erreurs · 20× coût le fourre-tout accroché COUPLAGE faible fort
Sur 450 routines (Selby et Basili, 1991), celles qui mêlaient faible cohésion et fort couplage cumulaient 7 fois plus d'erreurs et coûtaient 20 fois plus cher à corriger.

5La barricade : où les données sales s'arrêtent

Le principe de "programmation défensive" est souvent mal compris. Il ne s'agit pas de vérifier partout tout le temps. C'est exactement le contraire.

McConnell propose la barricade : une ligne dans votre code qui sépare le monde extérieur (données non validées, saisies utilisateur, réponses d'API) du monde intérieur (fonctions métier qui peuvent faire confiance à leurs arguments). Les classes qui valident les données sales vivent d'un côté de la barricade. Les classes qui utilisent des données propres vivent de l'autre.

La barricade : validation côté sale, confiance côté propre CÔTÉ SALE saisie utilisateur · API · fichiers Classes de validation gestion d'erreur (if / try) BARRICADE CÔTÉ PROPRE logique métier · données sûres Classes domaine assertions (on suppose valide) données propres seulement
Côté sale : gérer les erreurs attendues. Côté propre : asserter ce qui ne devrait jamais être faux.

La distinction mérite un aparté. Une assertion (assert($valeur > 0)) documente une hypothèse qui ne devrait jamais être fausse si le programme est correct. Elle n'est pas là pour gérer les entrées utilisateur : c'est un filet de sécurité interne, souvent désactivé en production. La gestion d'erreur, elle, est là pour les situations normales-mais-inattendues : un fichier absent, une API qui ne répond pas.

Andy Hunt et Dave Thomas, auteurs de The Pragmatic Programmer, ont une formule que McConnell cite au chapitre 8 : "Un programme mort fait normalement beaucoup moins de dégâts qu'un programme estropié." Mieux vaut un crash propre qu'un calcul silencieusement faux.

Et dans mon code PHP/Symfony, concrètement ?

La barricade, en Symfony, c'est le contrôleur. Les données brutes (la requête HTTP) entrent dans un DTO porteur des règles de validation, le composant Validator les vérifie, et toute erreur est gérée là, sur place (une réponse 422). Passé cette ligne, le service métier reçoit un objet déjà validé et n'a plus à se méfier de rien.

// CÔTÉ SALE : le contrôleur reçoit la requête brute et la valide.
final class CommandeController {
    public function creer(Request $req, ValidatorInterface $validator): Response {
        $dto = CreerCommandeDto::depuis($req);          // données brutes
        $erreurs = $validator->validate($dto);          // la barricade
        if (count($erreurs) > 0) {
            return new JsonResponse((string) $erreurs, 422);   // erreur gérée ICI
        }
        $this->service->passer($dto);                   // au-delà, tout est propre
        return new JsonResponse(status: 201);
    }
}

// CÔTÉ PROPRE : le service métier fait confiance à ses arguments.
final class CommandeService {
    public function passer(CreerCommandeDto $dto): void {
        assert($dto->quantite > 0);   // filet interne, PAS du contrôle d'entrée
        $commande = new Commande($dto->email, $dto->quantite);
        // ... logique métier, point. Aucune re-validation.
    }
}

L'assert() côté propre n'est pas du contrôle d'entrée (le Validator s'en est chargé) : c'est le filet interne du chapitre, désactivé en production, qui dit « si on arrive ici avec une quantité négative, c'est un bug chez nous, pas une saisie invalide ».

6La qualité réduit les coûts (et c'est contre-intuitif)

C'est l'idée la plus importante du livre pour n'importe quel responsable technique. McConnell la nomme "le principe général de la qualité logicielle" : améliorer la qualité réduit les coûts de développement. Pas uniquement les coûts de maintenance. Les coûts totaux.

L'ancrage concret : le coût de correction d'un défaut se multiplie par 10 à 100 selon le moment où on le trouve. Une erreur de specs corrigée pendant la phase de specs coûte 1 unité. Détectée en test système : 10 unités. Détectée après la mise en production : 10 à 100 unités (Fagan 1976 ; Boehm et Turner 2004).

Sur un projet ordinaire, le débogage et le retravail absorbent environ 50 % du temps de développement. Pas parce que c'est inévitable, mais parce que la détection se fait trop tard. Le calcul est simple : avancer la détection vers l'amont.

La démonstration repose sur des données dures. Aucune technique de détection de défauts seule ne dépasse 75 % d'efficacité. Les tests unitaires atteignent typiquement 30 % de détection. Les inspections de code formelles en atteignent 60 %.

Une inspection formelle, c'est une relecture structurée et préparée à l'avance, avec des rôles définis (un animateur, un lecteur, des relecteurs) et une checklist, dont le seul but est de repérer les défauts, pas de les corriger sur le moment. C'est la revue de code poussée à son maximum de rigueur, loin du coup d'œil rapide sur une pull request. La seule façon de passer au-dessus de 90 % est de combiner plusieurs techniques.

Taux de détection des défauts par technique Taux modal de détection (Jones 1996, Shull et al. 2002) Inspections formelles 60 % Tests unitaires 30 % Test système 40 %
Aucune technique ne trouve tout. Il faut en combiner plusieurs pour dépasser 90 %.

Ce qui est frappant, c'est le coût de chaque approche. IBM a mesuré en 1995 qu'une heure d'inspection de code coûtait 3,5 heures par défaut trouvé. Une heure de tests coûtait 15 à 25 heures par défaut. IBM encore, via une autre étude : chaque heure d'inspection a évité environ 100 heures de travail associé, tests, corrections et retours clients inclus (Holland 1999). Capers Jones a analysé des milliers de projets : tous ceux qui avaient atteint une efficacité de suppression des défauts supérieure à 99 % avaient utilisé des inspections formelles de code.

Ça ne veut pas dire qu'il faut abandonner les tests. McConnell a une métaphore au chapitre 22 qui résume le problème : "Si vous voulez maigrir, n'achetez pas une nouvelle balance : changez votre alimentation. Si vous voulez améliorer votre logiciel, ne testez pas juste davantage : développez mieux."

7L'humilité comme compétence technique

Edsger Dijkstra a reçu le prix Turing en 1972 pour sa contribution à la programmation structurée. Son discours de réception s'appelait "The Humble Programmer" (le programmeur humble). La thèse : les meilleurs développeurs savent que leur cerveau est trop petit. L'ego est l'obstacle principal.

McConnell reprend cette idée au chapitre 33 et la formule sans détour : "Les gens qui sont les meilleurs en programmation sont ceux qui réalisent à quel point leur cerveau est petit."

Ce n'est pas un conseil de sagesse zen. C'est une conséquence directe de la complexité logicielle. Un développeur qui se croit capable de tout tenir en tête ne documente pas, ne teste pas, n'écoute pas les revues de code. Il accumule de la dette technique avec la conviction que c'est sous contrôle.

McConnell raconte l'histoire d'une développeuse qui nommait ses variables x, xx, xxx, xx1, xx2. Toutes globales, zéro commentaire. Son manager la croyait brillante parce qu'elle corrigeait rapidement les bugs. Les bugs qu'elle avait elle-même produits.

L'humilité technique a des manifestations concrètes :

  • admettre qu'on ne comprend pas un avertissement du compilateur plutôt que de l'ignorer
  • donner des estimations réalistes plutôt que des estimations plaisantes
  • accepter les revues de code comme une aide plutôt qu'une critique

McConnell cite McCue (1978) : la programmation, c'est 85 % de communication avec des humains et seulement 15 % de communication avec un ordinateur (ch. 33).

8Programmer dans son langage, pas en son langage

La dernière idée majeure du livre vient du chapitre 34, la conclusion. McConnell distingue deux modes :

Programmer en un langage, c'est se limiter aux constructions que ce langage propose nativement. Si le langage n'a pas de types énumérés, on utilise des constantes numériques vagues. Si le langage n'a pas d'assertions, on n'en fait pas. On suit le chemin du moindre effort.

Programmer dans un langage, c'est décider d'abord ce dont on a besoin pour rendre le code lisible et correct, puis trouver comment l'implémenter dans le langage disponible. Si le langage n'a pas d'assertions, on écrit sa propre fonction assert(). Si le langage n'a pas de types fortement typés pour certaines valeurs métier, on crée des classes wrapper.

Le même principe s'applique aux méthodologies. McConnell consacre une section entière (§34.9) à ce qu'il appelle la "religion en développement logiciel" : l'adhésion dogmatique à une seule méthode de design, à un style de commentaires, à l'évitement absolu des variables globales. Sa conclusion est catégorique : "Quelle que soit la forme qu'elle prend, c'est toujours inapproprié."

Ce qui remplace le dogme, c'est la boîte à outils intellectuelle. Chaque technique est un outil, pas une vérité révélée. Le bon développeur sait lequel prendre pour quel problème, et ne cherche pas à tout faire avec un marteau parce qu'il est convaincu que les marteaux sont supérieurs aux tournevis.

9Déboguer avec méthode, pas avec instinct

Le chapitre 23 de Code Complete porte entièrement sur le débogage. Rappel du poids du sujet : le débogage et le retravail absorbent environ 50 % du temps de développement sur un projet ordinaire. La plupart des développeurs le traitent à l'instinct, à la chasse au trésor, à l'essai-erreur. McConnell montre qu'il existe une méthode, et que l'écart entre les bons et les moins bons est mesurable.

Une étude de Gould (1975) a mis neuf développeurs sur les mêmes programmes boguées : les trois meilleurs trouvaient tous les bugs en 5 minutes en moyenne et introduisaient 3,0 nouveaux défauts. Les trois plus lents mettaient 14 minutes et introduisaient 7,7 nouveaux défauts : ils aggravaient activement le code en le « corrigeant ».

La méthode en cinq étapes :

  • stabiliser le bug (le rendre reproductible à coup sûr)
  • formuler une hypothèse sur sa cause
  • tester l'hypothèse avec la modification minimale qui la confirme ou l'infirme
  • corriger
  • chercher les bugs similaires ailleurs

L'insistance de McConnell sur l'hypothèse avant l'action est centrale : "Trouver le défaut et le comprendre, c'est en général 90 % du travail." La majorité des développeurs sautent à l'action et passent 90 % de leur temps à deviner.

McConnell introduit le concept de set psychologique : on voit ce qu'on s'attend à voir. L'exemple classique est l'affiche "Paris in the the Spring" : la plupart des lecteurs passent sur le double "the" sans le voir. Même phénomène en code : un développeur a utilisé deux variables SYSTSTS et SYSSTSTS pendant des centaines d'exécutions en croyant n'en avoir qu'une. Le bon nommage, l'indentation cohérente, la mise en forme rigoureuse ne sont pas des caprices esthétiques : ils réduisent la cécité de débogage (ch. 23).

La technique la plus immédiatement utile : expliquer le bug à voix haute à quelqu'un. C'est ce que McConnell appelle le confessional debugging (le débogage par confession). L'exemple du livre : un développeur commence à expliquer un problème mystérieux à Jennifer, trouve lui-même la réponse au milieu de sa phrase. Jennifer n'a pas dit un mot. "Ce résultat est typique", note McConnell. L'acte de reconstruire la chaîne causale pour quelqu'un d'autre force le cerveau à passer du mode "chercher" au mode "comprendre".

Un développeur assis explique avec sérieux un long listing de code à un petit canard en plastique jaune impassible ; une ampoule s'allume au-dessus de sa tête en pleine phrase
Le débogage par confession : en expliquant le bug à voix haute, on trouve la réponse au milieu de sa phrase. Le canard n'a rien dit.

Mon avis, honnêtement

Code Complete est le livre que j'aurais voulu lire à mes débuts. Pas pour les 960 pages, mais pour les cent premières. La thèse sur la complexité, les ADTs, les routines cohésives, les barricades défensives : ça s'apprend en général par essais et erreurs, sur des projets qui partent en couilles, après des revues de code humiliantes. Le livre court-circuite cette pédagogie douloureuse.

Ce qui a vieilli, c'est réel et visible. Les exemples de code sont en C++, Java, Visual Basic 6 et Pascal. Les chapitres sur le "code tuning" (optimisation bas niveau) parlent de cycles CPU et de pipelines de processeurs : c'est de l'archéologie, et c'est affiché comme tel dans la table des matières. La partie sur les outils de développement (chapitre 30) fait l'effet d'un catalogue de musée informatique.

Ce qui tient, c'est tout ce qui concerne les humains : les chiffres sur les inspections de code, la psychologie du débogage, l'humilité professionnelle, la complexité comme ennemi universel. Ces parties-là n'ont pas bougé d'un millimètre. En 2026, elles sont même plus pertinentes qu'en 2004 : les bases de code sont plus grandes, les équipes plus distribuées, la dette technique plus facile à accumuler.

Pour la lecture : vous n'avez pas besoin des 960 pages. Les parties I, II et VII, plus les chapitres 20 et 21, représentent peut-être 250 pages. C'est suffisant pour en sortir avec les idées importantes. Le reste est de la référence : utile ponctuellement, soporifique en lecture linéaire.

Odilon

Pour qui ?

Lisez-le si

  • Vous débutez et voulez passer de "ça marche" à "ça se lit et se maintient"
  • Vous avez des pratiques issues de l'expérience mais n'avez jamais vu les données empiriques derrière
  • Vous menez des revues de code et cherchez des arguments solides pour convaincre l'équipe
  • Vous avez l'impression que "qualité" et "rapidité" sont forcément en tension

Passez votre chemin si

  • Vous cherchez un guide sur un langage ou un framework spécifique
  • Vous avez besoin d'exemples de code 2026 pour les parties bas niveau
  • Vous avez déjà lu Clean Code et Working Effectively with Legacy Code : vous avez couvert l'essentiel

Pour aller plus loin

Deux fiches complètent naturellement celle-ci : Refactoring (Fowler) prolonge les idées du chapitre 24 sur l'évolution du code, et Working Effectively with Legacy Code (Feathers) montre comment appliquer ces principes quand le code dont vous héritez ressemble à HandleStuff() étendu sur 50 000 lignes. Pour mettre en pratique les idées des chapitres 22 à 24, le cours Tests automatisés démarre de zéro en PHP.

Commentaires (0)

Voir toute la bibliothèque

D'autres fiches arrivent : un livre à la fois, la substantifique moelle seulement.