5 points par GN⁺ 2025-06-18 | 1 commentaires | Partager sur WhatsApp
  • À travers le témoignage d’un développeur au « petit cerveau » ayant programmé longtemps, l’article souligne que le plus grand ennemi du développement logiciel est la complexité, et que l’attitude consistant à la réduire doit être au cœur du travail au quotidien
  • L’arme de base contre la complexité est de dire « no » ; lorsqu’un compromis est nécessaire, on cherche des solutions pragmatiques avec des solutions 80/20, des prototypes, de petits refactorings et des abstractions tardives
  • Les tests doivent être centrés sur les tests d’intégration une fois que le code s’est quelque peu stabilisé, avec des tests unitaires et quelques tests de bout en bout en appoint ; les bugs doivent d’abord être reproduits par des tests de régression avant d’être corrigés
  • Les outils, les systèmes de types, le logging, les débogueurs et les API simples réduisent la charge de mémoire et de raisonnement des développeurs, mais l’excès de génériques, de callbacks, de microservices et de frameworks front-end peut accroître la complexité
  • Il faut aborder le code existant et les processus organisationnels avec humilité, ne pas supprimer à la légère du code que l’on ne comprend pas, et instaurer une culture où l’on peut dire « c’est trop complexe »

La complexité est l’ennemi éternel du développeur

  • Dans le développement logiciel, l’ennemi le plus dangereux est la complexité
    • La complexité s’introduit progressivement dans une base de code, au point qu’un changement à un endroit casse des parties qui semblaient sans rapport
    • Grug la compare à un « démon » invisible, une entité que le développeur ne peut ni voir directement ni terrasser facilement
  • La complexité peut aussi arriver par l’intermédiaire de développeurs ou de chefs de projet bien intentionnés
    • Plus les fonctionnalités, abstractions et processus se multiplient, plus le code devient difficile à comprendre
    • Grug reconnaît que lui-même a parfois été celui qui a introduit de la complexité

Comment utiliser « no » et « ok »

  • L’arme la plus puissante contre la complexité est « no »
    • Ne pas créer de fonctionnalités inutiles
    • Ne pas créer d’abstractions inutiles
  • Cela dit, même si « no » est un bon conseil d’ingénierie, ce n’est pas toujours avantageux pour une carrière
    • « yes » peut mener à davantage de reconnaissance ou à des postes de management
    • Malgré cela, pour rester honnête avec soi-même en tant que développeur, « no » est important
  • Lorsqu’un compromis est nécessaire, répondre « ok » puis chercher une solution 80/20
    • Une solution 80/20 consiste à satisfaire 80 % des besoins avec 20 % du code
    • Même sans offrir toutes les fonctionnalités décoratives, elle peut apporter l’essentiel de la valeur tout en contenant la complexité
    • Il estime parfois préférable de résoudre le problème de manière simple sans expliquer au chef de projet tous les détails d’implémentation

Structuration du code et refactoring

  • Au début d’un projet, il ne faut pas découper l’application trop vite
    • Au départ, la forme du système est encore floue et l’on ne comprend pas encore entièrement ce que l’on construit
    • Avec le temps, de bons points de coupe (cut points) apparaissent
  • Un bon point de coupe a une interface étroite avec le reste du système
    • Il masque la complexité interne derrière un petit nombre de fonctions ou d’abstractions
    • Grug compare cela au fait d’enfermer le démon de la complexité dans un cristal
  • Quand un développeur à gros cerveau veut créer beaucoup d’abstractions au début d’un projet, il faut trouver des moyens de limiter les dégâts
    • On peut l’orienter vers des livrables qui ne nuisent pas directement au code, comme des diagrammes UML
    • Demander « une démo qui fonctionne demain » permet de confronter rapidement le code réellement exécutable à la réalité
    • Cette approche par démo peut être appelée prototype
  • Le refactoring devient utile plus tard dans le projet, une fois que le code s’est stabilisé
    • Plus un refactoring est gros, plus il a de chances d’échouer
    • Il préfère avancer par petites étapes tout en gardant le système en fonctionnement
    • Les tests de bout en bout deviennent une bouée de sauvetage pendant un refactoring, mais il peut être difficile d’en comprendre la cause lorsqu’ils cassent
  • L’excès d’abstraction peut mener à l’échec d’un refactoring et du système
    • J2EE est cité comme exemple d’abstraction excessive
    • L’introduction d’OSGi visait à réduire la complexité, mais aurait créé une complexité encore plus forte et nécessité plusieurs années-personnes de reprise

