2 points par GN⁺ 2025-01-26 | 1 commentaires | Partager sur WhatsApp
  • Une culture de développement où l’installation de packages est devenue le comportement par défaut accroît le dependency churn — mises à jour, correctifs, audits et gestion des dépendances transitives — créant un coût caché pour la productivité
  • L’impact est d’autant plus fort dans les écosystèmes disposant d’un packaging bien établi, comme JavaScript et Rust ; on peut citer un nouveau projet Tokio avec 28 crates, Rocket avec 172 crates, ou la CLI de MiniJinja avec 142 dépendances
  • Même pour une fonctionnalité ancienne et stable comme terminal_size, un paradoxe apparaît : les évolutions des bibliothèques d’abstraction de plateforme entraînent l’ajout de crates et des releases répétées
  • Pour de petites fonctionnalités, créer une implémentation sans dépendance avec ChatGPT ou Cursor peut être plus rapide que de chercher des dépendances puis de les mettre à jour en continu, tout en réduisant la charge de compilation du code externe
  • Les bibliothèques pour les domaines difficiles comme HTTP, QUIC, le graphisme ou tokio restent nécessaires, mais accepter un grand graphe de dépendances pour une seule fonction devrait être davantage remis en question

Le treadmill de maintenance créé par l’augmentation des dépendances

  • Les développeurs installent facilement des packages au nom de la productivité, mais se retrouvent alors dans un dependency churn sans fin : mises à jour, correctifs, audits et gestion des dépendances transitives
  • Plus un écosystème dispose de solutions de packaging bien établies, plus ce problème est visible ; JavaScript et Rust sont particulièrement concernés
  • Exemples en Rust :
    • Un nouveau projet Tokio apporte 28 crates
    • Un nouveau projet Rocket monte jusqu’à 172 crates
    • MiniJinja lui-même peut exister avec une seule dépendance, mais sa variante CLI apporte 142 dépendances
  • Le problème central est une structure qui oblige à compiler et gérer beaucoup plus de code externe que ce qui est réellement nécessaire pour une petite fonctionnalité

Le paradoxe du code stable illustré par terminal_size

  • terminal_size est, comme son nom l’indique, une crate qui récupère la taille du terminal
  • Les API sous-jacentes utilisées par cette fonctionnalité sont en pratique stables depuis les débuts des terminaux informatiques, mais selon l’OS, elle entraîne 3 à 4 crates supplémentaires
  • On se retrouve à compiler des milliers d’autres fonctions simplement pour savoir si un terminal fait 80x25 ou 120x40
  • Cette crate a connu 26 releases, alors qu’une implémentation maison intégrée à un projet il y a dix ans pour la même fonctionnalité fonctionne toujours sans mise à jour
  • Ces nombreuses releases ne s’expliquent pas par un changement de la fonctionnalité elle-même, mais par l’évolution continue des bibliothèques d’abstraction de plateforme sous-jacentes
  • Sous UNIX, il existe une exception : une dépendance à libc est nécessaire
    • Rust n’expose pas les constantes libc de la plateforme, et ces constantes ne sont pas standardisées
    • Cela dit, libc est une dépendance courante et légère, difficile à éviter

La sécurité et la culture de la réutilisation de code renforcent les dépendances

  • L’approche de type « big supply chain » se méfie du fait de copier des fonctions ou d’écrire directement du unsafe, et pousse à déléguer à des couches d’abstraction de plateforme
  • Il existe aussi des entreprises qui fournissent des outils pour gérer le problème des dépendances et qui, au nom de la sécurité, incitent à conserver les dépendances et à les mettre à jour
  • Pourtant, l’abondance de dépendances peut elle-même devenir une source majeure de problèmes de sécurité
  • L’objectif du code devrait être d’atteindre, à un moment donné, un état de stabilité où aucune mise à jour n’est nécessaire
  • Dans l’écosystème Rust, même une dépendance qui fonctionne de manière stable peut être mal notée par RUSTSEC si son bug tracker est relativement inactif
  • La culture de la revue de code en entreprise influence aussi l’open source
    • Un ingénieur qui apporte une bibliothèque nouvelle et séduisante a plus de chances d’être récompensé que réprimandé
    • C’est ainsi que des outils comme Dependabot sont apparus, et que les projets reçoivent continuellement des PR de mise à jour de dépendances
    • En interne, dans les entreprises, le vendoring, les audits internes et les mises à niveau à l’échelle de l’organisation peuvent à eux seuls maintenir des équipes d’ingénierie constamment occupées

