5 points par GN⁺ 2025-06-18 | 1 commentaires | Partager sur WhatsApp
  • La complexité est l’élément le plus dangereux du développement
  • La véritable efficacité vient d’une approche pragmatique qui évite la complexité, comme les « solutions 80/20 »
  • Il est important de garder une attitude équilibrée et souple vis-à-vis des tests et du refactoring
  • L’auteur insiste sur l’usage des outils et sur l’adoption d’habitudes d’écriture de code lisible et facile à maintenir
  • Il met en garde contre l’abstraction excessive et les effets de mode, et recommande de rechercher la simplicité

Introduction

  • Cet article rassemble les enseignements tirés de l’expérience d’un développeur au cerveau Grug après de longues années de développement logiciel
  • Le développeur au cerveau Grug ne se considère pas comme particulièrement intelligent, mais il a beaucoup appris en programmant pendant longtemps
  • Il partage ses constats d’une manière simple et drôle, en espérant que d’autres apprendront de leurs erreurs
  • La complexité est le plus grand ennemi de toute une vie de développeur
  • Elle s’infiltre en douce dans une base de code et finit par rendre impossible la modification d’un code qui était pourtant facile à comprendre au départ

Faire face au démon de la complexité

  • La complexité s’insinue silencieusement comme un esprit invisible, et les chefs de projet comme les développeurs non-Grug ne s’en rendent souvent pas bien compte
  • Le meilleur moyen de bloquer la complexité, c’est de dire « non »
    • « Je ne vais pas développer cette fonctionnalité »
    • « Je ne vais pas introduire cette abstraction »
    • Bien sûr, pour sa carrière, crier « oui » peut être plus avantageux, mais le développeur Grug accorde plus d’importance à l’honnêteté envers lui-même
  • Selon les cas, un compromis (« ok ») est aussi nécessaire, et dans ce genre de situation il préfère résoudre le problème simplement avec une solution 80/20 (application du principe de Pareto)
  • Il peut aussi être judicieux de ne pas tout dire au chef de projet et de réaliser les choses en pratique selon une approche 80/20

Structure du code et abstraction

  • Les bonnes unités de code (points de découpe) se révèlent naturellement avec le temps, donc il vaut mieux éviter l’abstraction précoce
  • Un bon point de découpe a idéalement une interface étroite avec le reste du système
  • Les tentatives d’abstraction trop tôt échouent facilement, et les développeurs expérimentés essaient de structurer le code lentement une fois que sa forme générale s’est stabilisée
  • Les développeurs moins expérimentés ou « big brain » tentent trop d’abstractions dès le début du projet, laissant derrière eux une charge de maintenance

Stratégie de test

  • L’équilibre est essentiel dans le rapport aux tests
  • L’auteur préfère écrire les tests après le prototypage, une fois que le code s’est relativement stabilisé
  • Les tests unitaires sont utiles au départ, mais dans la pratique c’est le niveau intermédiaire (tests d’intégration) qui apporte le plus de valeur
  • Les tests end-to-end sont également nécessaires, mais s’ils sont trop nombreux ils deviennent ingérables ; il faut donc en garder peu, sur les parcours vraiment essentiels
  • Lorsqu’un bug est signalé, il faut toujours ajouter un test de reproduction avant de corriger le bug

Processus, agile, refactoring

  • L’agile n’est pas mauvais pour un développeur Grug, et ce n’est pas non plus le pire, mais il est dangereux d’avoir des attentes excessives envers les « chamans agile »
  • Le prototypage, les outils et de bons collègues sont en réalité des facteurs de réussite plus importants
  • Le refactoring est aussi une bonne habitude, mais les gros refactorings forcés sont risqués
  • Introduire de force des abstractions complexes provoque au contraire l’échec du projet