Stratégie de test

  • Les tests font gagner beaucoup de temps, mais il se montre sceptique envers le « toujours les tests d’abord »
    • Quand on ne comprend pas encore le domaine, il est difficile de savoir quoi tester
    • Grug préfère écrire la plupart des tests après le prototype, lorsque le code commence à se stabiliser
  • Même si l’on écrit les tests plus tard, il faut de la discipline
    • Il ne faut pas sauter les tests sous prétexte que « ça marche sur ma machine »
    • Rien ne garantit que cela fonctionne sur une autre machine, ni sur la même machine dans le futur
  • Il distingue les rôles des tests unitaires, des tests d’intégration et des tests de bout en bout
    • Les tests unitaires sont utiles au début d’un projet, mais ils cassent facilement lorsque l’implémentation change et peuvent compliquer le refactoring
    • Les tests end to end montrent le comportement du système complet, mais lorsqu’ils cassent, il est difficile d’en identifier la cause, et s’ils cassent trop souvent, ils peuvent être ignorés
    • Les tests d’intégration sont assez hauts niveau pour vérifier la justesse du système, et assez bas niveau pour permettre d’en voir la cause au débogueur : ils se rapprochent du « sweet spot »
  • La composition idéale des tests est la suivante
    • Quelques tests unitaires au début
    • Des tests d’intégration solides à mesure que des points de coupe apparaissent et que le système se stabilise
    • Un petit ensemble bien maintenu de tests de bout en bout couvrant seulement les fonctionnalités UI les plus courantes et les cas limites importants
  • Il préfère n’utiliser le mocking que rarement, et même lorsqu’il est nécessaire, seulement à de grands niveaux comme les frontières du système
  • Pour corriger un bug, il fait une exception : il commence par un test de régression pour reproduire le bug, puis le corrige

Processus, agile et attitude face au code existant

  • L’agile n’est pas la pire chose, mais il est difficile de dire que c’est simplement bien
    • Comme manière d’organiser le développement, cela peut être correct dans une certaine mesure
    • Il se méfie des experts agile qui, à chaque échec, disent que « l’agile n’a pas été fait correctement »
  • Pour réussir, les prototypes, les outils et le recrutement de bons développeurs sont plus importants
    • Les processus agiles peuvent aider, mais les prendre trop au sérieux peut être nuisible
    • no silver club, autrement dit il n’existe pas de solution miracle à tous les problèmes logiciels
  • La barrière de Chesterton sert d’avertissement contre la suppression de code existant
    • Si l’on ne connaît pas l’utilité d’une barrière, il faut d’abord la comprendre et ne pas la retirer immédiatement
    • Même du code laid mérite du respect s’il fonctionne aujourd’hui
    • Plus le système est grand, plus il faut commencer par comprendre avant d’améliorer
  • Les tests peuvent fournir des indices sur les raisons de l’existence d’une certaine « barrière »

Outils et systèmes de types

  • Les outils réduisent la charge de mémoire et de raisonnement que les développeurs doivent assumer eux-mêmes
    • Quand on arrive dans un nouvel environnement, consacrer du temps à apprendre les outils qui l’entourent peut améliorer la productivité
    • S’il n’y a pas de documentation, il peut falloir interroger d’autres développeurs pour comprendre
  • La complétion de code des IDE évite d’avoir à mémoriser toutes les API
    • Il dit que programmer en Java est presque impossible sans complétion de code
  • Un bon débogueur est très important
    • Il faut apprendre en profondeur les fonctionnalités comme les points d’arrêt conditionnels, l’évaluation d’expressions et l’exploration de la pile
    • Selon lui, apprendre le débogueur à un nouveau développeur peut lui en apprendre davantage sur l’ordinateur qu’un cours universitaire
  • La plus grande valeur d’un système de types est que « lorsque l’on appuie sur le point (.), les actions possibles apparaissent »
    • La correction des types est appréciable, mais pour Grug, le support des outils et la complétion de code ont davantage de valeur
    • Trop d’abstraction de types ou de génériques peut rendre le code métier difficile à comprendre
    • Il préfère limiter l’usage des génériques essentiellement aux classes conteneurs

