52 points par GN⁺ 2025-02-06 | 6 commentaires | Partager sur WhatsApp
  • Des développeurs expérimentés partagent avec de nouveaux développeurs leur philosophie du développement logiciel
  • Le texte contient des conseils sur divers sujets, notamment éviter les réécritures complètes (ground-up rewrite), la gestion des délais, la qualité du code, l’automatisation et la prise en compte des edge cases

Éviter à tout prix les situations où une réécriture complète (ground-up rewrite) paraît séduisante

  • La tentation d’une réécriture complète apparaît au moment où la dette technique accumulée rend déjà le code existant difficile à maintenir
  • Il faut repérer tôt les signaux d’alerte liés à l’accumulation de complexité du code (même de simples modifications deviennent difficiles, les commentaires/la documentation sont compliqués à rédiger, moins de personnes comprennent le code critique, etc.) et chercher activement des solutions
  • Une fois la phase d’expansion terminée, il faut impérativement passer par une étape d’intégration pour réduire la complexité et remettre de l’ordre dans la qualité
  • Si une réécriture complète devient inévitable, cela signifie que le projet est déjà entré dans une zone de danger
  • Pour réduire les risques, il faut gérer en continu la dette technique et surveiller attentivement la qualité du code

Viser 90 % du travail total avec la moitié du temps disponible

  • Écrire une première version du code qui fonctionne ne représente qu’environ la moitié du travail total
  • Les étapes suivantes (traitement des edge cases, tests, déploiement, documentation, performances, maintenabilité, etc.) demandent bien plus de temps qu’on ne l’imagine
  • Il faut prévoir une marge suffisante pour absorber les problèmes imprévus et les finitions (polishing)
  • Au final, il faut reconnaître l’écart entre « faire vaguement tourner le code une première fois » et « en faire une fonctionnalité terminée », puis l’intégrer au planning

Automatiser les bonnes pratiques

  • Répéter aux développeurs, à l’oral ou dans la documentation, qu’« il faut faire comme ceci » conduit facilement à des erreurs
  • Quand c’est possible, il est plus efficace d’imposer les règles via des tests automatisés ou des scripts, par exemple sous la forme « échec du build en cas de violation d’une règle »
  • Même pour de nouvelles règles introduites plus tard (API interdites, commentaires obligatoires, etc.), une automatisation progressive permet de réduire les erreurs et les oublis
  • Cela dit, tout ne peut pas être automatisé, et des règles trop strictes peuvent nuire au flux de développement ; un équilibre est donc nécessaire

Tenir compte aussi des données extrêmes (pathological)

  • Il est risqué d’évaluer du code uniquement à partir d’entrées normales (golden path)
  • Il faut envisager des situations problématiques comme des requêtes retardées ou interrompues, des volumes massifs de données (de plusieurs millions à plusieurs centaines de millions de lignes), ou des chaînes étranges (très longues, contenant des slashs ou des espaces)
  • La qualité finale du code dépend de la rigueur avec laquelle on prépare les edge cases

Il existe généralement une manière plus simple d’écrire

  • Une fois le code fonctionnel au départ, il est utile de revenir dessus avec un peu de recul pour l’améliorer de façon plus simple et plus claire
  • Même lorsqu’on a trouvé une « solution qui semble bonne », il est important de garder l’attitude qui consiste à vérifier s’il n’existe pas une meilleure solution
  • Le processus de refactorisation d’un code long et complexe vers quelque chose de plus concis améliore la qualité finale

Écrire du code testable

  • Réduire au minimum les interfaces et les effets de bord facilite énormément l’écriture de tests automatisés
  • Si une structure est difficile à tester, il est fort probable que l’encapsulation soit insuffisante
  • Concevoir la structure du code de manière à bien se prêter aux tests permet de détecter les bugs plus tôt

Le fait qu’un code soit « correct en théorie » ne signifie pas que tout est réglé

  • Même si un code semble structurellement sûr, un changement de l’environnement ou de certaines façons de l’appeler peut provoquer des problèmes
  • Pour du code lié à la sécurité, il faut concevoir en tenant compte du fait que, même si les appels actuels sont sûrs, ils pourront changer à l’avenir
  • Le code doit être écrit de manière à être « manifestement sûr et sans danger même en cas de changement »

