Logiciel à sang froid
(dubroy.com)- Certains projets logiciels sont à sang chaud : ils restent maintenus seulement s’il y a une activité de développement continue. S’ils peuvent reprendre même après une interruption, ils se rapprochent plutôt du modèle à sang froid
- Les projets à sang froid choisissent des technologies ennuyeuses pour éviter que la compilation et les tests ne cassent même après une longue pause, et évitent les dépendances à des services externes susceptibles de changer ou de disparaître
- Le rachat ou la fermeture de services dépendants, les mises à niveau de compilateur et l’arrêt du support de paquets se transforment en coûts de maintenance lorsqu’on relance un projet peu actif
- Pour du code auquel on ne touche pas pendant un, deux ou trois ans, comme dans un projet personnel, il est difficile de créer une chaleur continue ; il faut donc le concevoir dès le départ en partant d’un faible taux de changement
- Un générateur de site statique pour blog continue de fonctionner presque sans modification depuis son premier commit en 2012, avec Python 2, quatre modules tiers inclus dans le dépôt, une exécution locale et un déploiement via
rsyncoverssh
Maintenir un projet vu à travers la métaphore des animaux à sang froid
- Lors d’un cours d’histoire naturelle en 2004, le professeur a placé sous la caméra un jeune painted turtle sorti du congélateur pour faire son cours
- Les jeunes painted turtles faisaient partie des rares espèces capables de survivre même à l’état gelé
- Pendant une heure, la tortue est passée de mouvements presque imperceptibles à un déplacement couvrant environ la moitié de l’écran à la fin
- Les animaux à sang chaud doivent maintenir leur température corporelle dans une plage étroite ; chez l’humain, les problèmes apparaissent lorsqu’on s’éloigne d’environ 37 °C
- Les animaux à sang froid ajustent leur métabolisme à la température ambiante : ils sont actifs quand il fait chaud et se déplacent de plus en plus lentement à mesure que leur corps et l’environnement refroidissent
- Les projets logiciels peuvent se répartir de façon similaire
- Un logiciel à sang chaud fonctionne bien lorsqu’il y a un mouvement et une chaleur continus autour du projet
- Si on le laisse à l’arrêt pendant six mois, il peut ressembler à un projet mort lorsqu’on le ressort
Conditions et exemples de logiciels à sang froid
- Les projets à sang chaud deviennent difficiles à relancer parce que les changements externes s’accumulent
- Un service dont dépend la CI peut être racheté ou cesser de fonctionner faute de financement
- L’ajout d’une nouvelle dépendance peut imposer une mise à niveau du compilateur
- Un autre paquet peut ne plus être maintenu et ne plus fonctionner avec le compilateur récent
- Un projet sur lequel on travaille seul, que l’on modifie seulement lorsqu’on a de l’inspiration et auquel on ne retouche pas pendant plus d’un an, est difficile à exploiter comme un projet à sang chaud
- Un projet à sang froid doit pouvoir redémarrer un an plus tard exactement depuis son point d’arrêt, comme un jeune painted turtle gelé
- Pour cela, il utilise de la boring technology et évite que les scripts de build et de test ne dépendent de services externes pouvant changer, casser ou disparaître complètement
- Les dépendances sont incluses dans le dépôt du projet, selon une approche de type vendored dependencies
- Le logiciel qui fait fonctionner ce blog est un exemple de projet à sang froid
- Le premier commit date du 8 janvier 2012 ; il s’agissait d’un petit générateur de site statique destiné à remplacer une ancienne installation Wordpress
- Il est écrit en Python 2, dépend de quatre modules tiers, tous committés dans le dépôt du projet
- Tout s’exécute en local, et le résultat est déployé via
rsyncoverssh - À part quelques petites améliorations, il a continué de fonctionner sans modification, et l’auteur s’attend à ce qu’il fonctionne encore dans 12 ans
1 commentaires
Avis de Hacker News
Express, le framework web de l’écosystème Node et JavaScript, est toujours maintenu en version majeure 4.x.x depuis plus de 10 ans https://www.npmjs.com/package/express?activeTab=versions
Il reste pourtant très utilisé, avec plus de 17 millions de téléchargements par semaine https://www.npmjs.com/package/express, et même s’il manque de fonctionnalités ou n’offre pas les meilleures performances https://fastify.dev/benchmarks/, il a l’avantage de permettre un développement rapide et stable ainsi qu’une planification à long terme
Il n’y a pas à s’inquiéter de l’arrêt des correctifs de sécurité pour d’anciennes versions ni de changements d’API brutaux, et Go est encore plus stable grâce à sa vaste bibliothèque standard et à sa promesse de compatibilité, qui permettent encore d’exécuter des programmes vieux de plus de 10 ans https://go.dev/doc/go1compat
À ses débuts, il était souvent traité de déchet pour pseudo-programmeurs incompétents simplement parce qu’il était écrit en JavaScript, mais depuis, il a aidé plusieurs entreprises à créer avec Express de vrais services formidables qui rapportent de l’argent, et il doit aujourd’hui traiter un volume énorme de requêtes
De nos jours, j’écris aussi beaucoup en Go, mais je reste pleinement satisfait de construire des services avec Express et, dans l’ensemble, je le considère comme un bon logiciel
Ce n’est pas que je le déteste vraiment, mais je pense que je ne le choisirais pas à cause du cycle sans fin des mises à niveau de versions
Python est un très mauvais exemple de logiciel « à sang froid »
Il y a sans cesse des changements cassants, à la fois dans le runtime et dans les outils, et l’auteur se retrouve déjà dans une situation où il doit continuer à utiliser Python 2, dont le support a pris fin depuis longtemps
De meilleurs exemples seraient des langages comme Go ou Java, où du code vieux de 10 ans fonctionne encore bien avec les outils modernes, voire, plus extrême encore, Perl, où du code vieux de 30 ans fonctionne toujours correctement
Quand on crée un logiciel, on finit par faire des erreurs qui permettent aux utilisateurs de faire quelque chose d’une manière non prévue ; dans le monde Java, on règle cela en ajoutant des fonctionnalités plus récentes, plus sûres et plus explicites, puis en encourageant les utilisateurs à migrer
Python fait quelque chose de similaire, mais en y ajoutant « et bientôt, on désactivera l’ancienne fonctionnalité », ce que Java ne fait pas
Par exemple, la méthode
equalsdejava.net.URLest connue pour être mal conçue et son usage est fortement déconseillé, mais elle reste prise en charge depuis plus de 20 ansDans Python Airflow, l’opérateur vide a pris en charge pendant un temps le nom
DummyOperator, mais les mainteneurs l’ont remplacé parEmptyOperatorau motif que « dummy » avait historiquement et culturellement été utilisé comme terme péjoratif, cassant ainsi l’ancien nomAprès mise à niveau, le code échouait au chargement tant que les références n’étaient pas renommées, et personnellement, je ne pense pas que je casserais les utilisateurs de cette manière
Dans le monde Java, un simple changement de nom peut se gérer par une substitution de texte, donc on aurait probablement continué à le prendre en charge jusqu’à ce qu’il y ait une raison de ne plus le faire
C’est pourquoi, globalement, je pense que Java et les dépendances de bibliothèques Java peuvent être mis à niveau bien plus librement que Python
Si l’on utilise une version Java LTS et que l’on choisit de bonnes dépendances, on peut faire en sorte que le projet se relance à tout moment
Avec Python, dans un cours de machine learning, une dépendance avait introduit du jour au lendemain un changement d’API cassant, et l’enseignant ne s’en était pas aperçu parce qu’il utilisait simplement la version la plus récente au moment où il avait commencé à préparer le cours, quelques semaines plus tôt
Le passage de Python 2 à 3 était un changement cassant, mais c’était une transition ponctuelle, pas une série de « changements cassants continus »
Si l’on reste sur la même version majeure, les nouvelles versions mineures ne cassent pas l’ancien code ; par exemple, du vieux code 2.x tourne bien sur 2.7, et du vieux code 3.x tourne bien sur 3.12
Une version mineure peut ajouter de nouvelles fonctionnalités, mais le fait qu’un ancien code 3.x n’utilise pas le mot-clé
asyncou les annotations de type ne le casse pasC’est l’une des raisons pour lesquelles j’évite Python quand je le peux
J’ai l’impression que la probabilité que du code Python écrit aujourd’hui fonctionne encore dans quelques années est faible, et je considère que c’est un problème assez important
Même en essayant d’exécuter du code Java vieux de 3 ans avec un nouveau SDK, il y avait toujours quelque chose de cassé
Je travaille sur des mainframes IBM (z/OS), et en matière de maintien de la rétrocompatibilité, je n’ai presque rien vu qui se rapproche d’IBM
Je dirais que Microsoft Windows arrive en deuxième, puis l’ABI du noyau Linux en troisième environ, mais à l’échelle de tout l’écosystème Linux, ce n’est qu’une petite partie
La plupart du reste ressemble plutôt à du churn, et dans l’open source, il semble rare que des gens aient envie de consacrer leur temps libre à la rétrocompatibilité
Économiquement, cela ressemble au dilemme du prisonnier : tout le monde reporte sur les autres le coût du maintien de la compatibilité, ce qui finit par créer davantage de travail inutile pour tout le monde
Par exemple, dans les communautés de rétro-informatique, il n’est pas rare d’écrire des pilotes pour faire fonctionner du matériel récent sur d’anciens systèmes d’exploitation
Sans rémunération, tout dépend au final de l’attachement que l’on porte à la plateforme que l’on crée ; pour ma part, j’ai choisi de cibler directement le noyau Linux via les appels système, en raison de son engagement éprouvé envers la stabilité de l’ABI
À l’inverse, pour le langage de programmation que j’ai créé moi-même, j’ai envie de continuer à le corriger parce que je veux le rendre aussi « parfait » que possible
Au cas où quelqu’un voudrait s’en servir de façon déraisonnable, j’ai mis dans le README un avertissement indiquant qu’il est encore en phase initiale de développement et instable
J’imagine que les personnes qui créent Ruby ou Python ressentent quelque chose de similaire : un langage peut ressembler à un enfant dont on souhaite la réussite, et l’on peut donc considérer qu’il faut corriger des erreurs comme le fait que
printait été un mot-cléIl arrive même souvent que ce soient les parties prévues pour la rétrocompatibilité qui cassent
Dans mon précédent poste, nous développions des apps Node conteneurisées, et la CI construisait les images à partir des sources Node ; les déploiements de services auxquels nous n’avions pas touché depuis un moment se sont soudain mis à échouer
Il s’est avéré que le Dockerfile était basé sur une image Ubuntu dont la période de support était terminée, et que les dépôts de mise à jour avaient été déplacés vers des archives ; sans corriger le Dockerfile, il était impossible de construire l’image
C’est un exemple de logiciel auquel on ne touche pas mais qui casse quand même, et c’est pourquoi je préfère Go et les binaires autonomes
Une fois empaqueté en release, il n’y a même plus besoin de le reconstruire, et dans une image Docker Distroless, il n’y a aucune dépendance en dehors de mon binaire
J’utilise Go depuis longtemps, mais je n’ai jamais rencontré ce problème de logiciel qui se dégrade avec l’âge ; plusieurs types de problèmes que je ressentais avec Node ou PHP ont disparu
Côté Node, le deuxième plus gros problème vient des schémas d’indirection des frameworks, et le premier de la gestion des paquets
Les problèmes de peer dependencies du type « vous avez installé la version X, mais le module Y a besoin de la version Z » reviennent sans cesse
Beaucoup d’ingénieurs, lorsqu’ils cherchent une bibliothèque sur GitHub, regardent la date du dernier commit
Ils ont tendance à penser que plus le commit est récent, mieux la bibliothèque est prise en charge
Mais un projet archivé qui fait exactement ce dont on a besoin, qui a 0 bug et qui est stable depuis des années, c’est comme trouver une perle rare dans une friperie
De nos jours, beaucoup d’ingénieurs écartent automatiquement les bibliothèques qui ne sont pas mises à jour « en continu », et semblent considérer que c’est une bonne chose
Les environnements modernes de développement logiciel ne le sont souvent pas, et le frontend web en est un exemple emblématique, car il change fréquemment
Si une bibliothèque est totalement indépendante, l’absence de mises à jour peut être acceptable ; mais une bibliothèque qui dépend d’un framework frontend web posera problème si elle n’est pas mise à jour pour suivre l’évolution de l’écosystème
Je ne connais pas les chiffres exacts, mais dans l’immense majorité des cas, l’absence d’activité récente signifie « abandonné » plutôt que « terminé et sans bug »
Certains langages sont devenus presque méconnaissables par rapport à leur version 1.0, tandis que d’autres ont conservé l’essentiel du code écrit et se sont contentés d’ajouter des couches par-dessus
Au final, cette tendance semble aussi se refléter dans les communautés et les écosystèmes
Je me souviens que Clojure figurait dans le haut du classement parce qu’il introduit très peu de changements cassants, et une bibliothèque dont le dernier changement remonte à cinq ans fonctionne encore parfaitement avec la version actuelle du langage
Le fait que ce soit de la famille Lisp, et que l’on puisse étendre le cœur du langage sans changement upstream, aide probablement aussi, même si cela a évidemment ses propres défauts
Mais j’ai apprécié que cela m’aide à cesser d’associer « fraîcheur » et « qualité »
Aujourd’hui, j’utilise plus souvent des bibliothèques qui ont très peu changé depuis plusieurs années que des bibliothèques créées l’an dernier, sans rencontrer de gros problèmes
Certains langages publient une version tous les un ou deux ans et ajoutent régulièrement une nouvelle syntaxe plus élégante, ou des types de données abstraits dans la bibliothèque standard, pour remplacer des patterns très utilisés mais maladroits
La communauté de ce langage considère presque immédiatement la nouvelle syntaxe comme « idiomatique » et estime que le code écrit avec les anciennes méthodes lourdes doit être corrigé
La raison invoquée pour modifier une base de code donnée est généralement que l’ancienne approche est plus opaque que la nouvelle syntaxe, et rend la maintenance comme les revues de code plus difficiles
L’argument est que, si la nouvelle syntaxe avait existé dès le départ, personne n’aurait considéré l’ancienne manière de faire comme du bon code ; il faudrait donc mettre le code à jour pour améliorer la lisibilité pour les nouveaux développeurs et abaisser la barrière d’entrée aux contributions
Pour une bibliothèque implémentée dans ce type de langage, ne pas avoir été mise à jour depuis plus de 3 ans est souvent un mauvais signal
Cela peut indiquer que le développeur n’est pas suffisamment connecté à la communauté pour maintenir le code dans un style idiomatique, lisible facilement par d’autres développeurs ayant appris la forme récente du langage, et qu’il n’a peut-être pas non plus envie d’accepter des PR externes
Le projet peut être marqué comme abandonné et donc personne ne signale quoi que ce soit, alors qu’il peut quand même contenir des vulnérabilités de sécurité
Les seuls logiciels capables de vivre sans mises à jour sont ceux qui ont été bien conçus dès le départ.
Pour un logiciel destiné uniquement à soi-même, c’est relativement facile : il y a de fortes chances que vos goûts ne changent pas radicalement même après 10 ans, et comme
nest petit, on peut ignorer les petits problèmes où une fonction enO(n^2)suffit même s’il existe une fonction enO(n).Mais pour un logiciel destiné à d’autres, les exigences diffèrent, et des problèmes apparaissent : pour un
Nsuffisamment grand, une fonction enO(n)finit par avoir de la valeur.Que l’on écrive pour soi ou pour les autres, des problèmes imprévus peuvent surgir.
Par exemple, traiter un fichier de plus de 1 Go peut faire planter le programme, mais comme on n’utilisait d’ordinaire que des fichiers de moins de 100 Ko, on ne s’en était pas soucié ; puis, au moment de corriger, on peut découvrir qu’il faut en réécrire la moitié.
C’est là que se trouve la principale objection à l’idée selon laquelle un logiciel immuable serait intrinsèquement meilleur qu’un logiciel qui change souvent.
Un logiciel qui ne change pas a peut-être été parfait dès le départ, mais il peut aussi cacher des horreurs en profondeur, et il est difficile de les distinguer à l’avance.
Cela ne veut pas dire non plus qu’un logiciel mis à jour rapidement serait intrinsèquement meilleur qu’un logiciel mis à jour lentement ; il y a beaucoup d’autres facteurs que la cadence des mises à jour.
Si les exigences changent, le logiciel doit évidemment changer aussi.
Mais en 10 ans, beaucoup de choses peuvent se produire sans rapport avec l’évolution des exigences.
Un projet open source peut être abandonné ou changer de direction, un logiciel commercial peut être arrêté, une entreprise peut être rachetée, les règles de l’App Store ou du Play Store peuvent changer, une API peut disparaître ou ses tarifs évoluer au point de ruiner l’équilibre économique du projet.
Les chaînes d’outils, frameworks, langages de programmation, paradigmes et bonnes pratiques changent aussi.
À mon avis, l’essentiel est d’éviter que des changements externes sans rapport avec les exigences ne me forcent à changer.
C’est un bon principe, mais comme toujours, il y a des compromis.
La stabilité et l’obsolescence sont deux choses différentes, et la frontière se joue souvent sur la sécurité.
Que faire si satisfaire une nouvelle exigence importante est facile, mais nécessite de faire monter une bibliothèque vendored de sept versions majeures, ce qui provoque au passage une foule de casses sans rapport ?
Que faire si les personnes familières avec un ensemble d’outils figé dans le temps ne sont plus assez nombreuses, et que personne ne veut l’apprendre ?
Choisir ses dépendances avec prudence et conservatisme est une bonne chose, mais refuser de suivre même l’évolution limitée de ces dépendances ainsi réduites me semble aller un pas trop loin.
Je partage le sentiment du texte.
Je déteste vraiment le fait qu’une application mobile créée il y a seulement quelques années exige désormais des dizaines d’heures pour être corrigée et soumise en mise à jour.
La fin, où l’auteur qualifie son générateur de site statique de logiciel à sang froid en précisant qu’il tourne sous Python 2, est aussi intéressante.
Python 2 devient aujourd’hui de plus en plus difficile à installer, et ce projet finira lui aussi par devenir un projet à sang chaud.
À chaque mise à niveau de Xcode, il faut corriger tout un tas de petites choses pour que le projet compile proprement et fonctionne ; c’est vraiment exaspérant et cela devrait être totalement inacceptable.
Les messages récents de l’historique git sont tous des variantes de « correction pour fonctionner avec le dernier Xcode ».
Si ces changements des anciens SDK ou OS étaient nécessaires à cause de menaces de sécurité, je pourrais le comprendre dans une certaine mesure, mais ce n’est presque jamais le cas.
La plupart du temps, ce sont des changements stupides du genre déprécier des API, ajouter des avertissements par défaut, ou décider qu’il faut désormais utiliser tel framework plutôt que tel autre.
Les plateformes et les frameworks doivent arrêter d’être délibérément des cibles mouvantes, surtout lorsqu’il s’agit désormais de systèmes d’exploitation très stables et fiables.
On devrait pouvoir sortir un projet vieux de 10 ans du congélateur et le voir compiler et s’exécuter aussi proprement qu’il y a 10 ans.
Ces fournisseurs d’OS sont des entreprises valorisées en milliers de milliards ; je ne veux pas entendre l’excuse selon laquelle la rétrocompatibilité demanderait beaucoup d’efforts d’ingénierie.
Je continue de maintenir un projet personnel parallèle.
Je l’ai commencé il y a 12 ou 13 ans en PHP pur, puis je l’ai réécrit plus tard avec Laravel, avant de le réécrire encore vers 2017 avec Symfony.
Il y a eu des périodes de 6 à 18 mois où, faute d’énergie à cause de mon travail freelance à plein temps, je n’ai fait que deux ou trois tout petits commits ; mais quand j’avais du temps, j’ajoutais des fonctionnalités, je faisais des mises à niveau, j’expérimentais et j’apprenais.
Cela m’a été très utile pour apprendre à maintenir un projet sur le long terme.
J’ai appris à mettre à jour les dépendances, supprimer l’inutile, vérifier les mises à jour de sécurité, chercher des occasions de simplifier (passer de Vagrant à Docker, de Vue + Axios + Webpack, etc. à Htmx), et aussi ce qu’il fallait éviter.
Personnellement, j’en suis venu à éviter les dépendances tout juste sorties, les microservices et les infrastructures complexes comme Kubernetes.
Récemment, j’ai développé plusieurs fonctionnalités, migré vers PHP 8.2 et Symfony 7, et intégré aussi des fonctionnalités basées sur ChatGPT ; donc si je le veux, je pense pouvoir faire une pause d’un à trois ans.
Sur les 4 ou 5 dernières années, ce projet a généré un chiffre d’affaires proche du revenu annuel moyen d’un freelance, ce n’est donc pas non plus un side project obscur et endormi.
Après ne pas l’avoir utilisé pendant quelques années, j’y suis revenu et j’ai retrouvé exactement les mêmes horribles fonctions de manipulation d’images qu’au moment où j’étais parti huit ans plus tôt.
Au-delà de ce que dit l’article, un modèle de menace intrinsèquement sûr est important.
Par exemple, un site web complet doit constamment faire face aux attaquants et aux spambots, il est donc par nature plutôt à sang chaud.
À l’inverse, une page statique comme TiddlyWiki peut ne pas être mise en ligne du tout, et le navigateur est une plateforme extrêmement stable, ce qui est bien mieux.
La différence de préférence entre les projets « à sang froid » et les projets « à sang chaud » semble liée au Buxton Index mentionné dans https://www.cs.utexas.edu/users/EWD/transcriptions/EWD11xx/EWD1175.html
Une petite épicerie de quartier est à environ 0,5 an, un véritable chrétien à l’infini, un politicien moyen visant sa réélection à environ 4 ans, la plupart des industries un peu plus longtemps, et les managers qui doivent produire des rapports trimestriels beaucoup moins
Le Buxton Index est important parce qu’une coopération étroite entre des acteurs ayant des Buxton Index très différents échoue nécessairement et mène à des reproches moraux
Le côté au court horizon est accusé d’être superficiel et myope, tandis que le côté au long horizon est accusé de négligence professionnelle, de fuite des responsabilités ou de parasitisme
Ils finissent aussi par se considérer mutuellement comme stupides
L’avantage du Buxton Index est qu’il s’agit d’un simple concept numérique, donc moralement neutre, qui permet de placer les différences au-dessus du débat moral
C’est particulièrement important quand on pense à la coopération entre le monde académique et l’industrie
Ce nom est vraiment mauvais
Les animaux à sang froid dépendent fortement de leur environnement, tandis que les animaux à sang chaud réduisent leur dépendance à la température extérieure grâce à leur métabolisme
Dans tous les cas, c’est inutilement ambigu
Il suffirait de dire « logiciel sans dépendances externes » pour supprimer tout un paragraphe d’explication verbeuse
Je n’aime déjà pas les articles de développement logiciel qui tirent des conclusions superficielles à partir de métaphores naturelles inappropriées, mais j’aime encore moins ceux qui le font en comprenant complètement de travers le phénomène naturel servant de métaphore
Si certaines espèces, dont la tortue peinte, survivent au gel, ce n’est pas parce qu’elles sont à sang froid, mais grâce à des protéines antigel particulières
D’autres lézards ou animaux à sang froid verraient leurs propres tissus se déchirer lors de la décongélation
https://lobste.rs/s/hitos3/cold_blooded_software#c_mxjzwh