Expressions, DRY et séparation des préoccupations

  • Il préfère du code facile à déboguer à du code court
    • Plutôt que d’écrire une condition complexe sur une seule ligne, la découper en variables intermédiaires facilite la compréhension du résultat et du sens de chaque expression
    • Même si cela augmente le nombre de lignes, les conditions deviennent plus faciles à comprendre et à déboguer
  • DRY est un bon conseil, mais il faut trouver un équilibre
    • Une duplication de code simple et claire peut être préférable à des callbacks, closures ou modèles objet complexes
    • Si l’élimination de la duplication accroît la complexité, c’est une perte
  • Il est plus critique envers la separation of concerns
    • L’exemple classique en développement web est la séparation entre CSS, HTML et JavaScript
    • Grug préfère comme alternative la locality of behavior
    • Placer le code associé près de ce sur quoi il agit permet de savoir immédiatement ce que fait un élément quand on le regarde
  • Les closures sont utiles pour de bons usages, comme l’abstraction d’opérations sur des collections
    • Mais comme le sel, les systèmes de types et les génériques, une petite quantité suffit, et l’excès est nocif
    • Le « callback hell » de JavaScript est cité comme exemple d’abus de closures

Logging, concurrence et optimisation

  • Le logging est particulièrement important dans les environnements de déploiement cloud
    • Il faut journaliser chaque branche logique importante
    • Si une requête traverse plusieurs machines, inclure un request ID dans tous les logs afin de pouvoir les relier
    • Si possible, ajuster dynamiquement le niveau de log
    • Si possible, ajuster aussi le niveau de log par utilisateur
  • Les bibliothèques de logging Java peuvent être complexes, mais investir correctement dans l’infrastructure de logging rapporte beaucoup ensuite
  • La concurrence est quelque chose qu’il faut craindre
    • Utiliser autant que possible des modèles simples, comme des handlers de requêtes web sans état et des files de travaux distants indépendantes
    • Il estime que l’optimistic concurrency fonctionne bien dans le domaine web
    • Les thread local variables sont utilisées parfois, surtout lorsqu’on écrit du code de framework
    • Même les structures de données concurrentes comme ConcurrentHashMap de Java doivent encore être utilisées avec prudence
  • L’optimisation doit commencer seulement lorsqu’on dispose d’un vrai profil de performance
    • Il est d’accord avec « premature optimization is the root of all evil »
    • Le véritable goulot d’étranglement peut différer de ce que l’on imagine
    • Il ne faut pas regarder uniquement le CPU : les appels réseau peuvent représenter des centaines de milliers de cycles CPU et doivent donc être réduits autant que possible

API, parsing et patron visiteur

  • Une bonne API évite aux développeurs d’avoir à trop réfléchir
    • Une mauvaise API apparaît lorsqu’elle est conçue autour des détails internes d’implémentation ou du modèle de domaine, ou lorsqu’elle est trop abstraite
    • Il préfère une stratification qui propose une API simple pour les cas simples et une API plus complexe pour les cas complexes
  • Pour une API orientée objet, il estime qu’il vaut mieux rattacher les fonctionnalités aux objets concernés
    • Il cite comme mauvais exemple le flux Java où il faut convertir une liste en Stream pour la filtrer, puis la collecter à nouveau en List
    • Selon lui, une opération courante comme filter() devrait être sur la liste et retourner une liste
  • Les parsers recursive descent sont une approche amusante et élégante
    • Il critique les générateurs de parsers, difficiles à comprendre et à déboguer
    • Il affirme que les parsers réellement utilisés en production sont presque toujours à descente récursive
  • Il recommande Crafting Interpreters de Bob Nystrom
    • On peut le lire gratuitement en ligne, mais il recommande aussi d’acheter le livre
    • Il décrit toutefois le visitor pattern comme un piège
  • Son évaluation du visitor pattern tient en un mot : « bad »

