35 points par GN⁺ 2024-12-23 | 4 commentaires | Partager sur WhatsApp
  • Les logiciels modernes sont mis à jour fréquemment grâce au déploiement continu (CD) et aux tests automatisés (CI), mais les « logiciels conçus pour être utilisés sur le long terme » exigent une approche différente
    • Exemples : centrales nucléaires, avions, pacemakers, systèmes électoraux
    • Dans les domaines où la fiabilité et la stabilité sont cruciales, on privilégie la stabilité et des changements prévisibles plutôt qu’une évolution continue

Principes clés du développement logiciel à long terme

Dépendances

  • Les dépendances d’un logiciel sont un facteur essentiel de sa réussite à long terme
  • Un logiciel doit prendre en compte ses interactions avec le monde extérieur, et des choix fondamentaux comme le langage de programmation sont importants
  • Comprendre la hiérarchie des dépendances logicielles
    • Monde extérieur : logiciels clients que nous ne contrôlons pas (par exemple les navigateurs).
    • Choix fondamentaux : éléments qu’on ne peut modifier qu’au prix d’une réécriture complète de la stack, comme le langage de programmation.
    • Frameworks : Spring Framework, React, etc., fortement couplés à la base de code. Ils peuvent être remplacés, mais à un coût très élevé.
    • Base de données : généralement remplaçable, mais cela demande des ajustements détaillés et du travail.
    • Bibliothèques utilitaires : bibliothèques remplaçables fournissant une fonction précise.
  • Avec le temps, les dépendances et le monde extérieur évoluent :
    • Les changements dans les dépendances peuvent entraîner des modifications du code ou des changements de comportement.
    • La sortie d’une nouvelle version majeure peut provoquer des problèmes de compatibilité.
    • Un projet peut être abandonné ou disparaître.
    • Risques de sécurité : une dépendance peut être compromise par des acteurs malveillants (npm, PyPI, etc.).
    • Commercialisation : de nouveaux propriétaires financés par le capital-risque (VC) peuvent rendre l’outil payant.
    • Problèmes de conflits entre dépendances.
  • Points à vérifier lors du choix de dépendances pour un usage à long terme :
    • Niveau technique : peut-on juger de la qualité en lisant le code source ?
    • Base d’utilisateurs : vérifier qui l’utilise.
    • Objectif du développement : comprendre qui sont les développeurs et quels sont leurs objectifs.
    • Soutien financier : y a-t-il un financement, et d’où vient-il ?
    • Maintenance : vérifier si des correctifs de sécurité sont publiés régulièrement.
      • La communauté pourrait-elle reprendre la maintenance ?
      • Puis-je assurer moi-même la maintenance ?
      • Faut-il, si nécessaire, apporter un soutien financier pour assurer la pérennité du projet ?
    • Les dépendances des dépendances :
      • Examiner aussi l’historique de sécurité des dépendances transitives.
  • Approche réaliste
    • Limiter les dépendances :
      • Un projet qui compte plus de 1600 dépendances a de fortes chances de voir son code évoluer brutalement et devenir instable.
      • Dans un projet avec une multitude de dépendances, il devient même difficile de savoir quel code on déploie réellement.
    • Ajouts prudents :
      • Attribuer une difficulté technique à chaque nouvelle dépendance pour créer un temps naturel de revue.
      • Dans un projet de long terme, mieux vaut éviter les dépendances non indispensables.

Dépendances d’exécution

  • Jusqu’ici, la discussion portait uniquement sur les dépendances de build / compilation.
  • Mais les projets modernes incluent souvent aussi des dépendances d’exécution :
    • Exemples : Amazon S3, Google Firebase.
    • Certaines sont quasiment considérées comme des standards de fait (comme S3).
    • Mais la plupart des dépendances d’exécution impliquent un fort verrouillage fournisseur (lock-in).
  • Dans 10 ans, trouver une alternative aux services utilisés aujourd’hui aura probablement un coût très élevé.
  • Il faut réduire au minimum, voire éliminer, la liste des dépendances à des services tiers :
    • En particulier dans le développement de logiciels cloud native, l’usage de nombreux services tiers avancés est courant.
    • Pour les projets à long terme, ce type de dépendance comporte un risque élevé.
  • Les dépendances à des services au moment du build sont elles aussi importantes :
    • Exemple : si npm install ne fonctionne plus, il devient impossible de builder le logiciel.
    • Cela peut gravement réduire la réutilisabilité du projet.
  • Examiner rigoureusement les dépendances d’exécution :
    • Identifier les risques potentiels de lock-in et réduire ou supprimer ces dépendances.
  • Assurer la maintenabilité à long terme :
    • Anticiper la possibilité de remplacer un cloud ou un service tiers.