Les petites fonctionnalités peuvent être faites soi-même

  • La voie la plus simple consiste à écrire soi-même le code nécessaire
  • Le travail initial peut être plus important, mais une fois le code écrit, il ne nécessite pas de nouvelle crate et il n’est pas nécessaire d’attendre que l’auteur upstream corrige un edge case
  • Si le code casse dans son cas d’usage, on peut le corriger soi-même ; un code qui fonctionne n’a pas forcément besoin de monter sur le treadmill de maintenance
  • En 2025, ChatGPT ou Cursor peuvent produire rapidement une implémentation sans dépendance pour de petites fonctionnalités courantes
  • Beaucoup de petites fonctions ont un faible surcoût de maintenance, parfois inférieur à celui des mises à niveau continues de dépendances
  • Pour quelques lignes de code, il n’est pas nécessaire de compiler des milliers de lignes écrites par d’autres pour une seule fonctionnalité

Mieux valoriser les faibles dépendances

  • Toutes les dépendances ne sont pas mauvaises
    • Les bibliothèques graphiques qui abstraient des pilotes complexes
    • Les implémentations de protocoles comme HTTP et QUIC
    • Les bibliothèques importantes comme tokio, qu’on ne peut pas retirer et qu’on ne souhaite pas retirer
  • Mais si l’on compile des centaines de fonctions pour n’en utiliser qu’une, cela doit être vu comme un signal d’alerte
  • Il faut davantage reconnaître le choix d’écrire soi-même une petite fonction pour éviter un graphe de dépendances transitives
  • Les grands graphes de crates devraient susciter plus de méfiance, tandis qu’un code simple et stable, qui peut rester intact pendant des années, devrait être perçu positivement
  • sha1-smol, initialement nommé sha1, était devenu la crate standard pour calculer des hachages SHA1, mais il a ensuite subi des pressions pour céder ce nom à rust-crypto et s’aligner sur un écosystème crypto plus vaste
    • Utiliser la nouvelle crate sha1 entraîne 10 dépendances
    • C’était difficile à éviter, car le nom dans le registry était important et il existait des exigences de compatibilité avec les traits
  • MiniJinja met en avant ses faibles dépendances dans son README
$ cargo tree
minimal v0.1.0 (examples/minimal)
└── minijinja v2.6.0 (minijinja)
    └── serde v1.0.144
  • MiniJinja dispose d’une PR visant à supprimer sa dernière dépendance
  • Dans les situations appropriées, il faudrait célébrer le fait de faire soi-même et accorder davantage de reconnaissance aux auteurs qui créent des bibliothèques open source avec peu ou pas de dépendances

