- 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
Avis sur Hacker News
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’essayerprintest 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 utiliserprintprintet du code d’auto-vérification. Ajouter desprintest bien plus rapide que d’entrer pas à pas dans un débogueur. Et en plus, le code avecprintreste 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 boucleprint-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 unprintmontre 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 debuggingprintfa 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 parprintpartir 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 environnementAstWalker,AstItem::dispatch(AstWalker)ouAstWalker::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 nomAbstractImporter. C’était plus spécifique, le processus et le rôle étaient plus clairs. Ce n’était pas un visitor pattern typique