Tester, tester, et encore tester

  • La nécessité des tests est un principe de base sur lequel tout le monde s’accorde :
    • Écrire autant de tests que possible.
    • Tous les tests n’ont pas la même valeur, mais on regrette rarement d’avoir testé.
  • Les tests sont particulièrement indispensables dans les projets riches en dépendances :
    • Ils aident à détecter rapidement les problèmes quand des dépendances changent ou dérivent.
  • Rôle des tests
    • Aide à la résolution des problèmes :
      • Permet de s’ajuster rapidement quand des changements surviennent.
    • Soutien au refactoring :
      • Donne de la confiance quand on supprime ou modifie des dépendances dans le code.
    • Utile pour la maintenance à long terme :
      • Même après plus de 3 ans d’arrêt du développement, les tests permettent de vérifier que le système fonctionne toujours.
      • Ils permettent aussi de confirmer le bon fonctionnement avec un nouveau compilateur, runtime ou système d’exploitation.
  • Les tests ne sont pas un coût, mais un investissement
    • Écrire davantage de tests :
      • Les tests sont le socle de la maintenance et de la stabilité.
      • Lorsqu’on modifie ou étend le code, ils apportent un soutien mental considérable.

Complexité : le boss final du développement logiciel

  • La complexité est l’ennemi ultime du développement logiciel :
    • Même les meilleurs développeurs ou équipes peuvent être vaincus par elle.
    • Sous l’effet de l’entropie et des comportements humains, la complexité augmente toujours.
    • Si on ne la gère pas consciemment, un projet peut devenir impossible à maintenir.
  • Corrélation entre complexité et volume de code
    • Volume de code et complexité :
      • Avec peu de code, une certaine complexité reste encore gérable.
      • Plus le code grossit, plus il faut préserver la simplicité pour garder le contrôle.
      • Une complexité gérable doit rester à l’intérieur du « triangle vert » et dans les capacités de l’équipe.
    • Limites de la complexité :
      • Augmenter les effectifs ou recruter des développeurs exceptionnels ne supprime pas les limites de traitement de la complexité.
      • Au-delà d’un certain seuil, le projet devient impossible à maintenir.
  • Pourquoi le code se déplace toujours vers le coin supérieur droit (sur le graphique) :
    • Davantage de demandes de fonctionnalités.
    • Des tentatives d’optimisation inutiles.
    • Lorsqu’on corrige des bugs, on ajoute du code au lieu de réduire la complexité existante.
  • Le coût d’une mauvaise conception d’API :
    • Exemple : la fonction CreateFile ne crée pas de fichier dans la majorité des cas.
    • Ce genre de confusion augmente la charge cognitive et le risque d’erreur.
  • Stratégies de gestion de la complexité
    • Refactorer tôt et souvent :
      • Supprimer le code inutile et consacrer du temps à simplifier.
    • Investir dans les tests :
      • Plus il y a de tests, plus il est facile de réduire la complexité.
    • L’importance de gérer la complexité :
      • Si l’on ne fait pas l’effort en amont de simplifier, un projet de long terme risque de finir dans un état de « maintenance impossible ».

Écrivez du code ennuyeux et simple. Encore plus simple. Et encore plus ennuyeux.

« Déboguer est deux fois plus difficile qu’écrire un programme. Donc si vous écrivez le code aussi intelligemment que possible, comment allez-vous le déboguer ? » - Brian Kernighan

  • Écrire un code extrêmement ennuyeux et clair :
    • Privilégier un code naïf mais intuitivement compréhensible.
    • « L’optimisation prématurée est la racine de tous les maux. »
  • N’optimiser que lorsque c’est réellement nécessaire :
    • Si le code est trop simple au point de poser problème, il n’est pas difficile d’ajouter de la complexité plus tard.
    • Et ce moment n’arrivera peut-être jamais.
  • Éviter d’écrire du code complexe :
    • Attendre le moment où cela devient absolument nécessaire.
    • On regrette très rarement d’avoir écrit un code simple.
  • Un code très performant ou certaines fonctionnalités peuvent ne fonctionner que dans des environnements spécifiques.
    • Exemples :
      • LMDB : PowerDNS a rencontré de nombreuses difficultés avant de l’utiliser de manière stable.
      • RapidJSON : bibliothèque JSON accélérée par SIMD. Très performante, mais exigeante dans ses conditions d’utilisation.
  • Même si l’on pense « je peux surmonter ces contraintes » :
    • C’est peut-être vrai cette année, mais dans 5 ans, vous ou vos successeurs pourriez avoir du mal.
    • Le même principe s’applique aux langages de programmation complexes.
  • Conclusion :
    • Simplifiez le code :
      • Rendez-le vraiment simple. Encore plus simple que ça.
    • Repoussez l’optimisation à plus tard :
      • On peut ajouter de la complexité quand elle devient nécessaire, mais si l’on complexifie trop tôt, la maintenance devient difficile.