Commentaires

  • La citation « Si je ne vous ai pas écrit une lettre plus courte, c’est parce que je n’ai pas eu le temps de la faire plus courte » est attribuée à Pascal
  • Toujours faire attention aux erreurs de type off-by-one
  • Ajouter de nouvelles guidelines dans le wiki
    • Si le système de documentation/partage de connaissances de l’entreprise n’est pas bien structuré, l’information se disperse et crée de la confusion
    • Les documents officiels peuvent devenir obsolètes à force de passer par des circuits d’approbation, ou plusieurs wikis peuvent coexister en parallèle sans qu’on sache lequel fait autorité
    • Il est efficace de définir un seul wiki comme source unique, d’y regrouper tous les contenus, puis de laisser les développeurs compléter les manques eux-mêmes ou via de la rétro-ingénierie (reverse-engineering)
    • Quand la documentation est bien reliée à d’autres endroits (source control, commentaires dans le code, etc.), il est plus facile de maintenir l’information à jour
    • Si les tests automatisés et l’environnement de build sont complexes ou peu exposés aux développeurs, on peut se retrouver dans une situation où personne n’a jamais réellement exécuté l’ensemble des tests
    • Il est préférable de garder les scripts de build Jenkins simples, sous une forme comme cd project; ./build-it, et d’inclure aussi ces scripts dans le source control
    • Partager un environnement identique pour toute l’équipe (par ex. image de machine virtuelle, configuration de build) afin que tout le monde puisse lancer builds et tests permet de réduire en amont les problèmes
    • Il est utile de penser aux edge cases dès la phase de développement, par exemple en faisant en sorte que destroy_object(foo) fonctionne de manière sûre même si foo vaut NULL, ou que create_object() appelle destroy_object() en interne en cas d’échec
    • En fin de compte, l’important est que tous les développeurs puissent facilement accéder à la documentation et à l’environnement de build/test, et y contribuer
  • Documentation et tests automatisés : le texte mentionne l’importance des documents ou d’un wiki pour le partage des connaissances, ainsi qu’une recommandation pratique selon laquelle la configuration d’un environnement CI/CD comme Jenkins doit pouvoir être partagée
  • Il n’existe pas de levier de changement de comportement plus efficace que de « rendre les choses pénibles » jusqu’à ce que les gens mémorisent le comportement attendu
  • Comme pour un adage aux échecs — « quand vous voyez un coup qui a l’air bon, jouez-le d’abord » — il existe aussi un point de vue opposé ; la programmation présente la même ambivalence.

6 commentaires

 
bbulbum 2025-02-07

> Il existe généralement une façon plus simple de l’écrire

Je pense que réfléchir à une solution plus concise permet de réduire la complexité du code et des politiques.

 
kipsong133 2025-02-06

> « Terminer 90 % de l’ensemble du travail dans la moitié du temps disponible, si possible »

Je trouve que c’est vraiment très juste. Plutôt que de chercher à implémenter parfaitement du premier coup, faire rapidement une deuxième passe réduit les erreurs et aide aussi à mieux gérer son temps.

 
jhj0517 2025-02-06
  1. Automatiser les bonnes pratiques (tests, code, pipeline CI/CD)
  2. Mieux vaut d’abord faire fonctionner le code, puis, avec un peu de recul, le retravailler pour le rendre plus simple et plus clair
  3. Éviter à tout prix les situations où une réécriture complète (ground-up rewrite) paraît séduisante
 
winterjung 2025-02-06

> Toujours faire attention aux erreurs de type off-by-one

Je me demandais ce qu’était une erreur off-by-one, et il s’agit en fait d’une catégorie d’erreurs liées aux conditions aux limites, comme for (int i = 1; i < n; ++i) { ... } ou for (int i = 0; i <= n; ++i) { ... }.

 
ethanhur 2025-02-06

> Éviter les situations où une réécriture complète (ground-up rewrite) semble séduisante, quel qu’en soit le prix.

J’ai l’impression que c’est un piège qui empêche de nombreuses entreprises IT de faire progresser leur activité, et que les organisations d’ingénierie qui ont accumulé beaucoup de dette technique sans parvenir à la résorber (ou sans vouloir la résorber) ont tendance à trouver l’idée d’une réécriture complète attrayante.

Je partage profondément l’avis des auteurs selon lequel, en l’absence d’une raison réellement convaincante, il faut éviter autant que possible une réécriture.

 
GN⁺ 2025-02-06
Avis Hacker News
  • Le développement logiciel est un processus itératif d’essais et d’apprentissage. Les développeurs expérimentés approfondissent leur compréhension en écrivant du code et en le testant, et apprennent énormément, mais cela se voit rarement dans les réunions ou la planification. Les développeurs juniors ont du mal à produire du code prêt pour la production et hésitent à accepter qu’une partie du travail soit abandonnée. Des managers ayant peu d’expérience du développement peuvent aggraver ces problèmes

  • La citation « Je vous demande pardon de vous écrire une si longue lettre, parce que je n’ai pas eu le loisir de la faire plus courte » est attribuée au mathématicien et philosophe français Blaise Pascal, et signifie qu’il est plus difficile d’écrire de façon concise

  • La règle du 90/50 souligne l’importance des tests et de la gestion des exceptions pour améliorer la qualité du code. Mettre en place des tests automatisés aide à définir des attentes claires pour la base de code

  • L’enseignement de l’informatique pousse souvent à écrire du code complexe, alors qu’il est important d’écrire du code facile à lire. Cela nécessite un bon nommage des variables et des fonctions, un formatage cohérent et une conception modulaire

  • Un mécanisme appelé Ratchet offre une méthode idéale pour progresser durablement

  • Les tentatives de généraliser l’expérience et la connaissance du domaine peuvent conduire à de mauvaises généralisations. Le développement est l’art de gérer la complexité, et il est essentiel d’apprendre par l’échec

  • La citation « Les premiers 90 % du travail prennent 90 % du temps, et les 10 % restants prennent encore 90 % du temps » illustre bien la réalité du développement. Lorsqu’on prend en compte la gestion des exceptions, les erreurs, l’utilisabilité, la sécurité et d’autres aspects, beaucoup de travail imprévu apparaît

  • L’article de Joel Spolsky met en garde contre les risques de la réécriture et souligne la sagesse du DevOps

  • Il faut optimiser la lisibilité du code et reconnaître que plus le délai avant la découverte d’un bug est long, plus son coût augmente. Il est bénéfique de privilégier les principes de la programmation fonctionnelle et d’utiliser un système de types fort