Front-end, microservices et modes

  • Les microservices sont risqués parce qu’ils « ajoutent des appels réseau au problème le plus difficile : découper correctement un système »
  • En développement front-end, la complexité est particulièrement forte
    • Il critique les situations où même une simple sauvegarde de formulaire ou un site vitrine utilisent une bibliothèque SPA, une API JSON GraphQL et un backend HTTP
    • Il dit que séparer front-end et backend crée deux domiciles pour la complexité
  • Grug explique avoir créé htmx et hyperscript pour réduire la complexité
    • Il préfère conserver du HTML simple et éviter d’écrire beaucoup de JavaScript
    • Il reconnaît que React peut être meilleur pour trouver un emploi et pour certains types d’applications
  • Le développement connaît beaucoup de modes, particulièrement côté front-end
    • Le backend est devenu plus ennuyeux, et beaucoup de mauvaises idées y ont déjà été essayées, selon lui
    • Il recommande d’aborder les nouvelles approches révolutionnaires avec autant de prudence qu’une pincée de sel
    • Beaucoup d’idées ont déjà été tentées au moins une fois, et de mauvaises idées recyclées peuvent faire perdre du temps

Peur et syndrome de l’imposteur

  • Il est sain qu’un développeur senior dise publiquement « c’est trop complexe »
    • Les développeurs ont du mal à dire qu’ils ne comprennent pas à cause de la Fear Of Looking Dumb (FOLD), la peur de passer pour idiot
    • Si un senior l’admet d’abord, les juniors peuvent eux aussi parler de la complexité et de leur incompréhension
  • FOLD est, surtout chez les jeunes développeurs, l’une des principales sources de pouvoir de la complexité
    • Un sens de l’humour et le souvenir de ses échecs passés peuvent aider
  • Le syndrome de l’imposteur est lui aussi courant dans le développement
    • Grug dit se situer entre le sentiment de tout maîtriser et celui de ne pas savoir ce qu’il fait
    • Même avec les réussites open source de htmx et _hyperscript, il dit craindre encore les erreurs et les échecs
  • Si tout le monde ressent le syndrome de l’imposteur, il vaut mieux considérer que personne n’est vraiment un imposteur

Lectures recommandées et conclusion