Le développement logiciel piloté par LinkedIn

  • Réalité vs idéal
    • Approche idéale : le choix des dépendances doit faire l’objet d’une évaluation et d’une revue rigoureuses (en utilisant la checklist ci-dessus).
    • Approche réaliste : il arrive qu’on essaie une technologie séduisante et qu’on la garde simplement parce qu’elle fonctionne.
  • Pourquoi c’est séduisant
    • Une technologie recommandée par une personnalité connue ou un influenceur sur LinkedIn.
    • Le « framework à la mode » encensé dans des communautés comme Hacker News.
  • Les technologies à la mode manquent de validation dans la durée :
    • Elles peuvent ne pas convenir à un projet logiciel destiné à durer plus de 10 ans.
    • Les nouvelles technologies ont davantage de chances de poser des problèmes de stabilité et de maintenabilité dans leurs premières phases.
  • Recommandations
    • Les réserver aux zones expérimentales :
      • Tester d’abord les nouvelles technologies sur de petits projets ou des zones non critiques.
    • Prendre en compte l’effet Lindy :
      • La durée de vie future d’une technologie tend à être proportionnelle à sa durée d’existence actuelle.
      • Plus une technologie est ancienne, plus on peut espérer une stabilité de long terme.
  • Les nouvelles technologies sont attirantes, mais pour les projets à long terme, des technologies éprouvées et stables sont généralement plus adaptées.

Logging, télémétrie, performances

  • Si le logiciel n’est pas continuellement mis à jour ou déployé :
    • Il est possible qu’on ne reçoive pas de feedback immédiat lorsqu’un site web se casse.
    • Après un déploiement, il peut s’écouler beaucoup de temps avant que les problèmes réels soient résolus.
  • Mettre en place un logging et une télémétrie rigoureux dès la première release :
    • Enregistrer les performances, les échecs et l’activité du logiciel.
    • Les données accumulées au fil du temps sont très utiles pour résoudre des bugs rares.
  • Problèmes liés à un logging insuffisant :
    • Une UI a été déployée, puis un utilisateur ayant créé 3000 dossiers a signalé un problème.
    • L’utilisateur s’est contenté de dire « ça ne marche pas », et il a fallu plusieurs mois pour identifier la cause racine.
    • Avec du logging de performance et de la télémétrie, le problème aurait pu être résolu bien plus vite.
  • Le logging et la télémétrie sont indispensables :
    • Concevoir le logiciel de façon à pouvoir surveiller en détail son activité.
    • Ils sont d’une grande aide pour résoudre des problèmes inattendus lors du déploiement et de la maintenance à long terme.

Documentation

  • Importance de la documentation :
    • Il ne suffit pas de bien rédiger la documentation d’API ; il faut aussi expliquer « pourquoi ce choix de conception ? ».
    • Documenter les idées et la philosophie qui décrivent le fonctionnement du système.
    • Il faut conserver les raisons d’une séparation des solutions et la logique derrière des décisions de conception non intuitives.
  • Ressources utiles au-delà des documents d’architecture :
    • Billets de blog internes : permettent aux développeurs de partager librement des discussions sur la conception du système.
    • Interviews d’équipe : conservent des échanges sur le contexte des décisions de conception.
    • Ces documents permettent de transmettre les connaissances dans l’équipe même avec le temps.
  • Laisser des commentaires dans le code :
    • Malgré la tendance qui affirme qu’« un bon code n’a pas besoin de commentaires », les commentaires qui expliquent le “pourquoi” du code sont essentiels.
    • Il est important d’expliquer pourquoi une fonction donnée existe.
  • Rédiger les messages de commit :
    • Les messages de commit sont au cœur de l’historique du travail. Ils permettent de retracer les raisons d’un changement de code.
    • Il faut offrir un environnement où l’on peut facilement consulter ces messages.
  • Dégager du temps pour la documentation :
    • Les jours où le développement avance mal, consacrer du temps à laisser des commentaires et des traces utiles.
    • Allouer régulièrement, au niveau de l’équipe, du temps à la documentation.
  • Consigner pourquoi le système a été conçu ainsi :
    • Dans 7 ans, ces éléments seront précieux pour transmettre la philosophie et le contexte à une nouvelle équipe.
  • Laisser une histoire à travers les commentaires et les messages de commit :
    • C’est essentiel non seulement pendant le développement, mais aussi pour la maintenance à long terme.