Maintenance, perfectionnisme et humilité

  • Refaire un système existant sans raison est dangereux, et supprimer aveuglément une structure « dont on ne comprend pas pourquoi elle existe » est une mauvaise habitude
  • L’idéalisme qui rêve d’un code parfait crée dans la plupart des cas des problèmes en pratique
  • Avec l’expérience, on ressent concrètement qu’il faut « respecter le code qui fonctionne »

Outils et productivité

  • De bons outils de développement (autocomplétion de l’IDE, débogueur, etc.) augmentent fortement la productivité, et il est important de les maîtriser en profondeur
  • L’auteur souligne que la vraie valeur d’un système de types réside dans « l’autocomplétion » et la prévention des erreurs, tandis que l’abstraction excessive et les génériques peuvent au contraire être dangereux

Style de code et répétition

  • Il recommande un style consistant, par exemple, à découper les conditions sur plusieurs lignes afin d’obtenir un code plus lisible et plus facile à déboguer
  • Il respecte le principe DRY (Don’t Repeat Yourself), mais insiste sur l’importance de l’équilibre plutôt que sur l’élimination forcée du code répétitif
  • Dans bien des situations, une répétition simple vaut mieux qu’une implémentation DRY complexe

Principes de conception logicielle

  • Il préfère la localité du comportement au principe de SoC (séparation des préoccupations), affirmant que « le code qui réalise un comportement donné devrait se trouver dans l’objet concerné pour faciliter la maintenance »
  • Il avertit qu’il faut n’utiliser callbacks/closures, systèmes de types, génériques et abstractions qu’en petite quantité et à bon escient
  • L’abus de closures peut créer en JavaScript un « callback hell »

Logging, exploitation

  • Le logging est très important : il faut journaliser les points de branchement majeurs et, dans un environnement cloud, permettre le traçage via des éléments comme l’ID de requête
  • Si l’on peut utiliser des niveaux de logs dynamiques et des logs par utilisateur, cela aide énormément à suivre les problèmes en production

Concurrence, optimisation

  • Pour la concurrence, il ne fait confiance qu’aux modèles aussi simples que possible (requêtes web sans état, files de workers séparées, etc.)
  • Il recommande de n’optimiser réellement qu’après avoir obtenu des données concrètes de profilage des performances
  • Il faut faire attention aux coûts cachés comme les I/O réseau, et il est dangereux de ne regarder que la complexité CPU

Conception d’API

  • Une bonne API doit être facile à utiliser, et des conceptions ou abstractions trop complexes nuisent à l’expérience développeur
  • Il recommande une structure d’API avec d’un côté une « API simple adaptée aux cas d’usage » et de l’autre une « API en couches permettant aussi de couvrir des cas complexes »

Développement de parseurs

  • Les parseurs à descente récursive sont sous-estimés dans le monde académique, mais constituent selon lui la méthode la plus adaptée au code de production et la plus facile à comprendre
  • D’après la plupart de ses expériences en développement de parseurs, les parseurs générés par des outils produisent des résultats trop complexes, ce qui finit par être contre-productif
  • Parmi les livres recommandés, il place "Crafting Interpreters" tout en haut, car il regorge de conseils pratiques

Frontend et effets de mode

  • Le frontend moderne (React, SPA, GraphQL, etc.) invoque au contraire encore davantage le démon de la complexité, et il est souvent inutile
  • Grug lui-même préfère réduire la complexité avec des outils simples comme htmx et hyperscript
  • Le frontend voit sans cesse apparaître de nouvelles tentatives, mais il faut garder à l’esprit qu’il s’agit souvent de répétitions d’idées existantes

Facteurs psychologiques, syndrome de l’imposteur

  • La plupart des développeurs ont souvent l’impression de « ne pas savoir ce qu’ils font », et il faut parvenir à se libérer du phénomène FOLD (Fear Of Looking Dumb)
  • Lorsqu’un développeur senior dit publiquement « moi aussi je trouve ça difficile, c’est trop complexe », cela peut aider les développeurs juniors à relâcher la pression
  • Le syndrome de l’imposteur est un sentiment courant, et l’auteur encourage à continuer d’apprendre et de progresser

