La loi des abstractions qui fuient
Nb : j’ai toujours été très admiratif de Joel Spolsky, véritable visionnaire du développement logiciel et entrepreneur exceptionnel (créateur de Stack Overflow ET Trello qu’il a revendu $425M à Atlassian). Ses articles, d’une clarté et d’une profondeur remarquables, ont façonné la réflexion de toute une génération de développeurs. Ce texte sur la « Loi des Abstractions qui Fuient » reste l’un des concepts les plus fondamentaux et intemporels de l’informatique – un véritable chef-d’œuvre d’analyse technique qui explique pourquoi la programmation devient paradoxalement plus complexe malgré nos outils toujours plus sophistiqués. Bien qu’il l’ait retiré de son wiki, cet article légendaire est heureusement toujours disponible ici, sur Webarchive. Un must-read absolu pour tout développeur qui se respecte.
Je me permets de le copier coller en l’adaptant au goût du jour avec des exemples modernes, car l’analyse originale de Joel Spolsky reste absolument excellente et d’une pertinence saisissante – cela n’est que mon opinion bien sûr, mais ses réflexions sur les abstractions qui fuient demeurent l’un des concepts les plus fondamentaux pour comprendre pourquoi nos outils toujours plus sophistiqués n’éliminent jamais complètement la complexité sous-jacente.
Il y a une part de magie dans l’ingénierie de l’Internet sur laquelle vous vous appuyez tous les jours. Elle œuvre au niveau du protocole TCP, un des blocs constitutifs fondamentaux de l’Internet.
TCP est un moyen de transmettre des données qui est fiable. Par cela, je veux dire : si vous envoyez un message sur un réseau en utilisant TCP, il arrivera à destination et ne sera ni tronqué ni corrompu.
Nous utilisons TCP pour beaucoup de choses, comme accéder à des pages web ou envoyer du courrier électronique. La fiabilité de TCP est ce qui permet à tous les mails excitants d’escrocs est-africains d’arriver à destination en parfait état. Ô joie.
Par comparaison, il existe une autre méthode de transmission de données appelée IP, qui est peu fiable. Personne ne vous garantit que vos données arriveront à destination et qu’elles ne seront pas endommagées avant d’arriver. Si vous envoyez une série de messages avec IP, ne soyez pas surpris que seulement la moitié d’entre eux arrive, et que certains d’entre eux arrivent dans un ordre différent de l’ordre d’envoi, et que d’autres soient remplacés par des messages alternatifs, contenant peut-être des photos d’adorables bébés orangs-outangs, ou plus probablement beaucoup de fatras illisible ressemblant au champ objet d’un spam taiwanais.
Et voici la part de magie : TCP fonctionne au-dessus d’IP. En d’autres termes, TCP est obligé de se débrouiller pour envoyer des données de manière fiable en utilisant uniquement un outil peu fiable.
Pour illustrer en quoi cela est magique, considérez le scénario suivant issu du monde réel, moralement équivalent quoique quelque peu déraisonnable.
Imaginez que nous ayons un moyen d’envoyer des acteurs de Broadway à Hollywood, qui consiste à les mettre dans des voitures et à les conduire à travers le pays. Certaines de ces voitures s’écrasent, tuant les pauvres acteurs. Parfois les acteurs se saoulent en chemin et se rasent la tête ou se font des tatouages sur le nez, devenant de ce fait trop laids pour travailler à Hollywood, et fréquemment, les acteurs arrivent dans ordre différent de l’ordre initial, car ils ont tous pris des routes différentes. Maintenant imaginez un nouveau service intitulé Hollywood Express, qui livre des acteurs à Hollywood en garantissant (a) qu’ils arrivent à destination (b) dans l’ordre et (c) en parfait état. La part de magie est que Hollywood Express ne dispose d’aucune autre méthode pour livrer les acteurs que la méthode peu fiable qui consiste à les mettre dans des voitures et les conduire à travers le pays. Hollywood Express fonctionne en vérifiant que chaque acteur arrive en parfait état et, dans le cas contraire, appelle la maison mère pour demander qu’un jumeau identique de l’acteur soit envoyé pour le remplacer. Si les acteurs arrivent dans le désordre, Hollywood Express les remet dans l’ordre. Si un grand OVNI faisant route vers la zone 51 s’écrase sur l’autoroute du Nevada, la rendant impraticable, tous les acteurs qui devaient passer par-là sont re-routés via l’Arizona et Hollywood Express n’avertit même pas les réalisateurs californiens de ce qui est arrivé. Pour eux, il semble simplement que les acteurs mettent un peu plus de temps que d’habitude pour arriver, et ils n’entendent même pas parler du crash de l’OVNI.
Voilà, approximativement, la magie de TCP. C’est ce que les informaticiens aiment appeler une abstraction : une simplification de quelque chose de beaucoup plus compliqué qui se trame dans l’ombre. A ce qu’il paraît, une part importante de la programmation des ordinateurs consiste à élaborer des abstractions. Qu’est-ce qu’une bibliothèque de chaînes de caractères ? C’est une manière de simuler que les ordinateurs peuvent manipuler des chaînes de caractères aussi facilement qu’ils manipulent des nombres. Qu’est-ce qu’un système de fichiers ? C’est une manière de simuler qu’un disque dur n’est pas vraiment un ensemble de plateaux magnétiques en rotation capables de stocker des bits à certains endroits, mais plutôt un système hiérarchique de dossiers-dans-des-dossiers, qui contiennent eux-mêmes des fichiers individuels, qui à leur tour contiennent une ou plusieurs chaînes d’octets.
Revenons à TCP. Tout à l’heure, dans un souci de simplicité, j’ai raconté une petite fable et certains d’entre vous ont maintenant de la fumée qui sort par les oreilles parce que cette fable est en train de les rendre fous. J’ai dit que TCP garantissait que votre message arrive à destination. En fait, il ne le garantit pas vraiment. Si votre serpent apprivoisé a mâchouillé le câble réseau relié à votre ordinateur et qu’aucun paquet IP ne peut passer, alors TCP ne peut rien faire contre ça et votre message n’arrivera jamais. Si vous avez rudoyé les administrateurs système de votre entreprise et qu’ils se sont vengés en vous reliant à un concentrateur surchargé, seulement certains de vos paquets IP vont passer et TCP va fonctionner, mais tout sera vraiment lent.
C’est ce que j’appelle une abstraction qui fuit. TCP essaie de fournir une abstraction complète d’un réseau sous-jacent peu fiable, mais parfois le réseau fuit à travers l’abstraction et vous sentez des choses contre lesquelles l’abstraction ne peut pas vraiment vous protéger. Ce n’est qu’un exemple de ce que j’ai intitulé la Loi des Abstractions qui Fuient :
Toute abstraction non triviale, à un certain degré, fuit.
Les abstractions échouent. Parfois un peu, parfois beaucoup. Il y a des fuites. Les choses tournent mal. Cela arrive tout le temps quand vous avez des abstractions. Voici quelques exemples :
- Quelque chose d’aussi simple que de parcourir un tableau à deux dimensions de taille importante peut avoir des performances radicalement différentes si vous le faites horizontalement plutôt que verticalement, en fonction du « sens de la fibre » — une direction peut résulter en beaucoup plus de défaillances de page que l’autre direction, et les défaillances de page sont lentes. Même les programmeurs en assembleur sont censés pouvoir faire semblant de disposer d’un vaste espace d’adressage, mais la mémoire virtuelle leur rappelle que ce n’est qu’une abstraction, qui fuit quand il y a une défaillance de page et que certains accès mémoire prennent beaucoup plus de nano-secondes que d’autres.
- Le langage SQL est censé faire abstraction des étapes des procédures nécessaires pour interroger une base de données, en les remplaçant par la possibilité de définir simplement ce que l’on veut et de laisser la base de données trouver les étapes des procédure pour l’obtenir. Mais dans certains cas, les requêtes SQL sont des milliers de fois plus lentes que d’autres qui sont logiquement équivalentes. Un célèbre exemple de cela est que certains serveurs SQL sont nettement plus rapides si vous spécifiez « where a=b and b=c and a=c » plutôt que si vous spécifiez seulement « where a=b and b=c », même si le résultat est identique. Cette technique était plus pertinente avec les systèmes SQL plus anciens. Aujourd’hui (2025), c’est plutôt une optimisation de niche qui peut occasionnellement aider avec certains moteurs ou configurations spécifiques, mais qui n’est généralement pas nécessaire et peut même être contre-productive. Vous n’êtes pas censé devoir vous occuper de la procédure, mais seulement de la spécification. Mais parfois l’abstraction fuit et mène à des performances horribles et vous devez sortir l’analyseur de plan de requête et étudier ce qui ne va pas et trouver comment rendre votre requête plus rapide.
- Même si certaines bibliothèques réseau comme NFS ou SMB vous permettent de traiter des fichiers sur des machines distantes « comme s’ils » étaient locaux, parfois la connexion devient très lente ou tombe et le fichier cesse de se comporter comme s’il était local, et en tant que programmeur vous devez écrire du code pour prendre cela en compte. L’abstraction « un fichier distant c’est la même chose qu’un fichier local » fuit. Voici un exemple concret pour les administrateurs système UNIX. Si vous placez les répertoires home des utilisateurs sur des disques montés en NFS (une abstraction), et que vos utilisateurs créent des fichiers .forward pour transmettre leur courrier électronique ailleurs (une autre abstraction), et que le serveur NFS tombe pendant que des nouveaux courriers arrivent, les messages ne seront pas transmis car le fichier .forward sera introuvable. La fuite dans l’abstraction aura en fait laissé s’échapper quelques messages. Avec des technologies modernes : même si certaines technologies modernes comme les systèmes de fichiers cloud (OneDrive, Google Drive, Dropbox) ou les conteneurs Docker vous permettent de traiter des ressources distantes « comme si » elles étaient locales, parfois la connexion devient très lente ou tombe et les ressources cessent de se comporter comme si elles étaient locales, et en tant que développeur vous devez écrire du code pour prendre cela en compte. L’abstraction « une ressource distante c’est la même chose qu’une ressource locale » fuit. Voici un exemple concret pour les développeurs modernes : si vous placez vos fichiers de configuration d’application dans un volume Docker monté sur un stockage réseau (une abstraction), et que vos microservices lisent ces configs au démarrage (une autre abstraction), et que le stockage réseau devient indisponible pendant qu’un pod Kubernetes redémarre, le service ne pourra pas démarrer car ses fichiers de configuration seront introuvables. La fuite dans l’abstraction aura en fait cassé votre déploiement. Ou encore : vous développez une application qui stocke ses données dans un dossier « local » synchronisé par OneDrive. Tout fonctionne parfaitement jusqu’au jour où vous travaillez sans connexion internet et que l’application plante mystérieusement parce qu’elle essaie d’accéder à un fichier qui existe « localement » selon l’explorateur de fichiers, mais qui n’est en réalité qu’un stub de quelques octets en attente de téléchargement depuis le cloud.
- En C++, les classes de chaînes de caractères sont censées vous permettre de faire semblant que les chaînes de caractères sont un type de données à part entière. Elles tentent de faire abstraction du fait que les chaînes de caractères sont difficiles à manipuler et vous permettent d’agir comme si c’était aussi facile qu’avec des entiers. Presque toutes les classes C++ de chaînes de caractères surchargent l’opérateur » + » et vous pouvez écrire
s + "toto"
pour les concaténer. Mais vous savez quoi ? Peu importe à quel point elles essaient, il n’y a pas sur Terre de classe C++ de chaînes de caractères qui vous permette d’écrire"toto" + "titi"
, car les chaînes de caractères littérales en C++ sont toujours des char*, jamais des chaînes. L’abstraction a créé une fuite que le langage ne vous permet pas de reboucher. (De manière amusante, l’histoire de l’évolution de C++ au fil du temps peut être décrite comme l’histoire des tentatives pour reboucher les fuites dans l’abstraction des chaînes de caractères. Pourquoi ne pouvaient-ils pas simplement ajouter une classe de chaîne de caractères native au langage lui-même? Cela m’échappe pour le moment). De même, en JavaScript moderne, les frameworks comme React sont censés vous permettre de faire semblant que manipuler l’interface utilisateur est aussi simple que de modifier des variables. Ils tentent de faire abstraction du fait que le DOM est compliqué à manipuler et vous permettent d’agir comme si c’était aussi facile qu’avec des objets JavaScript. Presque tous les frameworks modernes vous laissent écrirecount + 1
dans votre JSX et l’interface se met à jour automatiquement. Mais vous savez quoi ? Peu importe à quel point ils essaient, il n’y a pas sur Terre de framework JavaScript qui vous permette d’éviter complètement de comprendre le cycle de vie des composants, les closures, ou pourquoi votre variablecount
garde mystérieusement sa valeur d’il y a trois re-rendus. L’abstraction a créé une fuite que même les hooks les plus sophistiqués ne peuvent pas reboucher. (De manière amusante, l’histoire de l’évolution des frameworks frontend au fil du temps peut être décrite comme l’histoire des tentatives pour reboucher les fuites dans l’abstraction de la réactivité : d’Angular à React à Vue à Svelte, chacun promettant de « simplifier » la gestion d’état, mais vous devez toujours comprendreuseEffect
, les dépendances, et pourquoi votre composant se re-rend 847 fois quand vous changez un booléen. Pourquoi ne peuvent-ils pas simplement faire en sorte que ça marche comme on s’y attend intuitivement ? Cela m’échappe pour le moment). - Et vous ne pouvez pas conduire aussi vite quand il pleut, même si votre voiture a des essuie-glaces et des phares et un toit et un chauffage, qui tous vous permettent de ne pas vous inquiéter du fait qu’il pleut (ils font abstraction de la météo), mais zut, vous devez faire attention à l’aquaplaning et parfois la pluie est si forte que vous ne pouvez pas voir très loin devant vous et donc vous roulez plus lentement quand il pleut, parce qu’on ne peut jamais faire complètement abstraction de la météo, à cause de la loi des abstractions qui fuient.
- Enfin, last not least : vous ne pouvez pas streamer en 4K aussi facilement quand votre voisin lance sa mise à jour Windows, même si votre box internet a la fibre et le WiFi 6 et un débit « jusqu’à 1 Gb/s » qui tous vous permettent de ne pas vous inquiéter du fait que l’internet soit un réseau partagé (ils font abstraction de la bande passante), mais zut, vous devez faire attention aux heures de pointe et parfois la connexion est si saturée que Netflix passe automatiquement en 480p et donc vous regardez vos séries en qualité potato quand tout le quartier est connecté, parce qu’on ne peut jamais faire complètement abstraction de l’infrastructure réseau, à cause de la loi des abstractions qui fuient.
Une des raisons pour lesquelles la loi des abstractions qui fuient est problématique est qu’elle implique que les abstractions ne nous simplifient pas vraiment la vie autant qu’elles le devraient. Quand je forme quelqu’un à être programmeur C++, ce serait bien si je n’avais jamais à lui parler des char* et de l’arithmétique des pointeurs. Ce serait bien si je pouvais aller tout droit aux chaînes de caractères STL. Mais un jour il va devoir écrire le code "toto" + "titi"
, et des choses vraiment bizarres vont se passer, et je vais devoir m’arrêter et tout lui apprendre sur les char* de toutes façons. Ou un jour il va essayer d’appeler une fonction de l’API Windows qui est documentée comme ayant un argument OUT LPTSTR et il ne sera pas capable de comprendre comment l’appeler jusqu’à ce qu’il ait tout appris sur les char*, et les pointeurs, et l’Unicode, et les wchar_t, et les fichiers d’en-tête TCHAR, et tout ce fatras qui fuit.
Lors de l’enseignement de la programmation COM, ce serait bien si je pouvais juste leur apprendre comment utiliser les assistants de Visual Studio et toutes les fonctionnalités de génération de code, mais si quoi que ce soit tourne mal, ils n’auront pas la moindre idée de ce qui s’est passé et de comment le corriger et réparer. Je vais devoir tout leur apprendre sur IUnknown
et les CLSID
et les ProgID
et … ah, l’humanité !
Pire, aujourd’hui : lors de l’enseignement du développement avec des IA comme GitHub Copilot, ce serait bien si je pouvais juste leur apprendre comment écrire des commentaires en langage naturel et laisser l’IA générer tout le code, mais si quoi que ce soit tourne mal, ils n’auront pas la moindre idée de ce qui s’est passé et de comment le corriger et déboguer. Je vais devoir tout leur apprendre sur les algorithmes et les structures de données et les patterns de conception et la gestion mémoire et les race conditions et …ah, l’humanité ! Ou encore, lors de l’enseignement du développement avec des frameworks no-code comme Bubble ou Webflow, ce serait bien si je pouvais juste leur apprendre à glisser-déposer des composants et connecter des APIs, mais dès qu’ils ont besoin d’une logique métier un peu complexe ou qu’une intégration plante mystérieusement, ils se retrouvent coincés sans comprendre ce qui se passe sous le capot et je dois leur expliquer les bases de la programmation, les appels HTTP, les formats JSON et …râââah, l’humanité !
Lors de l’enseignement de la programmation ASP.NET, ce serait bien si je pouvais juste leur apprendre qu’ils peuvent double-cliquer sur des choses et ensuite écrire le code qui s’exécute sur le serveur quand l’utilisateur clique sur ces choses. En fait ASP.NET fait abstraction de la différence entre écrire le code HTML pour traiter le clic sur un hyper lien (<a>) et le code pour traiter le clic sur un bouton. Problème : les concepteurs d’ASP.NET devaient cacher le fait qu’en HTML, il n’y a pas moyen de soumettre un formulaire avec un hyper lien. Ils font cela en générant quelques lignes de JavaScript et en attachant un contrôleur d’événement « onclick
» au lien. L’abstraction fuit, pourtant. Si l’utilisateur final a désactivé JavaScript, l’application ASP.NET ne fonctionne pas bien, et si le programmeur ne comprend pas ce qu’ASP.NET fait comme abstraction, il n’aura simplement aucun indice sur ce qui ne va pas.
On contine enfin avec, si, lors de l’enseignement du développement avec Next.js
, ce serait bien si je pouvais juste leur apprendre qu’ils peuvent créer des fichiers dans le dossier pages/
ou app/
et que le routing se fait automatiquement, et qu’ils peuvent écrire du code qui s’exécute côté serveur ou côté client de manière transparente. En fait Next.js
fait abstraction de la différence entre le rendu côté serveur (SSR
), la génération statique (SSG
) et le rendu côté client (CSR
). Problème : les concepteurs de Next.js
devaient cacher le fait qu’en réalité, certaines APIs JavaScript n’existent que dans le navigateur et d’autres que sur le serveur. Ils font cela en « hydratant » (oh mon Dieu, ce terme barbare) le HTML généré côté serveur avec du JavaScript côté client et en utilisant des techniques comme le « code splitting ». L’abstraction fuit, pourtant. Si vous essayez d’accéder à window
ou localStorage
pendant le SSR
, votre application plante mystérieusement, et si le programmeur ne comprend pas la différence entre l’environnement serveur et client que Next.js
tente de masquer, il n’aura simplement aucun indice sur pourquoi son code fonctionne en développement mais pas en production, ou pourquoi il voit des erreurs d’hydratation incompréhensibles dans la console.
La loi des abstractions qui fuient implique que chaque fois que quelqu’un arrive avec un nouvel outil magique de génération de code qui est censé nous rendre tous super-efficaces, vous entendez plein de gens dire « apprends d’abord comment le faire manuellement, et après utilise l’outil magique pour gagner du temps ». Les outils de génération de code qui prétendent faire abstraction de quelque chose, comme toutes les abstractions, fuient, et la seule façon compétente de s’accommoder des fuites est d’apprendre comment les abstractions fonctionnent et ce dont elles font abstraction. Ainsi les abstractions nous font gagner du temps lors du travail, mais elles ne nous font pas gagner du temps lors de l’apprentissage.
Tout cela implique que paradoxalement, alors même que nous avons des outils de programmation de plus en plus haut niveau avec des abstractions toujours meilleures, devenir un programmeur compétent devient de plus en plus difficile.
Lors de mon premier stage chez Microsoft, j’ai écrit des bibliothèques de chaînes de caractères pour Macintosh. Une tâche typique : écrire une version de strcat
qui retourne un pointeur sur la fin de la nouvelle chaîne. Quelques lignes de code C. Tout ce que j’ai fait était directement issu du K&R — un bouquin peu épais sur le langage de programmation C.
Aujourd’hui, pour travailler, j’ai besoin de connaître Visual Basic, COM, ATL, C++, InnoSetup, les rouages d’Internet Explorer, les expressions régulières, COM, HTML, CSS, et XML. Rien que des outils de haut niveau comparés aux vieux trucs de K&R, mais j’ai toujours besoin de connaître les trucs de K&R ou alors je suis fichu.
Aujourd’hui, pour travailler, j’ai besoin de connaître Python
, Django
, FastAPI
, PostgreSQL
, Redis
, Docker
, Kubernetes
, les modèles de langage comme GPT
et Claude
, le prompt engineering, les embeddings vectoriels, React
pour les interfaces, TypeScript
, les expressions régulières, Git
avec ses branches et merges, les commandes SSH (ssh
, scp
, rsync
, etc), systemctl
pour gérer les services, nginx
pour le reverse proxy, les certificats SSL, les pipelines CI/CD, et bien sûr HTML
, CSS
, et JSON
. Rien que des outils de haut niveau comparés aux vieux trucs de manipulation de pointeurs en C, mais j’ai toujours besoin de comprendre comment la mémoire fonctionne, pourquoi mon script Python bouffe 32GB de RAM sur un simple CSV, comment déboguer un segfault quand une librairie C plantouille, pourquoi ma requête SQL prend 30 secondes, ou comment résoudre un conflit de merge Git quand les abstractions de mon IDE lâchent et que je me retrouve avec des <<<<<<<<< et >>>>>>>>> partout dans mon code, ou alors je suis fichu.
Il y a dix ans, nous pouvions imaginer que de nouveaux paradigmes de programmation rendraient la programmation plus facile aujourd’hui. En fait, les abstractions que nous avons créées au fil des années nous permettent d’aborder de nouveaux ordres de complexité dans le développement logiciel, que nous n’avions pas à aborder il y a vingt ou trente ans, comme la programmation d’interfaces graphiques ou la programmation réseau. Et alors que ces fabuleux outils, comme les langages modernes de type objet, nous permettent d’effectuer beaucoup de travail incroyablement vite, soudain un jour nous devons régler un problème là où l’abstraction a fuit, et cela prend 2 semaines. Et quand vous devez embaucher un développeur pour faire majoritairement du Django
avec de l’IA, ce n’est pas suffisant d’embaucher quelqu’un qui connaît juste Django
et sait utiliser l’API OpenAI, parce qu’il restera collé au goudron chaque fois que l’abstraction fuira. Il faut qu’il comprenne pourquoi ses embeddings donnent des résultats incohérents (vectorisation et similarité cosinus), pourquoi son ORM Django
génère 500 requêtes SQL au lieu d’une (problème N+1
), comment déboguer quand son conteneur Docker refuse de démarrer (docker logs
, docker exec -it container /bin/bash
), pourquoi sa pipeline de fine-tuning explose la mémoire GPU, ou comment résoudre quand nginx
renvoie un 502
parce que Gunicorn est mort silencieusement. Sinon il va passer trois jours à dire « ça marchait sur ma machine » pendant que la prod brûle.
La loi des abstractions qui fuient nous met des bâtons dans les roues.
© Joël Spolsky puis mis au goût du jour par Olivier Pons