Composition de l’équipe

  • La continuité de l’équipe et le succès à long terme du logiciel :
    • Certains logiciels sont conçus pour être maintenus pendant 80 ans. Dans ce type de projet, la pérennité de l’équipe est essentielle.
    • Dans les environnements de développement modernes, environ 3 ans de présence sont déjà souvent considérés comme une longue ancienneté.
    • Une bonne documentation et de bons tests peuvent partiellement compenser le turnover, mais avec des limites.
  • Avantages d’une longue ancienneté :
    • Conserver les membres de l’équipe pendant plus de 10 ans :
      • Il est important de les embaucher en tant que vrais employés et de bien gérer les développeurs.
      • C’est considéré comme un « hack » clé pour la réussite des projets à long terme.
  • Les problèmes liés à la dépendance à la sous-traitance :
    • Les développeurs externes partent souvent après avoir livré le code au système.
    • Si l’on vise une qualité logicielle durable sur plus de 10 ans, c’est une méthode très inefficace.
  • Créer un environnement dans lequel les membres de l’équipe peuvent rester ensemble sur le long terme.
  • Il faut une stratégie visant à minimiser la dépendance aux consultants externes et à renforcer la durabilité de l’équipe interne.

Envisager l’open source

  • Avantages de l’open source :
    • Maintenir la qualité du code grâce à la revue externe :
      • Le regard extérieur pousse les développeurs à viser des standards plus élevés.
    • Un mécanisme puissant pour maintenir de meilleurs standards de code.
  • La réalité de la préparation à l’open source :
    • Les entreprises ou administrations affirment souvent qu’il faut des mois, voire des années, pour préparer l’ouverture du code.
    • Raisons :
      • En interne, il est courant d’écrire du code qu’on aurait honte de publier à l’extérieur.
      • Un nettoyage est donc nécessaire avant de passer en open source.
  • Évaluer la faisabilité :
    • L’open source n’est pas toujours une option possible.
    • Quand c’est possible, c’est un bon moyen d’améliorer la qualité du code et la transparence.
  • L’open source est une stratégie importante à utiliser quand elle est envisageable.
  • Le regard extérieur et des standards élevés aident à maintenir le projet dans la bonne direction.

Vérifier l’état de santé des dépendances

  • Le problème des changements de dépendances :
    • Les dépendances peuvent changer ou dériver au fil du temps d’une manière différente de ce qu’on attendait.
    • Si on laisse faire :
      • apparition de bugs
      • échecs de build
      • et autres résultats frustrants.
  • Recommander des contrôles réguliers :
    • Vérifications périodiques des dépendances :
      • elles offrent l’occasion de détecter les problèmes en amont.
      • elles permettent aussi de découvrir de nouvelles fonctionnalités qui pourraient simplifier le code ou permettre de supprimer d’autres dépendances.
    • Importance de la maintenance préventive :
      • si vous ne planifiez pas vous-même le temps d’inspection, vous serez finalement forcé de le consacrer quand un problème surviendra.
  • Une métaphore de maintenance :
    • Adage des mécaniciens :
      • « Planifiez vous-même le temps de maintenance. Sinon, c’est l’équipement qui le planifiera à votre place. »
  • Des contrôles réguliers des dépendances sont une activité essentielle pour la stabilité et l’efficacité d’un logiciel sur le long terme.
  • Ils permettent de résoudre les problèmes à l’avance et de découvrir des évolutions positives.

Ouvrages de référence majeurs

Pour finir

