Rich Hickey & Clojure : simplicité, immutabilité, meilleurs choix par défaut
Une présentation accessible des idées de Rich Hickey sur Clojure : simplicité, immutabilité et meilleurs choix par défaut — leçons pratiques pour construire des systèmes complexes plus calmes et plus sûrs.

Pourquoi la complexité finit par l’emporter dans les projets réels
Le logiciel ne devient rarement compliqué d’un seul coup. Il y arrive par une succession de décisions « raisonnables » : un cache rapide pour tenir un délai, un objet mutable partagé pour éviter des copies, une exception aux règles parce que « celui-ci est spécial ». Chaque choix paraît petit, mais ensemble ils créent un système où changer quelque chose semble risqué, où les bugs sont difficiles à reproduire, et où ajouter des fonctionnalités prend plus de temps que de les construire.
La complexité gagne parce qu’elle offre du confort à court terme. Il est souvent plus rapide d’ajouter une dépendance que de simplifier une dépendance existante. Il est plus simple de coller de l’état que de se demander pourquoi l’état est réparti sur cinq services. Et il est tentant de compter sur des conventions et du savoir tribal quand le système croît plus vite que la documentation.
Ce que cet article est (et n’est pas)
Ce n’est pas un tutoriel sur Clojure, et vous n’avez pas besoin de connaître Clojure pour en retirer de la valeur. L’objectif est d’emprunter un ensemble d’idées pratiques souvent associées au travail de Rich Hickey—des idées que vous pouvez appliquer aux décisions d’ingénierie quotidiennes, quel que soit le langage.
Pourquoi les defaults comptent plus que vous ne le croyez
La plupart de la complexité n’est pas créée par le code que vous écrivez volontairement ; elle est créée par ce que vos outils rendent facile par défaut. Si le défaut est « objets mutables partout », vous vous retrouvez avec un couplage caché. Si le défaut est « l’état vit en mémoire », vous peinerez avec le debug et la traçabilité. Les defaults façonnent des habitudes, et les habitudes façonnent les systèmes.
Nous nous concentrerons sur trois thèmes :
- Simplicité : pas moins de fonctionnalités, mais moins d’éléments mobiles et moins de cas particuliers.
- Immutabilité : traiter les données comme des valeurs qui ne changent pas, pour pouvoir les raisonner.
- Meilleurs choix par défaut : choisir des patterns qui rendent l’option sûre et prédictible la plus facile.
Ces idées ne suppriment pas la complexité de votre domaine, mais elles peuvent empêcher votre logiciel de la multiplier.
Rich Hickey et ce que Clojure a cherché à réparer
Rich Hickey est un développeur et concepteur de logiciels de longue date, surtout connu pour avoir créé Clojure et pour ses talks qui remettent en cause des habitudes courantes en programmation. Son focus n’est pas les tendances : c’est les raisons récurrentes pour lesquelles les systèmes deviennent difficiles à changer, à comprendre, et à faire confiance quand ils grossissent.
Qu’est-ce que Clojure (haut niveau, sans jargon)
Clojure est un langage moderne qui tourne sur des plateformes bien connues comme la JVM (l’exécution Java) et JavaScript. Il est conçu pour travailler avec des écosystèmes existants tout en encourageant un style particulier : représenter l’information comme des données simples, préférer des valeurs qui ne changent pas, et séparer « ce qui s’est passé » de « ce que l’on affiche à l’écran ».
Vous pouvez le percevoir comme un langage qui vous pousse vers des blocs de construction plus clairs et éloigne des effets de bord cachés.
Les problèmes qu’il visait à réduire
Clojure n’a pas été créé pour rendre les petits scripts plus courts. Il visait des douleurs récurrentes de projet :
- Complexité croissante due à l’état partagé : quand beaucoup de parties du système peuvent modifier la même donnée, les bugs deviennent dépendants du timing et difficiles à reproduire.
- Couplage fort entre données et comportement : quand l’information est enfermée dans des objets ou classes, la réutilisation devient plus difficile et les changements se propagent.
- Casse-têtes de concurrence : au fur et à mesure que les systèmes ajoutent des jobs en arrière-plan, des files et du travail parallèle, « qui a changé quoi, quand ? » devient un problème quotidien.
Les choix par défaut de Clojure poussent vers moins d’éléments mobiles : structures de données stables, mises à jour explicites, et outils qui rendent la coordination plus sûre.
Utile même si vous n’adoptez jamais Clojure
La valeur ne se limite pas au changement de langage. Les idées centrales de Hickey—simplifier en supprimant les interdépendances inutiles, traiter les données comme des faits durables, et minimiser l’état mutable—peuvent améliorer les systèmes en Java, Python, JavaScript, et au-delà.
Simplicité : pas « facile », mais moins d’éléments mobiles
Rich Hickey trace une ligne nette entre simple et facile—une ligne que la plupart des projets franchissent sans s’en apercevoir.
Simple vs facile (avec des exemples quotidiens)
Facile concerne la sensation immédiate. Simple concerne le nombre de parties et leur enchevêtrement.
- Les nouilles instantanées sont faciles. Un ragoût basique peut être simple : peu d’ingrédients, une seule marmite, rien de caché.
- Une télécommande avec 60 boutons peut rendre une fonction « facile » (elle est là), mais elle n’est pas simple. Une télécommande avec 6 commandes claires est plus simple, même si elle demande une minute d’apprentissage.
En logiciel, « facile » signifie souvent « rapide à taper aujourd’hui », tandis que « simple » signifie « plus difficile à casser le mois prochain ».
Comment le « facile maintenant » crée de la complexité future
Les équipes choisissent souvent des raccourcis qui réduisent la friction immédiate mais ajoutent des structures invisibles qui doivent être maintenues :
- « Ajoutons juste un flag. » Maintenant chaque fonctionnalité doit considérer ce flag.
- « On stocke la valeur calculée pour gagner du temps. » Il faut maintenant la garder synchronisée sur plusieurs chemins.
- « On le corrige dans l’UI. » La même règle métier existe alors à plusieurs endroits.
Chaque choix peut sembler être de la vitesse, mais il augmente le nombre d’éléments mobiles, de cas particuliers et d’interdépendances. C’est ainsi que les systèmes deviennent fragiles sans erreur dramatique unique.
La vitesse n’est pas la simplicité
Livrer vite peut être excellent—mais la vitesse sans simplifier revient souvent à s’endetter sur le futur. Les intérêts apparaissent sous forme de bugs difficiles à reproduire, d’intégration longue et de changements nécessitant une « coordination soigneuse ».
Une checklist rapide pour la complexité accidentelle
Posez ces questions lors d’une revue de design ou d’un PR :
- Avons-nous introduit de nouveaux modes, flags ou branches de configuration ?
- Mettons-nous en cache ou dupliquons-nous des données qui doivent rester cohérentes ?
- Plusieurs modules doivent-ils changer en même temps pour un même comportement ?
- La règle est-elle implémentée à plus d’un endroit ?
- Un nouveau collègue prédirait-il ce comportement sans explication supplémentaire ?
L’état : le multiplicateur silencieux de la complexité
« L’état » est simplement ce qui peut changer dans votre système : le panier d’un utilisateur, le solde d’un compte, la configuration courante, l’étape d’un workflow. Le problème n’est pas que le changement existe—c’est que chaque changement crée une nouvelle opportunité de désaccord.
Quand on dit « l’état cause des bugs », on veut dire généralement ceci : si la même information peut être différente à des moments différents (ou à des endroits différents), alors votre code doit constamment répondre à « Quelle version est la vraie maintenant ? ». Se tromper produit des erreurs qui semblent aléatoires.
Mutabilité : un changement qu’on ne peut pas ignorer
La mutabilité signifie qu’un objet est modifié en place : la « même » chose devient différente au fil du temps. Ça semble efficace, mais ça rend le raisonnement plus difficile parce qu’on ne peut pas se fier à ce qu’on a vu un instant plus tôt.
Un exemple parlant : une feuille de calcul partagée. Si plusieurs personnes peuvent éditer les mêmes cellules en même temps, votre compréhension peut être invalidée instantanément : les totaux changent, les formules cassent, une ligne disparaît parce que quelqu’un a réorganisé. Même sans malveillance, le caractère partagé et modifiable crée la confusion.
Le logiciel se comporte de la même façon. Si deux parties lisent la même valeur mutable, l’une peut la changer silencieusement pendant que l’autre continue avec une hypothèse obsolète.
Pourquoi le debug devient si pénible
L’état mutable transforme le debug en archéologie. Un rapport d’anomalie vous montre rarement « la donnée a été modifiée incorrectement à 10:14:03 ». Vous voyez juste le résultat final : un nombre erroné, un statut inattendu, une requête qui échoue seulement parfois.
Parce que l’état évolue dans le temps, la question la plus importante devient : quelle séquence de modifications a abouti ici ? Si vous ne pouvez pas reconstruire cette histoire, le comportement devient imprévisible :
- La même action produit des résultats différents selon le timing.
- Les corrections « marchent sur ma machine » mais pas en production.
- Ajouter du logging change le timing et le bug disparaît.
C’est pourquoi Hickey considère l’état comme un multiplicateur de complexité : une fois que les données sont à la fois partagées et mutables, le nombre d’interactions possibles croît plus vite que votre capacité à les maîtriser.
L’immuabilité expliquée sans jargon informatique
L’immuabilité signifie simplement des données qui ne changent pas après leur création. Plutôt que de modifier un élément existant en place, vous créez un nouvel élément qui reflète la mise à jour.
Pensez à un reçu : une fois imprimé, on n’efface pas des lignes et on ne réécrit pas les totaux. Si quelque chose change, on émet un reçu corrigé. L’ancien existe toujours, et le nouveau est clairement « la dernière version ».
Pourquoi ça réduit les surprises
Quand les données ne peuvent pas être modifiées en douce, on arrête de se préoccuper des modifications invisibles. Cela facilite grandement le raisonnement au quotidien :
- Si vous détenez une valeur, vous pouvez lui faire confiance.
- Les bugs deviennent plus faciles à reproduire parce que la même entrée reste la même.
- Partager des données entre parties du système est plus sûr—personne ne peut « casser » quelque chose pour quelqu’un d’autre.
C’est une part importante de la vision de Hickey : moins d’effets de bord cachés signifie moins de branches mentales à suivre.
« Nouvelles versions » vs « éditer en place »
Créer de nouvelles versions peut sembler coûteux jusqu’à ce que vous compariez à l’alternative. Éditer en place vous laisse demander : « Qui a changé ça ? Quand ? Qu’est-ce que c’était avant ? » Avec des données immuables, les changements sont explicites : une nouvelle version existe et l’ancienne reste disponible pour le debug, l’audit ou le rollback.
Clojure encourage naturellement à considérer les mises à jour comme la production de nouvelles valeurs, pas comme la mutation d’anciennes.
Équilibrer les coûts honnêtement
L’immuabilité n’est pas gratuite. Vous pouvez allouer plus d’objets et les équipes habituées au « on modifie juste » doivent s’adapter. La bonne nouvelle : les implémentations modernes partagent souvent des structures sous le capot pour réduire le coût mémoire, et le gain est généralement des systèmes plus calmes avec moins d’incidents difficiles à expliquer.
La concurrence devient plus simple quand les données ne changent pas
La concurrence, c’est simplement « beaucoup de choses qui se passent en même temps ». Une app web gérant des milliers de requêtes, un système de paiement mettant à jour des soldes pendant qu’il génère des reçus, ou une appli mobile qui sync en arrière-plan—tous sont concurrents.
Le problème n’est pas que plusieurs choses se passent ; c’est qu’elles touchent souvent les mêmes données.
Pourquoi les données partagées et modifiables créent des race conditions
Quand deux travailleurs peuvent lire puis modifier la même valeur, le résultat final peut dépendre du timing. C’est une condition de course : un bug difficile à reproduire qui apparaît lorsque le système est chargé.
Exemple : deux requêtes essaient de mettre à jour le total d’une commande.
- Requête A lit total = 100
- Requête B lit total = 100
- A ajoute 20 et écrit 120
- B ajoute 10 et écrit 110
Rien n’a « crashé », mais vous avez perdu une mise à jour. Sous charge, ces fenêtres temporelles se multiplient.
Les solutions traditionnelles—verrous, blocs synchronisés, ordonnancements stricts—fonctionnent, mais elles obligent tout le monde à se coordonner. La coordination coûte : elle ralentit le débit et devient fragile à mesure que le codebase grossit.
Comment l’immuabilité réduit la coordination
Avec des données immuables, une valeur n’est pas éditée en place. À la place, vous créez une nouvelle valeur qui représente la modification.
Ce simple changement supprime toute une catégorie de problèmes :
- Les lecteurs n’ont plus à craindre que la donnée change pendant la lecture.
- Les écrivains ne « se battent » pas pour la même mémoire ; ils produisent des versions nouvelles.
- Le système peut choisir des moyens sûrs pour publier la version la plus récente (souvent via des primitives simples et éprouvées).
Le résultat : comportement prédictible sous charge
L’immuabilité ne rend pas la concurrence gratuite—vous avez toujours besoin de règles pour déterminer quelle version est courante. Mais elle rend les programmes concurrents beaucoup plus prédictibles, car les données elles-mêmes ne sont plus une cible mouvante. Quand le trafic monte ou que les jobs d’arrière-plan s’accumulent, vous verrez moins de défaillances mystérieuses dépendant du timing.
Ce que « meilleurs choix par défaut » signifie en pratique
« Meilleurs choix par défaut » veut dire que l’option la plus sûre se produit automatiquement, et que vous ne prenez de risque supplémentaire que lorsque vous le décidez explicitement.
Cela paraît anodin, mais les defaults enseignent silencieusement comment coder un lundi matin, ce que les reviewers acceptent un vendredi soir, et ce qu’un nouveau venu apprend du premier codebase qu’il touche.
Defaults qui réduisent le risque
Un « meilleur défaut » ne consiste pas à tout décider pour vous. Il vise à rendre le chemin commun moins sujet aux erreurs.
Par exemple :
- Données immuables par défaut : au lieu de « modifier la chose », vous créez une nouvelle version. Cela rend plus difficile d’impacter accidentellement d’autres parties du programme qui dépendaient de l’ancienne valeur.
- Fonctions pures comme style normal : une fonction prend des entrées et renvoie une sortie, sans changer de données partagées ou dépendre d’un état global caché. Cela rend le comportement plus prévisible et testable.
- Changements d’état explicites : quand quelque chose doit changer, cela se fait via des mécanismes clairs et bien définis (et non parce que n’importe quelle partie du code peut muter n’importe quoi).
Rien de tout cela n’élimine la complexité, mais cela l’empêche de se propager.
Comment les defaults façonnent les équipes et les revues de code
Les équipes ne suivent pas seulement la documentation—elles suivent ce que le code « veut » que vous fassiez.
Quand muter l’état partagé est facile, cela devient un raccourci normal, et les reviewers se retrouvent à débattre d’intentions : « Est-ce sûr ici ? » Quand l’immuabilité et les fonctions pures sont la norme, les reviewers peuvent se concentrer sur la logique et la correction, parce que les mouvements risqués ressortent.
Autrement dit, de meilleurs defaults créent un socle plus sain : la plupart des changements paraissent cohérents, et les patterns inhabituels sont suffisamment visibles pour être questionnés.
Maintenance et onboarding
La maintenance long terme revient surtout à lire et modifier du code existant en toute sécurité.
Les meilleurs defaults aident les nouveaux collègues à monter en compétence parce qu’il y a moins de règles cachées (« attention, cette fonction modifie secrètement cette map globale »). Le système devient plus facile à raisonner, ce qui réduit le coût de chaque future feature, fix et refactor.
Séparer les faits des vues : temps, historique et traçabilité
Un basculement mental utile dans les talks de Hickey est de séparer faits (ce qui s’est produit) et vues (ce que nous croyons actuellement vrai). Beaucoup de systèmes brouillent ces notions en ne stockant que la valeur la plus récente—en écrasant hier par aujourd’hui—et cela fait disparaître la dimension temporelle.
Les faits sont en append-only ; les vues sont dérivées
Un fait est un enregistrement immuable : « Commande #4821 passée à 10:14 », « Paiement réussi », « Adresse modifiée ». On n’édite pas ces faits ; on ajoute de nouveaux faits à mesure que la réalité change.
Une vue est ce dont votre app a besoin maintenant : « Quelle est l’adresse de livraison actuelle ? » ou « Quel est le solde client ? » Les vues peuvent être recalculées à partir des faits, mises en cache, indexées ou matérialisées pour la vitesse.
Pourquoi garder l’historique paie
Quand vous conservez les faits, vous gagnez :
- Auditabilité : vous pouvez expliquer pourquoi la valeur actuelle est ce qu’elle est.
- Debug : vous pouvez rejouer la séquence et trouver le moment où les choses ont divergé.
- Traçabilité : « Qui a changé ça, quand, et qu’est-ce que c’était avant ? » devient une question de données, pas une enquête de détective.
Exemple abordable : écraser vs ajouter
Écraser des enregistrements, c’est comme mettre à jour une cellule de feuille de calcul : vous ne voyez que le dernier nombre.
Un journal append-only, c’est comme un registre de chéquier : chaque écriture est un fait, et le « solde courant » est une vue calculée à partir des écritures.
Pas besoin d’un event sourcing complet partout
Vous n’avez pas à adopter une architecture event-sourcée complète pour en bénéficier. Beaucoup d’équipes commencent plus modestement : conserver une table d’audit append-only pour les changements critiques, stocker des événements immuables pour quelques workflows à haut risque, ou garder des snapshots plus une courte fenêtre d’historique. L’essentiel est l’habitude : traiter les faits comme durables et l’état courant comme une projection pratique.
Data first : rendre l’information durable et flexible
Une des idées les plus pratiques de Hickey est data first : traiter l’information du système comme des valeurs simples (faits), et traiter le comportement comme quelque chose qu’on exécute contre ces valeurs.
Les données sont durables. Si vous stockez une information claire et autonome, vous pouvez la réinterpréter plus tard, la déplacer entre services, la réindexer, l’auditer, ou l’exploiter pour de nouvelles fonctionnalités. Le comportement est moins durable—le code change, les hypothèses changent, les dépendances changent.
Valeurs vs actions (sans jargon)
- Données (valeurs) : « Qu’est-ce qui est vrai ? » Email d’un client, total d’une commande, un timestamp, un statut.
- Comportement (actions) : « Que fait-on ? » Valider, calculer des remises, envoyer des notifications, décider de la signification d’un statut.
Quand vous mêlez tout, les systèmes deviennent collants : vous ne pouvez pas réutiliser des données sans traîner le comportement qui les a créées.
Moins de couplage, plus de réutilisation
Séparer faits et actions réduit le couplage parce que les composants peuvent se mettre d’accord sur une forme de données sans s’accorder sur un chemin d’exécution commun.
Un job de reporting, un outil support et un service de facturation peuvent consommer les mêmes données de commande, chacun appliquant sa logique. Si vous embeddiez la logique dans la représentation stockée, chaque consommateur deviendrait dépendant de cette logique—et la changer deviendrait risqué.
Exemple : stocker des données propres vs stocker des mini-programmes
Données propres (faciles à faire évoluer) :
{
"type": "discount",
"code": "WELCOME10",
"percent": 10,
"valid_until": "2026-01-31"
}
Mini-programmes dans le stockage (difficiles à faire évoluer) :
{
"type": "discount",
"rule": "if (customer.orders == 0) return total * 0.9; else return total;"
}
La seconde version paraît flexible, mais elle pousse la complexité dans la couche de données : vous avez besoin d’un évaluateur sécurisé, de règles de versioning, de frontières de sécurité, d’outils de debug et d’un plan de migration quand le langage de règles change.
Pourquoi cela rend les systèmes évolutifs
Quand l’information stockée reste simple et explicite, vous pouvez changer le comportement au fil du temps sans réécrire l’historique. Les anciens enregistrements restent lisibles. De nouveaux services peuvent être ajoutés sans « comprendre » l’exécution héritée. Et vous pouvez introduire de nouvelles interprétations—nouvelles vues UI, nouvelles stratégies de tarification, nouvelles analyses—en écrivant du code nouveau, pas en mutant la signification des données.
Appliquer ces idées aux systèmes complexes (sans tout réécrire)
La plupart des systèmes d’entreprise ne tombent pas en panne parce qu’un module est « mauvais ». Ils tombent parce que tout est connecté à tout.
Modes de défaillance à surveiller
Le couplage fort se manifeste par des changements « petits » qui déclenchent des semaines de retesting. Un champ ajouté à un service casse trois consommateurs en aval. Un schéma de base partagé devient un goulot de coordination. Un cache mutable unique ou un singleton "config" devient une dépendance cachée de la moitié du codebase.
Le changement en cascade est la conséquence naturelle : quand beaucoup de parties partagent la même chose changeante, le rayon d’explosion grandit. Les équipes réagissent en ajoutant plus de processus, plus de règles et plus de transferts—ralentissant souvent encore la livraison.
Réduire le rayon d’explosion avec des frontières plus simples
Vous pouvez appliquer les idées de Hickey sans changer de langage ni tout réécrire :
- Privilégier les données immuables aux frontières. Traitez messages, événements et entrées d’API comme des « faits » qui ne sont pas modifiés en place. Si quelque chose change, créez une nouvelle version.
- Déplacer l’état vers les bords. Gardez la logique centrale comme transformations pures : données d’entrée → données de sortie. Laissez les bases, caches et UI gérer l’« état courant ».
- Arrêter de partager des structures mutables. Si deux modules écrivent le même objet, ils sont couplés. Passez des valeurs, pas des références que vous prévoyez de muter.
Quand les données ne changent pas sous vos pieds, vous passez moins de temps à diagnostiquer « comment en est-on arrivé là ? » et plus de temps à raisonner sur ce que fait le code.
« Meilleurs defaults » à l’échelle des équipes
Les defaults sont le lieu où l’incohérence s’insinue : chaque équipe invente son propre format de timestamp, forme d’erreur, politique de retry et approche de concurrence.
De meilleurs defaults ressemblent à : schémas d’événements versionnés, DTOs immuables standards, propriété claire des écritures, et un petit ensemble de bibliothèques approuvées pour la sérialisation, la validation et le tracing. Le résultat : moins d’intégrations surprises et moins de correctifs ad hoc.
Adoption incrémentale qui marche dans du code existant
Commencez là où le changement a déjà lieu :
- Enveloppez des modules existants avec une API qui accepte/retourne des données immuables.
- Convertissez un workflow à forte rotation en enregistrements « append-only ».
- Remplacez des caches mutables partagés par des vues recomputables à partir de faits durables.
Cette approche améliore la fiabilité et la coordination d’équipe tout en gardant le système en marche—et en limitant la portée pour pouvoir terminer.
Où les plateformes et outils peuvent renforcer les « meilleurs defaults »
Il est plus facile d’appliquer ces idées quand votre workflow supporte une itération rapide et à faible risque. Par exemple, si vous développez dans Koder.ai (une plateforme de vibe-coding chat pour web, backend et mobile), deux fonctionnalités correspondent directement à l’esprit des « meilleurs defaults » :
- Le mode planning vous encourage à expliciter les frontières et les formes de données avant l’implémentation—souvent la différence entre un flux de données simple et un couplage accidentel.
- Snapshots et rollback rendent les changements plus sûrs à déployer, car vous pouvez revenir rapidement quand un raccourci « facile » se transforme en pic de complexité.
Même si votre stack est React + Go + PostgreSQL (ou Flutter pour mobile), le point central reste : les outils que vous utilisez au quotidien enseignent silencieusement une manière de travailler. Choisir des outils qui rendent routine la traçabilité, le rollback et la planification explicite peut réduire la tentation du « on patchera après ».
Compromis, limites et éviter l’idéologie
Simplicité et immuabilité sont des defaults puissants, pas des règles morales. Ils réduisent le nombre de choses qui peuvent changer de façon inattendue, ce qui aide quand les systèmes grossissent. Mais les projets réels ont des budgets, des délais et des contraintes—et parfois la mutabilité est l’outil approprié.
Quand la mutabilité est acceptable
La mutabilité peut être un choix pragmatique dans des hotspots de performance (boucles serrées, parsing à haut débit, graphismes, calcul numérique) où les allocations dominent. Elle peut aussi convenir lorsque le périmètre est contrôlé : variables locales, cache privé derrière une interface, ou composant mono-thread avec frontières claires.
La clé est la contention : si la « chose mutable » ne fuit pas, elle ne peut pas propager la complexité.
Bornes à la complexité : propriété et interfaces
Même dans un style majoritairement fonctionnel, les équipes ont besoin d’une propriété claire :
- Un module possède un état et expose une API réduite.
- Les données traversent les frontières comme des valeurs simples (pas des objets vivants avec un comportement secret).
- Les effets de bord sont poussés aux bords (I/O, bases, temps).
C’est là que le biais de Clojure pour les données et les frontières explicites aide, mais la discipline est architecturale, pas liée au langage.
Ce que Clojure ne résoudra pas
Aucun langage ne corrige de mauvaises exigences, un modèle de domaine flou, ou une équipe qui n’est pas d’accord sur la définition du « fini ». L’immuabilité ne rendra pas un workflow confus compréhensible, et du code « fonctionnel » peut toujours encoder de mauvaises règles métier—juste de façon plus propre.
Éviter le dogme : réduire le risque par le plus petit changement
Si votre système est déjà en production, ne traitez pas ces idées comme un tout-ou-rien. Cherchez le plus petit mouvement qui diminue le risque :
- Remplacez des structures mutables partagées par des données immuables aux frontières.
- Ajoutez des journaux d’événements ou une historique append-only là où l’audit compte.
- Enveloppez l’état legacy derrière une interface étroite et empêchez sa propagation.
Le but n’est pas la pureté—c’est moins de surprises à chaque changement.
Une checklist pratique pour aller vers un logiciel plus simple
Ceci est une checklist de l’ordre d’un sprint que vous pouvez appliquer sans changer de langage, framework, ou organisation.
3–5 choses à tester le sprint prochain
-
Rendez vos formes de données immuables par défaut. Considérez requêtes/réponses, événements et messages comme des valeurs qu’on crée une fois et ne modifie pas. Si quelque chose change, créez une nouvelle version.
-
Privilégiez les fonctions pures au centre des workflows. Commencez par un workflow (par ex. pricing, permissions, checkout) et refactorez le cœur en fonctions qui prennent des données en entrée et renvoient des données en sortie—sans lectures/écritures cachées.
-
Déplacez l’état vers moins d’endroits, et plus clairs. Choisissez une source de vérité par concept (statut client, feature flags, inventaire). Si plusieurs modules maintiennent des copies, faites-en une décision explicite avec une stratégie de sync.
-
Ajoutez un log append-only pour les faits clés. Pour un domaine, enregistrez « ce qui s’est passé » comme événements durables (même si vous conservez aussi l’état courant). Cela améliore la traçabilité et réduit les conjectures.
-
Définissez des defaults plus sûrs dans les APIs. Les defaults doivent minimiser les comportements surprenants : timezones explicites, gestion explicite des nulls, retries clairs, garanties d’ordonnancement explicites.
Questions à poser en revue de design (utilisez-les telles quelles)
- Quelles sont les parties mutables ici, et qui est autorisé à les changer ?
- Peut-on modéliser ça comme « faits » + vue dérivée au lieu d’écraser des champs ?
- Si deux requêtes s’exécutent en même temps, qu’est-ce qui casse ?
- Sur quels defaults comptons-nous (temps, ordres, retries, cache), et sont-ils documentés ?
- Que nous faudrait-il pour débuguer ceci dans trois mois ?
Sujets suggérés à revoir dans les talks de Hickey
Cherchez des ressources sur simplicité vs facilité, gestion d’état, conception orientée valeur, immutabilité, et comment l’« historique » (faits dans le temps) aide le debug et l’exploitation.
La simplicité n’est pas une fonctionnalité qu’on greffe—c’est une stratégie que l’on pratique par des choix petits et répétables.
FAQ
Pourquoi la complexité « gagne » si souvent dans les projets réels ?
La complexité s’accumule par de petites décisions localement « raisonnables » (flags supplémentaires, caches, exceptions, helpers partagés) qui ajoutent des modes et du couplage.
Un bon signal d’alerte : une « petite modification » nécessite des edits coordonnés dans plusieurs modules ou services, ou quand les relecteurs doivent s’appuyer sur du savoir tribal pour juger de la sécurité.
Quelle est la différence entre « simple » et « facile » en logiciel ?
Les raccourcis optimisent la friction d’aujourd’hui (le temps de livraison) tout en repoussant des coûts vers l’avenir : temps de debug, frais de coordination et risque au changement.
Une bonne habitude en revue de design/PR : demander « Quels nouveaux éléments mobiles ou cas particuliers cela introduit, et qui va les maintenir ? »
Comment les defaults des langages et frameworks créent-ils de la complexité accidentelle ?
Les choix par défaut guident ce que les ingénieurs font sous pression. Si la mutation est le comportement par défaut, l’état partagé se propage. Si « en mémoire c’est suffisant » est le défaut, la traçabilité disparaît.
Améliorez les défaults en rendant la voie sûre la plus facile : données immuables aux frontières, timezones/null explicites, retries clairs et propriété d’état définie.
Pourquoi l’état est-il décrit comme un « multiplicateur de complexité » ?
L’état est tout ce qui change dans le temps. Le problème est que chaque changement crée une opportunité de désaccord : deux composants peuvent avoir deux valeurs « actuelles » différentes.
Les bugs apparaissent comme des comportements dépendant du timing ("marche chez moi", problèmes intermittents en prod) parce que la question devient : sur quelle version des données avons-nous agi ?
Que signifie l’immuabilité en termes pratiques et non académiques ?
L’immuabilité signifie qu’on n’édite pas une valeur en place ; on crée une nouvelle valeur qui représente la mise à jour.
Concrètement, cela aide parce que :
- Les lecteurs peuvent faire confiance à la stabilité d’une donnée pendant son utilisation.
- Reproduire des bugs devient plus simple (les entrées restent stables).
- Partager des données entre threads/modules devient plus sûr.
Quand la mutabilité est-elle acceptable (ou même préférable) ?
Ce n’est pas toujours à proscrire. La mutabilité peut être utile si elle est contenue :
- Variables locales à une fonction
- Zones critiques de performance (boucles serrées, parsing, calculs numériques)
- Caches privés derrière une interface étroite
La règle clé : ne laissez pas des structures mutables fuir les frontières où beaucoup de parties peuvent lire/écrire.
Comment l’immuabilité aide-t-elle la concurrence sous forte charge ?
Les conditions de course viennent généralement des données partagées et mutables lues puis réécrites par plusieurs acteurs.
L’immuabilité réduit la surface de coordination parce que les écrivains produisent de nouvelles versions plutôt que de modifier un objet partagé. Il reste nécessaire d’avoir une règle pour publier la version courante, mais les données cessent d’être une cible mouvante.
Que signifie séparer « faits » et « vues », et comment l’appliquer progressivement ?
Considérez les faits comme des enregistrements append-only de ce qui s’est produit (événements), et l’état actuel comme une vue dérivée de ces faits.
Vous pouvez commencer petit sans event sourcing complet :
- Ajouter une table d’audit pour les changements critiques
- Enregistrer des événements de changement pour un workflow à haut risque
- Conserver des snapshots plus une fenêtre d’historique limitée pour le replay/debug
Qu’est-ce que le design « data-first » et pourquoi réduit-il le couplage ?
Stocker l’information comme des données simples et explicites (valeurs), et exécuter du comportement contre ces valeurs. Évitez d’imbriquer des règles exécutables dans des enregistrements stockés.
Cela rend les systèmes plus évolutifs car :
- Les anciens enregistrements restent lisibles quand le code change
- De nouveaux consommateurs peuvent réutiliser la même forme de données
- On change la logique sans réécrire l’historique
Quelles sont les 3 premières modifications concrètes à essayer lors du prochain sprint pour réduire la complexité ?
Choisissez un workflow qui change souvent et appliquez trois étapes :
- Rendre les données aux frontières immuables : traitez API in/out, messages et événements comme « créés une fois, ne pas muter ».
- Refactoriser le cœur en transformations pures : données en entrée → données en sortie ; pousser l’I/O et les effets de bord aux limites.
- Réduire les structures mutables partagées : un propriétaire par donnée, exposée via une interface minimale.
Mesurez le succès par moins de bugs intermittents, un blast radius réduit par changement, et moins de coordination pointilleuse aux releases.