1 commentaires

 
GN⁺ 2025-01-26
Avis sur Hacker News
  • J’aime le langage Rust lui-même, mais je n’aime pas l’écosystème des dépendances Rust. On se plaint souvent qu’il est difficile d’ajouter des dépendances en C++, mais, au fond, cela ressemble plutôt à une fonctionnalité. Parce que cela oblige à se demander si c’est vraiment nécessaire
    En C++, je contrôle mes dépendances, tandis qu’en Rust on dépasse vite la centaine et on finit par abandonner. Du point de vue de la sécurité, honnêtement, je ne sais pas ce que je déploie
    De plus, Rust n’a pas non plus de compatibilité ABI, ni de culture des bibliothèques partagées. J’ai donc l’impression que cela casse le modèle de distribution des paquets des systèmes d’exploitation. Quand on choisit une distribution Linux, on fait confiance aux personnes qui construisent cette distribution ; Rust me semble plus proche de Python, où n’importe qui peut publier quelque chose sur PyPI

    • Debian/Ubuntu peut être considéré comme sûr parce que les mainteneurs signent les paquets et que ces mainteneurs sont vérifiés. En fin de compte, le modèle repose sur la confiance dans le fait que Debian/Ubuntu n’autorise la signature de paquets qu’à des personnes dignes de confiance
      Et Docker/Python/Rust ? Je ne connais absolument pas les personnes qui ont créé mon image Docker, mon paquet PyPI ou mon crate Rust
      En pratique, on est revenus à l’époque où l’on s’échangeait des EXE et des DLL dans des ZIP. Sauf qu’aujourd’hui on appelle ça des conteneurs, et on les exécute fièrement avec les droits root
      Comme le dit l’auteur, parfois le mieux est simplement d’intégrer le code source des dépendances. Autrefois, on appelait cela le vendoring, et c’était assez important dans Rails/Ruby. Cela présente le gros avantage de ne pas être affecté plus tard par la prise de contrôle malveillante d’un paquet, et, si on le souhaite, on peut fusionner soi-même les correctifs de sécurité upstream
    • Je me demande vraiment dans quel domaine travaillent les personnes qui ont ce point de vue, pour pouvoir réinventer la roue à chaque projet au seul motif d’avoir peur de risques hypothétiques. La plupart de ces risques restent les mêmes avec une réimplémentation interne, et les bugs aussi
      Je ne vois pas de projet où le syndrome NIH ait eu un effet net positif. Même des dépendances non indispensables font gagner énormément de temps
      Si l’on réécrit à chaque projet une partie des dépendances optionnelles, je me demande où l’on trouve le temps de corriger les bugs supplémentaires, les cas limites, les multiples versions des dépendances transitives et le support de plusieurs plateformes
    • Quand un langage dispose dès le départ d’un gestionnaire de paquets qui réduit fortement les frictions pour ajouter et distribuer des paquets, son écosystème perd quelque chose de très utile : une bonne pression de sélection naturelle
      Si l’on ajoute à cela des dépôts où n’importe qui peut publier et où la curation est faible, comme PyPI, npm ou Cargo, ainsi qu’une petite bibliothèque standard, il faut s’attendre à souffrir
    • Je me demande si la fonctionnalité « ensemble de bibliothèques faciles à importer » devient une anti-fonctionnalité simplement parce qu’elle facilite l’import des bibliothèques de support. Cela me semble relever davantage du choix des développeurs que de Rust ou de JS
      C’est le développeur qui choisit quelles dépendances importer. Les dépendances que personne n’utilise disparaîtront, et si la plupart des bibliothèques ont beaucoup de dépendances, c’est parce que beaucoup de développeurs préfèrent construire quelque chose au-dessus de dépendances
      Si l’on parle du système de build, Cargo n’impose pas l’utilisation de Crates.io. Il la rend simplement facile. On peut aussi utiliser des dépendances basées sur des chemins, comme avec CMake/Vcpkg ou Conan, ou créer soi-même les bibliothèques
      Même en utilisant Crates.io, si l’on n’aime pas le changement, il suffit de figer les versions. Cela ne fait que faciliter l’obtention des dernières versions
      En Rust, il est facile de construire du logiciel par-dessus des logiciels existants. Si l’on n’aime pas les logiciels existants ou leur rythme d’évolution, il ne faut pas accuser Cargo : il suffit de faire comme on le souhaite
    • Le vrai problème avec l’ajout de dépendances en C++, c’est qu’il est difficile de les ajouter « d’une manière permettant à tout le monde de builder sans problème ». Même si cela fonctionne sur ma machine, cela peut ne pas fonctionner dans l’environnement d’utilisateurs ou de contributeurs potentiels, ce qui pose problème
      Les distributions continuent de builder les paquets Rust depuis les sources et de vendorer les dépendances des crates dans leurs dépôts. C’est plus pénible parce qu’il y a davantage de dépendances et des mises à jour plus fréquentes, mais cela n’a rien à voir avec les bibliothèques partagées
  • Dire que l’API permettant de connaître la taille du terminal est stable depuis 50 ans n’est pas exact. À ma connaissance, TIOCGWINSZ ioctl n’a jamais été standardisé, et il porte plusieurs noms selon les Unix et les BSD
    La fonction tcgetwinsize() n’est entrée dans POSIX qu’en 2024, et toute cette question a une histoire assez regrettable https://news.ycombinator.com/item?id=42039401. Et cela, avant même d’en arriver à Windows

    • L’impression que j’ai eue en lisant l’article, c’est que l’implémentation de remplacement n’est pas plus simple parce qu’elle est meilleure, mais plutôt parce qu’elle ne traite que son propre cas d’usage
      Parfois c’est acceptable, mais le logiciel peut devenir pire pour les personnes ayant d’autres cas d’usage, ou pour celles qui l’exécutent sur des systèmes que l’auteur ne comprend pas et n’utilise pas
      La vraie valeur d’une bibliothèque est de gérer le fait que même des problèmes qui semblent simples comportent en réalité beaucoup de complexité. Je ne pense pas non plus que 3 ou 4 dépendances soient énormes pour une bibliothèque qui tourne à la fois sous Windows et Linux. Dans le meilleur des cas, la personne qui utilise cette bibliothèque a plus d’expérience dans ce domaine et peut couvrir des inconnues dont je n’ai pas conscience
    • Ces appels ne sont pas standardisés, mais ils ne changent pas. En particulier, les appels Windows sont compilés dans d’innombrables binaires, ce qui garantit la stabilité de l’ABI
      Les ioctl ont clairement des problèmes, mais les changements qui ont conduit à plusieurs releases de terminal-size ou de ses dépendances n’ont rien à voir avec les constantes ioctl/TIOCGWINSZ ni avec la structure winsize. Ce code n’a pas changé
    • Dans ce cas, le crate terminal-size appelle simplement le tcgetwinsize de Rustix, et Rustix appelle à son tour le tcgetwinsize de libc. Donc, si l’on fait la même chose directement, on peut réduire pas mal de dépendances, le prix à payer étant à peu près le support de Windows
      Le fait que cette API soit stable depuis 50 ans ou 25 ans est un détail. Cette dépendance ne prétend même pas gérer cette complexité, et il est peu probable que cette fonction change ou disparaisse dans un avenir proche
    • Les terminaux sont un bon exemple de chose qui paraît très simple, mais qui est devenue un gros casse-tête parce qu’il y avait trop de fournisseurs au début et qu’aucun standard industriel ne s’est vraiment imposé. Même savoir si ce qui s’en rapproche le plus est le VT100 ou le VT102 reste ambigu
      La plupart des gens écrivent directement pour cela, mais des fonctionnalités comme la taille du terminal ou le mode brut sont un bazar et nécessitent des choses comme ioctl. Franchement, ce n’est pas terrible
      Mais les bibliothèques sont encore pires. Si vous ne voulez pas linker avec ncurses, que Dieu vous vienne en aide
  • Récemment, j’ai ressuscité la première web app de startup que j’avais créée en 2006. C’était un site de réseau social centré sur le partage de médias, avec une pile LAMP assez simple pour l’époque. PHP 5, MySQL 3.2, mais il avait globalement les fonctionnalités de réseau social de l’époque
    Comme je voulais tester directement de nouvelles technologies de CI/CD, je suis en train de construire un processus de déploiement en sur-ingénierant cette app à des fins d’apprentissage. J’aurais pu utiliser WordPress ou une app Hello World, mais c’est beaucoup plus amusant
    Le PHP, je l’avais presque entièrement écrit moi-même. J’avais aussi créé mes propres bibliothèques pour l’authentification/autorisation, les templates, le traitement des formulaires, etc., et je n’avais utilisé qu’une seule bibliothèque PEAR pour l’envoi d’e-mails. Le frontend était en HTML pur, avec très peu de JavaScript, et Flash servait à la lecture des médias. En 2006, on construisait généralement les choses comme ça
    Il ne m’a fallu qu’environ une heure pour relancer cette app vieille de 19 ans. J’ai remplacé l’ancien pilote PHP mysql par mysqli, et ajusté le schéma ainsi que certaines requêtes pour MySQL 8. Il s’agissait surtout d’entourer de backticks des mots qui sont désormais réservés, et de corriger des valeurs par défaut de colonnes devenues plus strictes. La seule chose qui ne fonctionnait pas, c’était Flash
    À l’inverse, dans mon emploi actuel, nous exploitons des dizaines d’apps Spring Boot écrites en Java 8, et les listes de vulnérabilités issues de dizaines de dépendances s’accumulent sur des pages entières. Quand on en met une à jour, il faut aussi mettre à jour toute une chaîne d’autres bibliothèques, et avec les dépendances transitives, cela devient un cauchemar. On se limite donc au strict minimum pour les vulnérabilités les plus critiques, sans plan réaliste pour tout mettre à niveau
    Ce qui est drôle, c’est que l’app PHP de 2006 et les apps Spring Boot actuelles ne font pas des choses très différentes. Au fond, tout est du CRUD, avec simplement beaucoup plus d’habillage et d’outillage enterprise autour

    • En regardant le monde Go et C sous Linux, j’ai été convaincu par la philosophie des bibliothèques épaisses. On prend une bibliothèque qui résout les problèmes d’un domaine donné, puis on ajoute les éléments nécessaires par-dessus
      On ne tire pas une dépendance pour chaque élément d’une checklist. Cela peut créer du travail en double, mais le chemin de mise à niveau devient beaucoup plus simple
    • La plupart des nouvelles technologies de CI/CD sont devenues standard à cause de la complexité née de la maintenance de dépendances tierces et d’environnements d’exécution qui changent sans cesse. Avec une vieille application LAMP déployée via scp, ce n’est généralement pas un problème
    • Je suis d’accord en partie, mais je n’ai pas envie de passer du temps à réinventer la roue en y introduisant des bugs
      Par exemple, pour des formats de messages de communication standard comme FHIR ou HL7, on n’a probablement pas envie de réimplémenter soi-même toute la définition, déjà complexe, du standard
      Écrire soi-même des fonctions de chiffrement revient aussi, en général, à se tirer une balle dans le pied, et les graves problèmes de sécurité découverts au fil des années l’ont bien montré
      Aujourd’hui, on vit une époque où l’on veut se concentrer sur la résolution des problèmes business plutôt que sur la manière de construire correctement la solution. Avec l’arrivée de l’IA, c’est encore plus important, car tout le code donne l’impression d’être assemblé les yeux fermés
      Passer du temps à tout fabriquer soi-même peut être avantageux à long terme, mais il faut d’abord survivre à la concurrence. Les concurrents ont peut-être déjà conquis le marché au départ avec du code rapide et jetable
    • Dans mon entreprise, nous avons une politique d’analyse statique assez stricte, et elle va devenir encore plus stricte à partir d’avril. Je me demande si vous avez regardé https://docs.openrewrite.org/ pour mettre à jour automatiquement les dépendances
      Récemment, nous sommes passés de Java 8, Spring Boot 2 et Swagger à Java 17, Spring Boot 3.3 et OpenAPI 3, et ça a été assez painless
      Il reste encore à mettre à jour certaines dépendances directes et transitives, mais le principal obstacle a été traité par la migration
    • Le point important ici est qu’en Rust, le problème des dépendances transitives n’existe pratiquement pas. Si vous mettez à jour un crate et que ses dépendances publiques montent aussi de version, et que cette dépendance est exposée dans l’API de sorte que vous devez interagir avec elle, alors oui, il faut naturellement les mettre à jour ensemble
      Mais tant qu’on ne lie pas une bibliothèque C, les dépendances transitives privées n’ont aucune importance. On peut avoir autant de versions de crates incompatibles SemVer que l’on veut dans le même arbre de dépendances, et même dépendre directement de plusieurs versions si nécessaire
      Il n’y a pas besoin de grandes mises à jour groupées à la Java : il suffit de mettre à jour l’élément vulnérable. Il me semble que C# dispose d’une fonctionnalité similaire, mais de manière un peu plus verbeuse
  • Je suis d’accord à 100 %. NodeJS a eu un impact énorme sur ma carrière, mais NPM m’a causé un traumatisme encore plus grand qu’une enfance chaotique
    Il suffit d’imaginer un nouveau programmeur en train de créer une nouvelle app à montrer à ses amis. Il veut ajouter une nouvelle dépendance, mais elle n’est pas compatible avec une autre, alors il décide de tout mettre à jour. L’instant d’après, plus rien ne fonctionne et Babel se met à hurler
    On se dit qu’on finira bien par s’en sortir, mais on tombe bientôt sur une issue Git ouverte indiquant que même des choses basiques ne fonctionnent littéralement pas. Par exemple, Expo a une issue ouverte où un nouveau projet React Native de base ne build pas sur Android
    La moitié du temps, on dirait que tout le monde s’en fiche, et dans ce cas la solution ne se trouve même pas dans l’écosystème Node, mais quelque part dans l’écosystème Android. C’est du duct tape jusqu’au sous-sol. Mais si un projet à plusieurs milliards de dollars peut distribuer un template qui ne fonctionne pas, cela donne aussi la confiance nécessaire pour ne pas ressentir le syndrome de l’imposteur quand mon side project ne marche qu’à moitié

    • Ça, c’est un problème de Node, pas de Rust. Les dépendances n’ont pas besoin de “s’apprécier” mutuellement. On peut avoir toutes les versions en même temps, et rien ne casse
    • Le fait que Node.js et npm soient devenus incontrôlables m’a servi de signal d’alarme il y a environ 15 ans
      J’ai commencé à voir le moment où l’on introduit sa première dépendance comme un échec personnel. Parce qu’à cet instant, au lieu de simplement charger du JS ordinaire avec une balise script, il fallait tout un packaging et toute une organisation rien que pour utiliser un package.json
  • C’est quelque chose qui m’a surpris en passant de Go à l’écosystème Rust. Un projet Go mature, par exemple le backend web en production d’une entreprise, peut n’avoir que 10 à 20 dépendances, dépendances transitives comprises.
    Comme le dit l’article, même un petit projet Rust a de fortes chances d’en avoir beaucoup plus, et c’est presque garanti dès qu’on fait de l’asynchrone.
    Je ne sais pas trop si c’est dû à la culture ou aux fonctionnalités du langage. Par exemple, en Go, les interfaces sont satisfaites implicitement, donc il n’est pas nécessaire d’importer quelque chose pour dire qu’on les implémente.

    • L’une des grandes raisons est que Go dispose d’une bibliothèque standard excellente et très complète, ce qui n’est vraiment pas le cas de Rust.
      Go inclut dans le langage ou la bibliothèque standard des choses qui, en Rust, nécessitent des dépendances : green threads, channels, expressions régulières, client HTTP, serveur HTTP, gestion du temps, flags de ligne de commande, logger, lecture/écriture de GIF animés, etc.
      Je n’adore pas particulièrement le langage Go lui-même, mais côté outils et bibliothèque standard, il est en tête, et c’est clairement ce que j’ai vu de mieux.
    • La différence entre Rust et Go se situe surtout au niveau de la bibliothèque standard. Celle de Rust est volontairement petite.
    • L’énorme bibliothèque standard de Go aide beaucoup à réduire le nombre de dépendances.
  • Je suis largement d’accord avec l’argument. Autoriser l’exposition, hors de sa propre bibliothèque, des abstractions d’autres bibliothèques comporte beaucoup de coûts cachés. Ça ne veut pas dire qu’il ne faut jamais le faire, mais au moment de décider, il faut mettre en balance les coûts futurs.
    Si le package qui encapsule change souvent de conception ou d’objectif, cela crée de l’instabilité. Ce qui était à l’origine la forme la plus simple de garantie en génie logiciel disparaît ou se fragmente.
    Cela rend aussi la participation à l’écosystème plus difficile pour des mainteneurs qui ne sont pas des contributeurs open source de carrière, mais qui connaissent bien un domaine précis. Quand « j’ai trouvé une façon propre de faire X » se transforme en plusieurs semaines de discussions, de négociations et de politique autour de « X doit s’insérer sous Y parce que des gens pourraient aimer Z », tout le monde perd son temps.
    Une chose que j’ai apprise dans la vie, c’est que ce qui est le plus simple survit le plus longtemps. Il faudrait plus souvent faire l’éloge des miniliths et des monolithes. Ce n’est pas un problème propre à Rust ; je l’ai vu dans plusieurs langages. Les communautés open source poussent souvent très fort vers des packages de taille atomique, et je me demande souvent si ce n’est pas davantage pour planter son drapeau ou transférer la propriété que dans l’intérêt réel de la communauté des utilisateurs.

  • En 2025, j’en suis arrivé un peu par hasard à l’idée qu’il est plus rapide de demander à ChatGPT ou Cursor de produire des implémentations sans dépendance de fonctions courantes, et j’y adhère de plus en plus. Surtout après avoir traversé l’enfer des dépendances dans de grosses apps React.
    Il faut aussi penser aux dépendances envers des SaaS ou des services tiers. Beaucoup d’entre elles relèvent de patterns courants et de problèmes déjà résolus, que les LLM peuvent reproduire rapidement.

    • J’ai beaucoup plus tendance qu’avant, avant d’ajouter une bibliothèque, à commencer par une petite fonction utilitaire interne implémentée par l’IA.
      Pour des problèmes au périmètre limité, ça marche plutôt bien, et je peux faire produire par l’IA une implémentation plus complète et plus robuste que ce que j’écrirais moi-même.
      Si je décide plus tard d’ajouter une bibliothèque, j’ai déjà une encapsulation naturelle. Il suffit de modifier l’implémentation de ma fonction pour qu’elle utilise la bibliothèque, sans forcément toucher tous les endroits qui l’appellent. Le moment venu, il est aussi plus facile d’essayer plusieurs bibliothèques.
  • Ironiquement, c’est assez intéressant. Armin est l’auteur original de Flask, le framework web Python. À peu près à la même époque, il existait aussi Bottle, une bibliothèque très similaire.
    Les fonctionnalités étaient presque les mêmes, mais Flask a connu un grand succès, alors que j’ai toujours préféré Bottle. Parce que c’était un fichier unique, sans dépendance, très facile à copier tel quel dans un projet.
    C’était aussi facile à bricoler, et j’ai fini par pouvoir le comprendre dans son ensemble. J’y ai ajouté Gevent pour le serveur et les WebSockets, et cette approche permettait quand même de construire des projets assez lourds.
    Aujourd’hui encore, j’ai une forte envie d’utiliser Bottle pour de petits projets web. C’est juste dommage qu’il n’ait pas vraiment suivi beaucoup des pratiques modernes adoptées par Python au fil des années, et qu’il paraisse désormais un peu daté.

  • Faire soi-même exige de vraies compétences d’ingénierie. Si l’on n’a que des ingénieurs qui ont toujours pioché des bibliothèques dans des écosystèmes comme NPM ou PyPI, ils auront du mal à développer eux-mêmes des solutions à beaucoup de problèmes. Surtout si ces solutions doivent durer et offrir la flexibilité nécessaire.
    « Ne pas se programmer soi-même dans une impasse » demande beaucoup de pratique.
    J’ai aussi souvent vu des cas où l’on peut faire mieux, plus facilement, qu’une bibliothèque existante. Sur un projet, j’ai implémenté un parseur pour une variante de Markdown avec des métadonnées en haut du fichier. La syntaxe était petite, et le parseur tenait en moins d’un écran de code.
    Mais la bibliothèque frontend qui parsait les mêmes fichiers était bien pire que prévu. Elle cassait dès qu’un identifiant de métadonnée contenait un tiret, et il s’est avéré qu’elle utilisait directement l’identifiant comme nom de membre d’objet. Au lieu d’utiliser un simple objet JSON, elle imposait donc artificiellement des limites au choix des noms, et cassait dès qu’il y avait un tiret, comme dans something-something.
    Au final, mon parseur a été abandonné, au motif que les gens devraient le « maintenir ». Pourtant, il fonctionnait tout simplement, et pouvait facilement s’adapter aux changements de syntaxe. Avec un minimum de connaissances sur les parseurs, il n’avait rien de difficile à comprendre.
    C’est difficile à croire, mais il me semble que personne d’autre que moi dans l’équipe n’avait déjà écrit de parseur avec une bibliothèque de génération de parseurs. Et il y a beaucoup d’autres exemples de ce genre.

  • Si l’on n’utilise qu’une seule fonction mais qu’on se retrouve à en compiler des centaines, cela devrait allumer un signal d’alarme. Il y a environ un an, j’ai mené un projet de mise à jour de dépendances tierces.
    L’une d’elles était une riche bibliothèque mathématique contenant toutes sortes de fonctions. En creusant un peu, on s’est rendu compte que nous n’utilisions qu’une seule méthode : trouver la médiane d’une liste.
    J’ai indiqué à l’ingénieur responsable la page Wikipedia, puis je lui ai demandé de supprimer la dépendance et d’écrire une unique méthode effectuant cette opération mathématique.
    Cela dit, le vrai problème n’est pas tant l’usage de dépendances tierces en soi, mais le besoin d’un concept permettant de ne prendre qu’un fragment étroit d’une bibliothèque. Si l’on n’a besoin que d’une petite partie d’une énorme bibliothèque, pourquoi devoir tout embarquer ? J’ai déjà entendu quelqu’un proposer les « microframeworks » comme approche pour cela.

    • En Rust, les bibliothèques peuvent définir des features activables ou désactivables conditionnellement, ce qui fournit un mécanisme intégré pour contrôler ce qui est réellement inclus. Tokio en est un bon exemple : vous serez peut-être surpris d’apprendre que Tokio n’a besoin que de deux dépendances directes au total. Tout le reste est optionnel https://github.com/tokio-rs/tokio/blob/ee19b0ed7371b069112b9...
      Malheureusement, les gens ne semblent pas examiner soigneusement l’ensemble de features activées par défaut par leurs dépendances, ni le réduire activement. La syntaxe pour ajouter des features est simple, mais retirer des options activées par défaut est plus pénible : il faut spécifier no-default-features, puis rajouter une à une les features par défaut que l’on veut malgré tout conserver.
      Pire encore : pour qu’une bibliothèque permette d’élaguer les features inutiles de ses propres dépendances, elle doit créer ses propres features et les mapper vers les features de chacune de ces dépendances. Par exemple, si vous avez 5 dépendances, chacune avec 1 dépendance obligatoire et 4 dépendances optionnelles, il faut créer 20 features de mapping dans votre propre bibliothèque pour donner aux utilisateurs un contrôle total sur les features transitives. Et cela n’inclut même pas les features que vous voudriez créer pour vos propres utilisateurs en aval, autour de votre propre code.
      J’en viens de plus en plus à penser que l’ergonomie autour des features, surtout lorsqu’il s’agit de réduire le gonflement inutile, est si mauvaise qu’elle agit comme catalyseur des problèmes de temps de compilation de Rust. Comme ce sujet ne semble pas beaucoup discuté lorsqu’il est soulevé, je devrais peut-être écrire un billet de blog exposant fermement mon point de vue, afin d’avoir quelque chose à pointer si la situation actuelle perdure.
    • Si l’on fait ça, on se retrouve avec des affaires comme leftpad dans l’écosystème JS.
      D’un autre côté, je pense que ce n’est pas un problème si le système de confiance est bon.
      J’aime aussi, dans une certaine mesure, l’approche à la Shadcn : si un composant vous plaît, vous le copiez dans votre propre bibliothèque. Mais si une vulnérabilité apparaît, je ne peux pas savoir si je suis concerné.
      Pour certains codes, c’est acceptable ; pour d’autres, il faut vraiment s’appuyer sur du code examiné par un maximum de regards.
    • Pour ceux qui ne connaissent pas, en pseudocode cela donne à peu près ceci :
      Median(list) { let len = length(list) if len % 2 == 0 { let x = floor(len/2) return (list[x] + list[x+1]) / 2 } return list[len/2] }
      En supposant toutefois que la liste soit déjà triée. J’ai résisté à la tentation d’appeler is_odd ici.