En relisant Clean Architecture pour ma bibliothèque, je suis tombé sur une phrase qui m'a arrêté net. Robert Martin y définit les trois paradigmes de programmation, et il les définit à l'envers de toutes les brochures :
« Chacun des paradigmes retire des capacités au programmeur. Aucun ne lui en ajoute de nouvelles. » (Clean Architecture, ch. 3)
Relis-la. Soixante-dix ans de progrès des langages, et les trois plus grandes avancées sont des interdictions. On ne t'a jamais rien donné. On t'a confisqué des jouets dangereux, un par un, et chaque confiscation a rendu ton code plus prévisible. Une fois qu'on a vu ce motif, on le voit partout, jusque dans les langages de 2026. Et il pose une question vertigineuse pour la suite : que reste-t-il à retirer ?
1968 : la première confiscation
Le paradigme structuré naît d'une lettre de Dijkstra devenue légendaire, « Go To Statement Considered Harmful ». Le goto, c'était la liberté absolue : sauter n'importe où dans le programme, n'importe quand. Et c'était précisément le problème. Un code truffé de goto ne se lit pas, il s'enquête. Impossible de raisonner sur un bloc si n'importe qui peut y atterrir depuis n'importe où.
La solution n'a pas été d'ajouter une fonctionnalité. Elle a été d'interdire le saut sauvage. Ce qui te reste, if, while, for, c'est le goto apprivoisé : du transfert de contrôle qui annonce d'où il vient et où il va. Tu utilises le paradigme structuré chaque jour sans le savoir, comme on parle en prose.
Les deux paradigmes suivants suivent le même schéma, et c'est ça la découverte de Martin :
- l'objet (Dahl et Nygaard, 1966) retire les sauts indirects sauvages. En C, tu pouvais appeler « le code dont l'adresse traîne dans cette variable », sans aucune garantie que l'adresse soit bonne. L'objet remplace ça par la méthode : un appel indirect dont le compilateur garantit la cible. Le polymorphisme, c'est le pointeur de fonction apprivoisé ;
- le fonctionnel (Church, 1936, avant même les ordinateurs) retire l'affectation : une valeur créée ne change plus jamais. Si rien ne change, des familles entières de bugs (état partagé, concurrence) disparaissent avec.
Trois paradigmes, trois retraits, et tous découverts entre 1958 et 1968. Martin en tire un pari : il n'y a plus rien à retirer, il n'y aura pas de quatrième paradigme.
Le pari perdu (et c'est une bonne nouvelle)
Le pari est imprimé dans Clean Architecture, publié en septembre 2017. Et le plus piquant, c'est qu'il était déjà en train de perdre à la sortie du livre : un des deux contre-exemples existait depuis deux ans.
Rust (version 1.0 en mai 2015) a retiré le partage mutable : interdiction d'avoir deux endroits du code qui modifient la même donnée en même temps. Ce n'est ni le retrait de l'objet (tu peux toujours faire des appels indirects) ni celui du fonctionnel (tu peux toujours muter, mais jamais à deux). Le compilateur refuse le programme, et la catégorie entière des data races disparaît à la compilation.
La concurrence structurée, elle, est arrivée un an après le livre, et a retiré le spawn sauvage : interdiction de lancer une tâche de fond qui survit au bloc qui l'a créée. L'essai fondateur de Nathaniel J. Smith (2018) s'appelle « Go Statement Considered Harmful », calque assumé du titre de Dijkstra, parce que c'est exactement le même crime : du contrôle qui part on ne sait où, pour on ne sait combien de temps. Le spawn lâché dans la nature est le goto de la concurrence.
Martin parlait du code séquentiel, et sur ce terrain étroit son inventaire est probablement complet. Mais sa mécanique du progrès, retirer pour rendre prévisible, continue de produire. Raison sur le principe, tort sur la prophétie. C'est le meilleur genre d'erreur : celle qui confirme la théorie en cassant la prédiction.
Go, ou l'art de ne garder que la moelle
Si la soustraction est le moteur du progrès, alors le langage le plus instructif de la période n'est pas le plus puissant, c'est le plus têtu dans le refus. Rob Pike, co-créateur de Go, a résumé la philosophie du langage dans une conférence de 2012 dont le titre est tout le programme : « Less is exponentially more ».
Regarde ce que Go a refusé : les classes, l'héritage, les exceptions, la surcharge d'opérateurs, les annotations, et pendant dix ans les génériques. La liste des absences a fait hurler. Mais regarde ce qu'il a gardé de l'objet : une seule chose, l'interface, c'est-à-dire l'appel indirect garanti par le compilateur. Et il l'a même purifiée : en Go, un type satisfait une interface automatiquement, sans la déclarer. Le code métier définit son interface chez lui, et le package base de données ne sait même pas qu'elle existe.
// le package métier déclare son besoin, chez lui
type Sauvegarde interface {
Save(facture Facture) error
}
// le package mysql n'importe RIEN du métier.
// Il a une méthode Save : il satisfait l'interface, point.
func (s *Store) Save(facture Facture) error { ... }
C'est l'inversion de dépendance de Clean Architecture, non plus comme une technique qu'on applique, mais comme l'état naturel du langage. Go a jeté l'emballage de l'objet et gardé le seul morceau qui comptait. Soixante-dix ans de POO distillés en un mécanisme de dix lignes de spec. Si tu veux voir ce tri de plus près, j'ai fiché Learning Go dans la bibliothèque.
Ceux qui ont retiré plus, et ce que ça leur a coûté
Go n'est pas le bout de la route. D'autres langages ont poussé la soustraction plus loin, et leurs trajectoires dessinent la vraie limite de l'exercice.
Zig retire le flux de contrôle caché. Son manifeste tient en une ligne : pas d'exceptions, pas de surcharge, pas d'allocation invisible. Tu lis une ligne, tu sais ce qu'elle exécute, tout ce qu'elle exécute. Une erreur est une valeur que le compilateur t'oblige à traiter, là où Go te laisse encore écrire _ = err en sifflotant.
Elm retire les effets non déclarés : une fonction ne peut ni muter ni faire d'entrée-sortie en douce, tout passe par le type de retour. Résultat célèbre et mesurable : pas d'exception runtime en production. C'est probablement le plus gros gain de fiabilité jamais obtenu par retrait. Et pourtant Elm reste un langage de niche qui stagne : trop pur, écosystème minuscule, le marché a tranché.
Erlang retire la mémoire partagée entre tâches, depuis 1986 : des processus isolés qui s'envoient des messages, un crash qui reste local. Toute l'industrie a passé trente ans à redécouvrir ce que ses créateurs avaient compris pour les centraux téléphoniques.
Le motif qui émerge : les premiers retraits étaient gratuits, personne ne pleure le goto. Les suivants coûtent. Rust achète l'absence de data races au prix d'une courbe d'apprentissage brutale. Elm achète l'absence de crashs au prix de l'isolement. Go est probablement un optimum local sociologique : son vrai produit n'est pas la pureté, c'est « n'importe quel dev de l'équipe lit le code de n'importe qui ». Retirer plus, c'est souvent perdre ça. La courbe de Pareto se plie.
Un espéranto de la programmation ?
D'où la question qui me trotte dans la tête depuis cette relecture : est-ce qu'une IA pourrait concevoir le langage terminal ? Celui qui synthétise tous les retraits gagnants, garde les briques unitaires essentielles, la moelle et rien d'autre : erreurs-valeurs obligatoires, effets visibles dans les signatures, concurrence structurée native, zéro flux de contrôle caché. Un espéranto de la programmation : minimal, régulier, sans exception ni bizarrerie historique.
L'analogie avec l'espéranto est tentante, et elle est surtout instructive par son échec. L'espéranto est objectivement plus simple que toutes les langues qu'il voulait remplacer. Grammaire régulière, zéro verbe irrégulier, apprentissage dix fois plus rapide. Il a perdu quand même, parce qu'une langue ne gagne pas par ses qualités intrinsèques : elle gagne par son écosystème. On apprend l'anglais pour les gens qui le parlent déjà, pas pour sa grammaire. Remplace « gens » par « bibliothèques, tutoriels, offres d'emploi, réponses Stack Overflow » et tu as exactement la gravité qui maintient PHP, Java et JavaScript en orbite des décennies après que de « meilleurs » langages sont apparus.
Mais il y a un paramètre que l'espéranto n'a jamais eu : le locuteur principal est en train de changer. Une part croissante du code est écrite par des IA, et pour une IA, le calcul des coûts s'inverse. La courbe d'apprentissage brutale de Rust ne coûte rien à un modèle. L'écosystème de bibliothèques pèse moins quand le générateur peut réécrire la brique manquante. Et chaque interdiction supplémentaire devient une aubaine : un langage qui refuse plus de programmes à la compilation est un meilleur harnais pour du code généré, chaque retrait transforme une classe de bugs plausibles en erreurs de compilation immédiates. Les retraits « trop chers pour les humains » deviennent rentables quand c'est la machine qui écrit.
Mon pronostic honnête : le langage universel unique ne viendra pas, parce que les domaines tirent dans des directions opposées (le SQL a retiré la boucle, les shaders ont retiré la récursion, chacun a bien fait, et aucun ne peut remplacer l'autre). Ce qui me semble plausible, c'est un langage cible conçu pour l'ère du code généré, qui assemble le palmarès des soustractions. Pas l'espéranto que tout le monde parle ; le latin que les machines écriraient entre elles, vérifiable par le compilateur le plus intransigeant jamais construit. Et nous, on le relirait, parce que par construction il serait ennuyeux à lire. C'est un compliment.
Conclusion
Le truc qui change ma façon d'évaluer un outil depuis cette relecture : ne plus demander ce qu'il permet, demander ce qu'il interdit. La liste des fonctionnalités est du marketing ; la liste des interdictions est de l'ingénierie. Un framework qui peut tout faire ne te protège de rien.
Et l'ironie finale mérite d'être savourée : on construit aujourd'hui les générateurs de code les plus puissants de l'histoire, des IA capables de produire n'importe quoi, et la meilleure chose qu'on puisse leur offrir, c'est un langage qui refuse presque tout. Soixante-dix ans de progrès par soustraction, pour finir par comprendre que le cadeau le plus précieux qu'un langage puisse faire à un programmeur, humain ou pas, c'est un « non » du compilateur.