Recommandations essentielles pour le développement logiciel à long terme :

  • Préserver la simplicité :
    • Faire simple, encore plus simple ! Puisqu’on peut ajouter de la complexité quand c’est nécessaire, il ne faut pas complexifier excessivement dès le départ.
    • Préserver la simplicité demande des refactorings réguliers et la suppression de code.
  • Réfléchir soigneusement aux dépendances :
    • Moins il y a de dépendances, mieux c’est. Il faut les examiner attentivement et les auditer.
    • Si vous ne pouvez pas auditer 1600 dépendances, il faut revoir le plan.
    • Éviter les choix dictés par les tendances ou la mode (par exemple le développement piloté par LinkedIn).
    • Contrôles réguliers des dépendances : surveiller en continu leur état.
  • Tester, tester, et encore tester :
    • Pour identifier à temps les dépendances qui évoluent.
    • Pour donner confiance lors des refactorings et aider à préserver la simplicité.
  • Documentation :
    • Documenter non seulement le code, mais aussi la philosophie, les idées et le contexte du « pourquoi nous avons fait ce choix ».
    • C’est un atout précieux pour les futures équipes.
  • Maintenir une équipe stable :
    • Pour les projets de long terme, envisager des embauches sur la durée.
    • Aider les membres de l’équipe à s’engager sur le projet pendant longtemps.
  • Envisager l’open source :
    • Quand c’est possible, l’open source aide à maintenir un niveau de qualité du code plus élevé.
  • Logs et télémétrie de performance :
    • Ils jouent un rôle important pour détecter et résoudre rapidement les problèmes.
  • Ces recommandations ne sont peut-être pas nouvelles, mais puisqu’elles sont soulignées par des développeurs expérimentés, elles méritent une réflexion approfondie.

4 commentaires

 
kandk 2024-12-30

La capacité d’ingénierie la plus importante consiste à séparer les couches où la stabilité est essentielle de celles où la vitesse est essentielle, puis à décider comment gérer la relation entre les deux.
Si Toss n’avait recherché que la stabilité, elle ne serait pas différente des autres banques.

 
kandk 2024-12-30

C’est risqué, SpaceX aussi est comme ça. Tesla aussi...

 
aer0700 2024-12-25

Le développement piloté par le CV est-il un problème ?

 
GN⁺ 2024-12-23
Avis Hacker News
  • Mettre à jour activement la chaîne d’outils est une partie importante du processus de développement. Beaucoup d’entreprises relèguent les mises à niveau de la toolchain hors de leurs priorités, ce qui entraîne des problèmes comme des vulnérabilités de sécurité. Une branche est créée à chaque nouvelle version du compilateur ou du système de build pour vérifier l’état de la compilation, et s’il y a des erreurs, elles sont traitées immédiatement comme des bugs. Cela aide à moderniser et refactoriser progressivement la base de code avec les fonctionnalités récentes du langage.

  • Les dépendances tierces sont souvent décevantes à long terme. Dans un nouveau projet, elles peuvent résoudre des problèmes à court terme, mais sur la durée, il vaut mieux les remplacer par son propre code.

  • Il est nécessaire de vendoriser les dépendances et de les gérer via des revues de code. La qualité du code tiers est souvent faible, au point qu’il vaut parfois mieux l’écrire soi-même.

  • Un projet est en cours avec Qt, CMake et le C++ moderne, avec pour objectif une extensibilité sur le long terme. Cette stack technologique continue d’apporter des fonctionnalités et des améliorations.

  • Travailler en Emacs Lisp a été une expérience rafraîchissante. Le fait que les bibliothèques continuent de fonctionner de manière stable même sans mises à jour est un avantage. À l’inverse, l’expérience avec Gatsby et Node a été difficile à cause des problèmes liés aux mises à jour.

  • Il est important d’écrire du code simple. Le code complexe ne devrait être écrit que lorsqu’il est vraiment nécessaire, alors qu’on ne regrette jamais d’avoir écrit du code simple.

  • La documentation des systèmes et du code est importante. Plus on a d’expérience en développement logiciel, plus on prend conscience de l’importance de la documentation.

  • Les tests jouent un rôle important dans la planification. Il faut s’inspirer des méthodes de développement de la NASA et se concentrer sur la recherche d’erreurs de programmation. Dans le développement de logiciels médicaux, on évite toute interprétation et on n’utilise pas d’allocation dynamique de mémoire.

  • La meilleure manière d’écrire un logiciel durable est d’écrire du code « ennuyeux ». Il faut éviter les dépendances et rester fidèle aux fondamentaux.

  • Il y a eu des difficultés en Python à cause des problèmes de dépendances. On appelle cela le « DLL Hell », que COM a essayé de résoudre sans vraiment y parvenir.

  • Les pratiques appliquées au logiciel industriel ne sont pas assez robustes pour être transposées telles quelles au logiciel généraliste. Les ingénieurs cherchent à atténuer les risques, et nous nous concentrons nous aussi sur cette réduction des risques.