Conclusion

  • En programmation, il faut toujours se méfier de la complexité, et préserver la simplicité est au cœur d’un développement réussi
  • L’expérience, l’usage efficace des outils, l’humilité et le respect du code qui fonctionne mènent à long terme à un développement plus efficace et plus utile
  • « La complexité est très, très mauvaise » — il faut toujours garder cette phrase en tête

1 commentaires

 
GN⁺ 2025-06-18
Avis sur Hacker News
  • J’accorde une valeur inestimable à un bon débogueur, et je le trouve en pratique encore plus impressionnant. Que ce soit dans une petite startup ou dans une équipe Big Tech renommée, j’ai souvent été le seul de l’équipe à utiliser un débogueur. En réalité, beaucoup de gens déboguent encore avec des instructions print. Même quand j’essaie de montrer mon workflow à mes collègues, ça ne suscite aucune réaction. Je suis d’accord pour dire que le meilleur point de départ pour comprendre un système, c’est justement le débogueur. Mettre un point d’arrêt sur une ligne de code intéressante pendant un test et regarder la stack, c’est bien plus simple que de suivre le code mentalement. Apprendre à se servir d’un débogueur, c’est vraiment acquérir un petit super-pouvoir. Si possible, je recommande vraiment d’essayer
    • J’aimerais vraiment utiliser un vrai débogueur, mais en n’ayant travaillé que dans de grandes entreprises, c’était en pratique impossible. Avec une architecture en mesh de microservices, on ne peut rien exécuter en local, et dans la plupart des cas les environnements de test sont configurés pour qu’on ne puisse pas y attacher un débogueur pas à pas. Du coup, le débogage par print est la seule option réaliste. Et si même le système de logs a un problème, ou que le programme plante avant d’émettre un log, alors on ne peut même plus utiliser print
    • Il y a eu une bonne discussion sur ce sujet il y a des années. Il y a une citation célèbre de Brian Kernighan et Rob Pike, qui ne sont pas exactement de jeunes développeurs : « Nous n’utilisons pas de débogueur à d’autres fins que vérifier une stack trace ou quelques valeurs de variables. Avec des structures de données et des flux de contrôle complexes, on se retrouve facilement piégé dans les détails. Il est plus productif de réfléchir davantage soi-même au programme, puis d’ajouter au passage des print et du code d’auto-vérification. Ajouter des print est bien plus rapide que d’entrer pas à pas dans un débogueur. Et en plus, le code avec print reste dans le programme, alors que la session de débogage disparaît. » Je suis d’accord avec cet avis. Dans la plupart des phases de développement, la boucle print-hypothèse-exécution permet de résoudre les problèmes bien plus vite. Ce n’est pas que je “fais tourner” le code dans ma tête, c’est plutôt que j’ai déjà un modèle mental de son fonctionnement, et quand un print montre une sortie incorrecte, j’ai souvent très vite l’intuition de ce qui se passe réellement. Lien associé : The unreasonable effectiveness of print debugging
    • Si le débogage par printf a toujours été aussi courant dans le monde Linux, c’est parce que les environnements à débogueur graphique n’y étaient pas fiables. Les interfaces graphiques sous Linux sont souvent instables, donc difficiles à croire sur parole. Dans mon cas, j’ai vraiment commencé à bien utiliser les débogueurs quand, (1) sous Windows, l’interface graphique marchait bien mais la CLI cassait souvent, et (2) après avoir plusieurs fois causé des problèmes en laissant par erreur du code de débogage par print partir dans une version. Ensuite, j’ai fait toutes sortes d’expériences avec des débogueurs CLI, et j’ai trouvé qu’un processus basé sur Junit + débogueur (dans un IDE comme Eclipse), où l’on peut immédiatement tester du code expérimental et le laisser sous forme de test, était presque aussi pratique qu’un REPL Python. Cela dit, il faut un investissement initial pour configurer correctement le débogueur selon son environnement
    • Dans mon propre code, il m’est facile d’utiliser un débogueur et j’adore ça. En revanche, dès que le débogueur s’enfonce profondément dans les bibliothèques ou les frameworks plutôt que dans le code que j’ai écrit, je me perds immédiatement et je déteste ça. Ces frameworks et bibliothèques ont été construits sur des dizaines de milliers d’heures de travail, donc à mon niveau ça dépasse tout de suite ce que je peux comprendre
  • Professeur Carson, si jamais vous lisez ce texte, j’aimerais vous remercier sincèrement. À l’université, je ne comprenais pas pourquoi on apprenait HTMX ni pourquoi vous y mettiez autant de passion, puis quelques années plus tard j’ai enfin compris. HTML over the wire, c’est vraiment l’essentiel. En travaillant comme Staff Ruby on Rails Engineer, j’ai vu votre travail plusieurs fois dans Hotwire, et je suis toujours étonné de vous voir parfois actif sur GitHub ou Hacker News. Vous êtes toujours comme une lumière pour la communauté du développement. Tout mon respect et ma gratitude
    • Je ne suis pas le seul à être ému ici, c’est touchant
    • HTMX, ce n’était pas juste un mème ? Avec la loi de Poe, j’ai du mal à savoir si c’est sérieux
  • Il y a énormément de formules mémorables dans ce texte, mais celle que j’ai préférée concerne les microservices : « grug ne sait pas pourquoi gros cerveau, qui a déjà du mal à découper correctement le système, ajoute en plus des appels réseau »
    • Certaines personnes ne connaissent qu’une seule manière de découper un système en parties : en faire des API. Si ce n’est pas exposé via une API, elles considèrent que c’est juste du code opaque, incompréhensible et inutilisable
    • C’est un peu dommage aussi que, pour diverses raisons, les microservices soient parfois un choix pragmatique et donc effectivement utilisés
    • Je continue à voir des petites équipes de deux développeurs transformer en quelque chose de “microservices” une simple petite web app avec cinq formulaires seulement : base de données partagée, gestion d’API, tâches batch via des files, notifications par e-mail, ajout d’une plateforme d’observability maison, etc. Et au final, même les formulaires les plus ordinaires deviennent des SPA, parce que « c’est plus simple ». J’en suis venu à comprendre que les “architectures” et les “patterns” servent à créer du travail pour des développeurs inutiles. Sans ça, ce sont des gens qui seraient dans la rue avec une pancarte disant : « J’utiliserai du JavaScript pour un demi-sandwich »
    • Ma petite théorie du complot, c’est que ce résultat vient du fait que les fournisseurs cloud ont poussé le pattern microservices. - Impossible de faire tourner tout ça sans orchestrateur comme K8S, ce qui facilite la vente de cloud managé - Plus de trafic réseau et de consommation CPU, donc plus de facturation - Le partage d’état à grande échelle devient difficile, ce qui pousse vers des bases de données managées et des files d’événements - L’exécution locale devient compliquée, ce qui finit aussi par transformer l’environnement de développement en coût cloud - On devient dépendant de façons de faire propres au cloud, ce qui rend la sortie difficile. À l’époque, on nous vendait le cloud comme une manière de réduire les coûts IT, ce qui est franchement risible. On savait déjà depuis les années 2000 que c’était un mirage, et au bout du compte tout devient simplement plus cher
  • La phrase « complexité contre face-à-face avec un tyrannosaure, grug choisit la complexité : au moins le tyrannosaure est visible » m’a tellement marqué qu’elle me revient au moins une fois par semaine
    • Citation : « En tombant, Leyster n’avait pas lâché sa pelle. Dans la panique, il l’avait oublié. Il l’a donc brandie désespérément contre la jambe du jeune tyrannosaure... » Une scène qui décrit avec intensité un combat de survie extrême contre un tyrannosaure. Finalement, sa collègue Tamara surmonte la crise en lui plantant courageusement une lance en plein visage. Le combat, la tension, puis le silence de la scène sont saisissants
    • grug n’a clairement jamais dû affronter un tyrannosaure “invisible”. Moi, je suis encore en duel un contre un avec un tyrannosaure invisible, et c’est vraiment pénible
  • Ce qui force l’admiration dans cet article, c’est que son auteur est capable de faire des choses “plus complexes”, mais choisit par expérience de ne pas prendre cette voie. Bien sûr, il y a un temps et un lieu pour l’abstraction et la complexité, mais la philosophie grug dit qu’elles n’ont pas de valeur intrinsèque en soi. Je trouve que c’est très juste. J’ai aussi l’impression que l’IA est plus efficace sur du code cohérent et fondé sur des données
    • Le bon moment pour utiliser complexité et abstraction, c’est quand cela rend le code plus facile à comprendre qu’avant. Il faut absolument garder en tête la condition suivante : « ne pas nécessiter un cours supplémentaire juste pour comprendre ». (Selon le contexte, bien sûr)
    • « Tout doit être rendu aussi simple que possible, mais pas plus simple. »
  • J’ai du mal à croire que ce texte date de 2022. J’ai l’impression de l’avoir lu il y a déjà dix ans et de l’avoir toujours considéré comme un “classique”
  • Cet essai est mon texte préféré sur la façon de construire du logiciel. Le style a aussi son charme (même si certains peuvent le trouver rebutant), et le fond reste toujours valable
  • « Triste mais vrai : apprendre à dire “oui”, puis apprendre à blâmer un autre grug quand ça échoue, meilleure stratégie de carrière » — ce bout de code, c’est la réalité. Au début, je pensais naïvement qu’au travail les problèmes venaient simplement d’un manque de communication dans l’équipe technique, mais avec le temps j’ai appris que, comme grug, c’était bel et bien le cas
  • C’est de loin la meilleure explication du visitor pattern que j’aie vue jusqu’ici
    • Je ne travaille pas dans une codebase OO typique, donc je ne savais pas vraiment ce qu’était le visitor pattern, mais j’aimerais recommander le livre « Crafting Interpreters », qui explique comment on l’utilise concrètement dans la construction d’un interpréteur ou d’une VM. En le lisant, j’ai essayé de comprendre pourquoi cette complexité existait, mais au final je l’ai remplacé par un tagged union. C’est peut-être simplement parce que je ne suis pas très à l’aise avec l’OO, mais le fond de l’article grug est justement là : quand on n’a pas besoin de s’infliger de la complexité et de l’indirection, il existe des méthodes plus intuitives
    • Je suis assez sensible aux noms, et le nom visitor pattern me dérange parce qu’il est beaucoup trop vague. En pratique, je n’ai jamais créé quelque chose nommé Visitor. Par exemple, pour un exercice sur un arbre syntaxique (AST), des noms comme AstWalker, AstItem::dispatch(AstWalker) ou AstWalker::process(AstItem) me paraissent bien plus parlants que Visitor. Le mot visitor, le fait de “visiter”, est trop abstrait et n’a pas beaucoup de sens. Selon le contexte, il faut autre chose, et il suffit sinon d’indiquer en commentaire qu’il s’agit d’un visitor pattern pour que tout le monde comprenne. Autrefois, quand j’ai dû comparer/importer des données entre deux arbres d’objets, j’avais utilisé le nom AbstractImporter. C’était plus spécifique, le processus et le rôle étaient plus clairs. Ce n’était pas un visitor pattern typique
    • En cherchant, j’ai effectivement vu des avis disant que c’était “Bad”. haha
  • Je partage aussi ces fils liés. Quelqu’un a d’autres avis ou d’autres textes à recommander ?<br/><i>The Grug Brained Developer (2022)</i> - https://news.ycombinator.com/item?id=38076886 - octobre 2023 (192 commentaires)<br/><i>The Grug Brained Developer</i> - https://news.ycombinator.com/item?id=31840331 - juin 2022 (374 commentaires)