1 commentaires

 
GN⁺ 2025-06-18
Avis sur Hacker News
  • Si le professeur Carson lit les commentaires, je tiens à le remercier sincèrement pour toutes ses contributions au fil des années.
    À l’université, je ne comprenais pas pourquoi on apprenait HTMX, ni pourquoi il en était si enthousiaste, mais quelques années plus tard, je comprends enfin. Tout était dans HTML over the wire.
    En travaillant comme Staff Ruby on Rails Engineer, j’ai vu ses travaux dans Hotwire, et c’était aussi formidable de le voir apparaître de temps à autre sur Hacker News ou échanger avec les développeurs de Hotwire sur GitHub. Il est largement respecté et apprécié comme une lumière dans la communauté de la programmation.

  • Je suis d’accord avec l’idée qu’« un bon débogueur vaut autant qu’une pierre brillante, et en fait même davantage ».
    J’ai connu à la fois de petites startups et des équipes Big Tech « d’élite », mais j’étais presque toujours la seule personne de l’équipe à utiliser un débogueur. Dans le monde réel, du moins côté technologies web, la plupart des gens semblent déboguer avec des instructions print.
    S’arrêter sur une ligne de code intéressante pendant l’exécution des tests et regarder la pile d’appels qui y a mené est bien plus simple que d’exécuter le code mentalement vers l’avant. Pour un jeune grug, cette compétence est un petit superpouvoir ; si possible, cela vaut la peine de passer du temps à la faire fonctionner sur sa propre codebase.

    • Il y a quelques années, il y avait eu une bonne discussion sur ce sujet, et le commentaire le mieux classé partageait une citation de Brian Kernighan et Rob Pike. Difficile de les qualifier tous les deux de jeunes grugs.
      Ils disaient ne pas vraiment utiliser les débogueurs au-delà des traces de pile ou de l’inspection d’une ou deux variables, car il est facile de se perdre dans les détails des structures de données complexes et du flux de contrôle ; ils trouvaient plus productif d’ajouter des instructions d’affichage et du code d’auto-vérification aux points clés.
      Je suis globalement d’accord. Pour presque tout ce que je fais, une boucle hypothèse-log-exécution permet d’arriver beaucoup plus vite à la réponse. Il ne s’agit pas d’essayer d’exécuter le code dans ma tête : j’ai déjà un modèle de travail de la façon dont le code s’exécute, je sais quelle sortie devrait apparaître si ce modèle est correct, et une sortie incorrecte me permet de saisir rapidement la situation réelle.
      [0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
    • J’aimerais vraiment utiliser un vrai débogueur, mais comme je n’ai travaillé que dans de grandes entreprises, ce n’était pas une option.
      Dans une architecture en maillage de microservices, il est difficile de faire tourner correctement quoi que ce soit en local, et les environnements de test ne sont souvent pas configurés pour permettre d’attacher un débogueur pas à pas. Au final, il ne reste que le débogage par print, et même cela devient impossible si le système de logging lui-même pose problème ou si le programme meurt avant de flusher les logs.
    • Quand j’arrive dans une codebase, l’une des premières choses que je fais, même si je ne suis pas en train de corriger un problème immédiatement, est de mettre en place un environnement IDE/éditeur permettant de lancer rapidement le programme dans un débogueur.
      On en a forcément besoin assez vite. Mais même en collaboration, j’ai été surpris de voir des gens qui ne savaient tout simplement pas utiliser un débogueur. Quand je dis « mets un point d’arrêt là », « maintenant entre dans la fonction et regarde l’état des variables », « ça, passe par-dessus », j’ai droit à chaque fois à un regard vide.
    • Moi aussi, je suis plutôt du côté de ceux qui utilisent très peu les débogueurs.
      À strictement parler, je n’ajoute pas puis ne supprime pas des instructions print ; j’ajoute du code de logging destiné à rester. Pour les interfaces importantes, je commence généralement par enregistrer au niveau INFO l’entrée et la sortie des fonctions, ainsi que les valeurs des paramètres, puis, en utilisant le système, j’ajoute des logs plus détaillés aux endroits qui semblent nécessiter un examen plus poussé.
      Je soigne aussi beaucoup le format des logs. Quand je travaillais sur des systèmes distribués, il était très utile d’aligner précisément le préfixe de chaque ligne de log. En mettant l’ID de nœud, le pid et le timestamp dans une largeur fixe, puis en téléchargeant et en triant les logs de tout le cluster, on voyait le comportement de plusieurs nœuds interleavé dans un seul fichier.
    • Utiliser un débogueur sur mon propre code est facile, et j’aime ça.
      Mais dès que le débogueur descend profondément dans les bibliothèques ou frameworks que j’utilise, je me perds et je finis par détester l’expérience. Ces frameworks ou bibliothèques représentent des dizaines de milliers d’heures-personnes de travail accumulé ; j’ai l’impression que cela dépasse largement mon niveau.
  • Il y a beaucoup de phrases qui sont de véritables pépites ici, mais celle-ci sur les microservices est ma préférée : « grug se demande pourquoi des gens intelligents prennent le problème le plus difficile — découper correctement un système — et y ajoutent en plus des appels réseau »

    • J’essaie sans cesse d’expliquer ça à de petites équipes de développement de 1 ou 2 personnes
      Elles prennent volontiers une petite appli web triviale avec cinq formulaires environ, la découpent en « microservices », leur font partager la même base de données, ajoutent une couche de gestion d’API, une file pour des traitements batch volumineux (en mégaoctets), un système de notifications par e-mail, et même une plateforme d’observabilité maison, puis transforment de simples formulaires web en SPA en disant que « c’est plus facile »
      Je comprends maintenant que « l’architecture » et les « patterns » sont un programme de création d’emplois pour développeurs inutiles. Sinon, ils seraient probablement dans la rue avec une pancarte « écris du JavaScript contre sandwich »
    • Certaines personnes semblent ne connaître aucun autre moyen de diviser un système en parties plus petites
      Pour elles, tant que ce n’est pas exposé sous forme d’appel API, ce n’est qu’un bloc de code opaque qu’elles ne peuvent ni comprendre ni réutiliser
    • Une frontière réseau fournit un outil de décomposition que la plupart des systèmes de modules des langages n’ont pas
      Plusieurs packages peuvent collaborer en interne tout en n’exposant qu’une petite API au reste de la base de code. Le fait que ce soit du réseau force les modules à n’échanger que des données, pas des callbacks ou des comportements, et les pousse à faire évoluer leurs interfaces de manière rétrocompatible. C’est ce qui permet de « hot reloader » différents modules à des moments différents sans tout faire exploser
      J’ai l’impression qu’on pourrait obtenir l’essentiel sans véritable saut réseau, mais je n’ai pas encore vu de tentative sérieuse
    • J’ai une théorie du complot selon laquelle les microservices sont un pattern poussé par les sociétés de cloud pour inciter les gens à construire ce genre d’applications
      Sans orchestrateur comme K8S, elles sont impossibles à exécuter, et comme elles sont difficiles à installer et à opérer, elles favorisent la vente de cloud managé. Elles consomment plus de bande passante réseau et de CPU, deux choses facturables
      Elles rendent difficile le partage et le maintien d’un état complexe ou volumineux à l’intérieur de l’application, ce qui pousse à utiliser en remplacement des bases de données managées ou des services de files d’événements. Dans un monolithe, on utiliserait une queue ou un channel, mais avec des microservices on se met à vouloir une énorme bête comme Kafka
      L’exécution en local devient aussi difficile, ce qui rend nécessaire un environnement de développement dans le cloud, et il peut même falloir plusieurs environnements de dev et de test. On finit par dépendre de caractéristiques propres à un cloud donné, comme sa manière de gérer le réseau, ce qui accroît aussi le verrouillage cloud
      Je ne sais pas si vous vous souvenez de l’époque où le cloud était vendu comme un moyen de réduire les coûts IT. C’était risible ; dès les années 2000, je savais que c’était du pipeau et que ça finirait par augmenter tous les coûts
    • Je vois surtout ça comme une manière de découper un système entre équipes
      Parce que c’est plus facile à gérer ainsi ; c’est moins une décision technique qu’une question de mode d’organisation du développement. Si l’alternative est un monorepo, personnellement je trouve ça encore pire
  • « Entre la complexité et un 1 contre 1 avec un tyrannosaure, grug choisit le tyrannosaure. Au moins, grug peut voir le tyrannosaure »
    Je repense à cette phrase au moins une fois par semaine

    • grug n’a clairement jamais eu affaire à un tyrannosaure invisible
      Ce grug-ci est en 1 contre 1 avec un tyrannosaure transparent, et il est maudit
    • En lisant ça, j’ai eu l’impression que mon troisième œil s’ouvrait. C’était vraiment inspirant
  • L’une des valeurs de ce texte, c’est qu’une personne capable de faire des choses plus sophistiquées et complexes cherche délibérément, par expérience, à les éviter
    Il y a évidemment des moments et des endroits où la sophistication et des abstractions plus élevées sont nécessaires. Mais la philosophie grug dit qu’il n’y a pas de valeur intrinsèque à faire ce genre de choses pour elles-mêmes, et ça me semble être un conseil assez juste
    J’ai aussi constaté que l’assistance par IA est plus efficace sur du code cohérent, ordinaire et centré sur les données. Cela peut dépendre du contexte

    • Ça correspond parfaitement au mème de la courbe en cloche
      Le développeur débutant écrit du code simple, le développeur intermédiaire écrit du code complexe, et le développeur expert revient au code simple
    • Le bon moment et le bon endroit pour utiliser la sophistication et l’abstraction, c’est quand elles rendent réellement le code plus facile à comprendre, et pas quand il faut d’abord avoir suivi un cours spécial pour les comprendre
      Les cours qu’on peut supposer que tout le monde a évidemment suivis dépendent du contexte
    • « Il faut rendre toute chose aussi simple que possible, mais pas plus simple »
  • L’une des ironies du développement logiciel moderne, c’est qu’on introduit de la complexité en pensant qu’« au final, cela fera gagner du temps ».
    Parfois c’est vrai, et cela en fait réellement gagner, mais ce n’est pas toujours le cas, ni même souvent forcément le cas.
    DRY mène parfois à des abstractions prématurées. Au moment où l’on se dit : « ce pattern servira aussi ailleurs, je vais extraire la partie commune », le démon de la complexité s’invite.
    On veut attraper autant de bugs que possible à la compilation, mais pour cela le compilateur doit en savoir davantage sur ce qu’on essaie réellement de faire, et on finit par créer des types complexes qui grignotent la compréhension.
    Pour éviter le boilerplate, on crée des macros ou des DSL complexes, mais à cause de la loi des abstractions qui fuient, le jour où il faut comprendre l’implémentation réelle, on a la tête qui explose.
    Ce qui rend tous ces exemples difficiles, c’est que ce sont parfois de bonnes idées. Savoir juger quand introduire de la complexité revient à simplifier est, à mon avis, le signe d’un bon ingénieur logiciel.

    • Par expérience, SPoT (single point of truth, point unique de vérité) s’est avéré une meilleure maxime que DRY.
      La logique métier devrait idéalement être définie à un seul endroit, mais le reste peut être dupliqué si nécessaire, et ce n’est pas mauvais en soi.
      Pour tempérer DRY, je mets aussi en avant la « règle de trois » : du code copié-collé jusqu’à trois fois, ça va ; ensuite seulement il vaut la peine d’envisager une abstraction. Bien sûr, il n’existe pas de règle empirique valable dans tous les cas, et ce sens-là est difficile à enseigner.
    • En moyenne, je pense encore que la plupart du code n’est pas assez DRY, mais dans mes projets personnels, j’ai récemment adopté le principe suivant : « il n’y a pas d’application, il n’y a que des écrans ». Fait intéressant, j’utilise HTMX, qui semble avoir été créé par l’auteur de ce blog.
      En général, je construis des apps web avec des frameworks de type Sinatra comme Flask, en écrivant des fonctions qui répondent à des patterns d’URL. Un « écran » peut être composé d’une ou plusieurs fonctions qui fonctionnent ensemble et des templates HTML associés.
      Si l’application configure des choses comme la connexion à la base de données, l’emplacement des fichiers, les en-têtes et pieds de page HTML, alors pour le reste il y a très peu de couplage entre les écrans. Si j’ai besoin d’un nouvel écran, je peux copier un écran existant et le modifier, ou demander à un LLM de créer un écran ou un endpoint ; si le résultat est mauvais, je recommence.
      Dans un ancien travail, je développais un framework appelé Themis pour créer des jeux d’entraînement ML ; il utilisait des microservices, React, Docker, etc. Le besoin réel était d’ajouter sans cesse de nouvelles tâches et, pour chacune, de créer rapidement un écran simple mais très optimisé. Quand il faut prendre 20 000 décisions, cliquer une fois à chaque fois est déjà pénible ; si l’on doit cliquer quatre fois, cela fait 80 000 clics et on abandonne.
      Avec l’architecture de l’époque, il fallait écrire l’endpoint API de l’application JAXB et les composants d’une app React monolithique, puis attendre 20 minutes que TypeScript, Docker et javac tournent. Avec de la chance, ça démarrait ; sinon, retour au début.
      J’ai écrit une critique de Themis et conçu Nemesis avec pour objectif de développer rapidement de nouvelles tâches, mais dans mon ancien travail ce n’est pas la voie qui a été choisie. Malgré cela, Nemesis et moi avons depuis traité des millions d’instances de tâches.
    • J’ai un autocollant yin-yang sur mon ordinateur portable : côté yin, j’ai écrit DRY ; côté yang, YAGNI.
    • DRY n’est pas si dangereux.
      Cela ne veut pas dire qu’il faut extraire un helper utilisé à un seul endroit. Même si beaucoup de logique se trouve dans une fonction, une classe, un fichier, etc., tant qu’elle n’est pas copiée, cela reste DRY.
      Les abstractions prématurées existent bel et bien. Le fait que les cours d’informatique enseignent généralement à procéder ainsi n’aide pas. Donnez une base MySQL à un débutant, et la première chose qu’il voudra peut-être faire sera d’abstraire MySQL.
    • Grug passe parfois 100 heures à construire une machine pour finir le travail, alors que le faire à la main aurait pris 1 heure. Ou bien il passe 1 heure à construire la machine, puis 100 heures à réparer cette machine idiote.
      Complexe ou simple, peu importe si le résultat n’ajoute pas de valeur. Concentrez-vous d’abord sur l’ajout de plus de valeur que vous n’en retirez, et inquiétez-vous de la complexité ensuite.
  • L’une de mes utilisations préférées des LLM consiste à leur donner cet essai, puis à leur demander de commenter le problème que je traite en ce moment avec la persona de grug-brained developer. C’est bon pour évacuer le stress.

    • Je ne suis pas encore très familier avec les LLM, mais ça a l’air chouette. Je me demande ce que veut dire « donner à manger » cet essai au modèle.
      Est-ce qu’il suffit de commencer le prompt par quelque chose comme « comporte-toi comme le Grug Brained Developer de cet essai » ?
  • « La complexité, c’est très mauvais » est vraiment juste
    Pendant toutes mes années en génie logiciel, cette idée a été l’un des rares principes qui se sont révélés constamment vrais dans toutes les situations. Certains problèmes sont intrinsèquement complexes, mais même dans ces cas-là, il vaut bien mieux prendre le temps d’arriver à la solution la plus simple
    Mes travaux les plus efficaces sont venus après avoir remis en question l’approche précédente et l’avoir simplifiée radicalement. On peut perdre un peu de flexibilité potentielle, mais la plupart du temps, on n’avait pas réellement besoin d’autant de flexibilité qu’on le pensait
    Par exemple, depuis qu’il existe des LLM plutôt corrects qui se comportent de façon agentique, j’évite les types TypeScript trop complexes, fragiles et difficiles à déboguer ; j’écris du code qui ressemble à une spécification, puis je demande au LLM de générer statiquement le code correspondant
    La dépendance ESLint du projet cassait sans cesse après les mises à jour de version, beaucoup de règles n’étaient pas assez sophistiquées pour éviter les faux positifs, et la maintenir correctement avec TypeScript et VSCode était compliqué. Passer à Biome.js a rendu les choses plus simples et suffisamment efficaces, mais récemment des bugs apparaissent. J’ai quand même compris que le linting est un plus appréciable, pas quelque chose qui mérite qu’on passe trop de temps à le maintenir ; je l’ai donc retiré de la chaîne d’outils de build, et il n’est plus nécessaire de l’avoir activé en permanence dans VSCode. Il suffit de lancer Biome de temps en temps pour vérifier uniquement le style et le formatage du code
    En créant un outil personnalisé de migration de données pour le projet, j’ai considéré que les migrations vers l’avant étaient nécessaires, mais que les migrations arrière ne valaient pas le temps ni la complexité de leur implémentation. Si l’on doit revenir en arrière sur une base de données contenant des données, on peut restaurer une sauvegarde ; s’il n’y a pas de données, ou si ce n’est pas une base de données de production, on peut repartir d’un état propre avec un script d’initialisation versionné

    • Les deux premiers exemples me donnent l’impression qu’on cache surtout la complexité en utilisant d’autres outils
      Je ne vois pas non plus très bien en quoi le troisième est plus simple. Avec un esprit mathématique, on peut facilement construire un espace bijectif. Imiter une migration arrière par d’autres moyens peut être plus difficile. Bien sûr, cela dépend des détails, donc ce n’est pas une règle générale
    • Beaucoup de gens parlent de complexité, mais presque personne ne dit ce qu’elle signifie
      Le brillant Rich disait que complect, c’est lier des choses entre elles, et je suis d’accord. Rich disait que la complexité est mauvaise, mais là je ne suis pas d’accord. Lier des choses entre elles est nécessaire. Sans connexions entre elles, on ne peut pas résoudre les problèmes
  • J’ai du mal à croire que cet article date de 2022
    J’aurais juré l’avoir lu il y a dix ans, et qu’il était déjà un classique à l’époque

    • Pareil pour moi. Il s’est probablement inspiré de travaux plus anciens
  • Triste mais vrai : apprendre à dire « yes », puis, en cas d’échec, apprendre à rejeter la faute sur un autre grug, c’est le conseil de carrière idéal
    Quand je suis entré pour la première fois dans le monde de l’entreprise, je pensais que ce n’était pas vrai, et que les équipes techniques manquaient simplement de communication. J’avais tort, et grug avait raison