- Une dépendance peut donner l’impression d’être une fonctionnalité gratuite, mais elle s’accompagne en réalité de divers coûts et d’une complexité supplémentaire
- Une mauvaise dépendance entraîne différents risques, comme une courbe d’apprentissage, des changements d’interface soudains, ainsi que des problèmes de déploiement et d’installation
- Cas représentatif, TigerBeetle vise une politique de "zéro dépendance" pour des raisons de sécurité, de performance et de simplicité opérationnelle
- L’auteur propose un cadre d’évaluation des dépendances (ubiquité, stabilité, profondeur, ergonomie, étanchéité)
- Il est essentiel de faire preuve d’esprit critique pour distinguer les bonnes des mauvaises dépendances et, au moment d’en choisir une, de tenir compte non seulement de la productivité à court terme mais aussi du coût global et des risques
Une mauvaise dépendance coûte plus cher que le NIH (Not Invented Here)
Les inconvénients des dépendances et leurs coûts cachés
- Beaucoup de développeurs considèrent l’ajout d’une dépendance comme une « fonctionnalité gratuite », alors qu’en pratique cela entraîne les coûts suivants
- Le temps et la complexité nécessaires pour l’apprendre
- L’installation de la dépendance elle-même est souvent difficile
- Lors d’une montée de version majeure, la charge de modifier largement son propre code
- La dépendance doit au final impérativement être présente dans l’environnement de déploiement/d’installation (conteneurs, bundling, etc., avec la complexité que cela implique)
- L’adoption d’une mauvaise dépendance peut aussi créer une architecture de déploiement complexe sans lien avec la fonctionnalité principale
Le principe de zéro dépendance chez TigerBeetle
- Tigerbeetle est une base de données financière fondée sur Vanilla Zig et adopte une politique de zéro dépendance
- Développée uniquement avec le langage Zig, sans dépendances externes en dehors du toolchain Zig
- L’objectif est d’éviter les problèmes liés aux dépendances, comme les risques d’attaques sur la supply chain, les baisses de performance ou l’allongement du temps d’installation
- Plus un logiciel est profondément ancré dans l’infrastructure, plus les coûts liés aux dépendances se répercutent sur toute la stack
- L’usage d’une petite boîte à outils standardisée favorise la maintenance et l’efficacité de développement
- Le projet se concentre sur l’utilisation de Zig seul pour traiter rapidement de nouveaux problèmes et réduire la complexité
Cadre d’évaluation des dépendances
- Tout en reconnaissant qu’une absence totale de dépendances est impossible pour tous les développeurs, l’auteur souligne qu’il faut évaluer les dépendances avec prudence
- Il propose d’évaluer une dépendance selon les 5 critères suivants
- Ubiquité (Ubiquity) : à quel point est-elle courante dans l’environnement visé ? Faut-il un déploiement ou une installation supplémentaire ?
- Stabilité (Stability) : fréquence des problèmes comme la rétrocompatibilité, les changements d’API ou l’arrêt du support
- Profondeur (Depth) : volume réel de fonctionnalité caché sous l’API, difficulté à la remplacer en l’implémentant soi-même
- Ergonomie (Ergonomics) : l’API est-elle intuitive, déclarative et facile à utiliser ? Quel est l’état de la documentation ?
- Étanchéité (Watertightness) : dans quelle mesure l’abstraction fonctionne-t-elle bien, et à quel point peut-on ignorer la technologie sous-jacente ?
- La communauté de développement discute surtout de l’ergonomie, tandis que les quatre autres points sont souvent négligés
Exemples de bonnes dépendances
-
Appels système POSIX
- Universalité : utilisables sur presque toutes les plateformes, comme Linux, Android, macOS ou BSD
- Stabilité : très forte compatibilité d’interface, avec très peu de changements
- Profondeur : une API unique masque des centaines de milliers de lignes de code du noyau
- Ergonomie : style C assez traditionnel, mais sans difficulté majeure à l’usage
- Complétude : globalement satisfaisante, malgré quelques détails comme la persistance des données sur les dispositifs de stockage
-
Codes de contrôle de terminal ECMA-48
- Universalité : pris en charge par la plupart des terminaux, à l’exception de
cmd.exe sur Windows
- Stabilité : inchangés depuis 1991
- Profondeur : créer directement une telle norme serait absurdement difficile
- Ergonomie : correcte, hormis la faible lisibilité due au caractère Esc
- Complétude : très peu de préoccupations liées aux dépendances matérielles
-
Plateforme web (Web API, HTML, JS, CSS, etc.)
- Universalité : les navigateurs web sont installés dans presque tous les environnements du monde
- Stabilité : politique très forte de rétrocompatibilité
- Profondeur : créer son propre navigateur est irréaliste tant la profondeur est grande
- Ergonomie : un peu de complexité, mais une documentation et des outils de développement excellents
- Complétude : très élevée, sauf dans certains cas particuliers comme les fichiers, l’audio ou la vidéo
Conclusion
- Au lieu du copier-coller et de l’abus de dépendances, il faut faire preuve d’esprit critique et analyser les coûts dans leur ensemble
- Lorsqu’on introduit une dépendance, il faut évaluer de manière critique les coûts et les bénéfices de chaque dépendance,
et prendre en compte avec soin les risques et coûts potentiels de l’ensemble du système : à long terme, c’est une approche bien moins coûteuse et plus sûre
2 commentaires
TigerBeetle : une base de données OLTP spécialisée en comptabilité
Avis sur Hacker News
C’était la première fois que j’entendais parler de TigerBeetle, donc je suis allé voir. Si vous construisez une base de données de registre financier en Zig, où la sûreté et les performances sont critiques, et que vous devez encaisser un million de transactions par seconde sur un seul cœur CPU, alors éviter d’ajouter des dépendances est tout à fait rationnel vu le risque que cela introduit. Mais la plupart des développeurs ordinaires construisent des systèmes CRUD assez classiques, et ils ne sont généralement pas à ce niveau d’exigence. Beaucoup de dépendances peuvent être truffées de bugs, mais elles sont aussi souvent de meilleure qualité que ce que la majorité des développeurs écriraient eux-mêmes. En entreprise aussi, le vrai goulet d’étranglement est souvent le niveau des développeurs que l’on peut recruter. Les situations étant différentes, il faut garder à l’esprit que des conseils opposés peuvent tous être justes dans leur propre contexte
Je travaille effectivement chez TigerBeetle. Le point clé, c’est le contexte. TigerBeetle ou rust-analyzer ont tous deux une culture d’ingénierie forte, mais comme les problèmes traités sont différents, cela produit des cultures différentes. Les dépendances mentionnées dans l’article ressemblent moins à des bibliothèques qu’à des interfaces système, comme POSIX, ECMA-48 ou la plateforme web. Une dépendance de bibliothèque, si elle pose problème, on peut toujours la réécrire ; mais des éléments fondamentaux comme des interfaces système sont en pratique impossibles à remplacer, ou très coûteux à changer. Il est puissant de décider de ne pas faire des choses qui ne correspondent pas au périmètre du logiciel. Par exemple, si vous avez une équipe spécialisée pour écrire du code de multiplication matricielle, vous pouvez utiliser des bibliothèques externes pour d’autres sujets qui n’ont rien à voir avec le cœur métier, mais je pense qu’il faut mieux concevoir la décomposition des responsabilités du produit. Cela permet de mieux isoler les dépendances essentielles à l’intérieur du système
Le problème de ce point de vue, c’est qu’il n’est valable que si l’on ne pense qu’aux développeurs de bas niveau. Le secteur technique a tendance à diluer tous les conseils jusqu’au plus petit dénominateur commun, mais honnêtement, dans les endroits où j’ai travaillé, j’ai rarement vu des développeurs trop faibles pour dupliquer une dépendance cassée et résoudre le problème. On méprise aussi beaucoup les CRUD, alors que de mauvaises abstractions y provoquent une perte de temps et une douleur énormes. Même les solutions populaires posent souvent beaucoup de problèmes et font baisser la productivité dès qu’on sort du niveau tutoriel le plus basique
Cela n’a rien à voir avec le niveau intrinsèque des développeurs. Quel que soit l’outil ou le produit que vous utilisez, vous finissez toujours par dépendre de choses écrites par d’autres. Très peu de gens autour de moi implémentent eux-mêmes leur code de multiplication matricielle, et même eux ne réimplémentent pas pour autant toutes les bibliothèques open source sans rapport avec leur domaine. En général, on commence à s’obséder sur les dépendances à cause d’exigences réglementaires, d’un intérêt personnel ou d’un attachement particulier à une bibliothèque donnée. Si tout le monde appliquait ce principe jusqu’au bout, on passerait nos journées sur la plage à ramasser du sable
Dire que « les développeurs CRUD moyens ne sont pas solides » est excessivement catégorique. La plupart des développeurs ne choisissent pas les systèmes sur lesquels ils travaillent, et les ressources sont presque toujours insuffisantes pendant le développement. Il faut exploiter des dépendances « bon marché » pour sortir rapidement un logiciel fonctionnel. On dirait que cet avis ne tient pas vraiment compte de cette réalité
TigerBeetle est une startup lancée assez récemment, et n’a même pas encore sa version 1.0 stable. Je pense qu’il est beaucoup trop tôt pour dire si cette approche fonctionne vraiment
Le NIH (Not Invented Here) en lui-même est extrêmement utile lorsqu’on l’applique en évaluant de façon réaliste jusqu’où l’on veut assumer la responsabilité. Par exemple, un framework frontend web parfaitement adapté à mon domaine peut très bien mériter d’être créé et maintenu en interne. En revanche, pour une base de données, un moteur de jeu, un serveur web ou des briques fondamentales de cryptographie, c’est une autre histoire. Si le problème est tellement difficile qu’aucune solution existante ne convient, il vaut mieux commencer par redéfinir le problème. Repenser le problème lui-même coûtera bien moins cher que de recréer de zéro toute la suite de tests de SQLite
Il existe pourtant beaucoup de cas où il vaut mieux écrire soi-même une base de données, un moteur de jeu, un serveur web ou des primitives cryptographiques. Si de simples fichiers et des index en mémoire suffisent, même SQLite est souvent un choix excessif. Dans beaucoup de jeux, surtout avec de petites équipes, un moteur sur mesure peut être plus avantageux. L’intérêt des moteurs complets réside surtout dans la pipeline, mais ils s’accompagnent d’un surcoût important. Un serveur web n’est pas plus complexe qu’une application FastCGI. En cryptographie aussi, toutes les situations ne sont pas des problèmes de sécurité ; pour une simple vérification de hash, une implémentation maison peut très bien convenir. Je ne pense pas qu’il soit bon de développer une forme d’impuissance acquise face à des sujets qui paraissent difficiles. Le fait qu’une solution existante résolve le problème ne veut pas dire qu’elle soit optimale ni la plus efficace
Dans ce cas, pourquoi existe-t-il autant de moteurs de base de données ? Au final, les systèmes informatiques complexes impliquent chacun leurs propres compromis. Contraintes, scalabilité, concurrence, sécurité, caractéristiques des données, mode de stockage : il y a énormément de choix possibles. J’ai tendance à faire d’autant plus confiance à une dépendance coûteuse qu’elle cherche elle-même à minimiser ses propres dépendances. Les systèmes automatisés de gestion des dépendances ont plutôt tendance à complexifier les choses, donc une gestion manuelle prudente réduit souvent la charge
Je vois deux raisons d’utiliser des dépendances tierces. (1) Elles sont publiées directement par le fournisseur du service, avec une durée de vie relativement alignée. (2) Elles remplacent du code complexe que je n’ai pas envie d’écrire. Le cas (1) ne pose pas de problème, pour des raisons business. Il faut simplement accepter que des changements importants puissent survenir quand le service évolue. Le cas (2) dépend de la valeur du code complexe que je veux éviter. Introduire une dépendance signifie que je dois consacrer du temps et des ressources aux mises à jour et aux tests selon le calendrier de quelqu’un d’autre, et en assumer la responsabilité
On rencontre assez vite des problèmes qu’un SGBDR ne peut pas résoudre. Les SGBDR sont conçus autour des modifications concurrentes de données et des jeux de données mutables ; si vous n’en avez pas besoin, de simples index peuvent déjà apporter des gains de performance énormes. Si les données sont immuables, une implémentation maison peut être bien plus rapide qu’un SGBDR
L’exemple des SGBDR est intéressant. Wikipédia recense plus d’une centaine de SGBDR, et chacun résout certains problèmes tout en échouant sur d’autres. C’est le résultat d’une réflexion pratique sur les solutions et de leur mise en œuvre réelle
Les dépendances créent des risques, mais ne pas les utiliser du tout peut aussi faire perdre en compétitivité de développement et de mise sur le marché. D’où l’importance d’un processus de gestion des dépendances
Les points 4 et 5 sont vraiment importants, mais souvent oubliés. Même sur des projets personnels, après une période d’abandon, il m’est arrivé de revenir sur un projet et de constater que les dépendances étaient obsolètes ou que le dépôt avait été supprimé. Du coup, maintenant, je fais des forks privés du code source, je le compile moi-même, et je fork aussi au niveau source les dépendances de toutes mes dépendances. Comme ça, même si l’écosystème change brutalement plus tard, l’impact reste limité. J’en viens à préférer les bibliothèques source aux binaires
Au point 5, le fork peut être une charge excessive. Le vendoring, en stockant les dépendances dans son propre git ou derrière un proxy de cache, est aussi une bonne méthode. C’est particulièrement adapté aux projets de longue durée. Quand il y a énormément de fichiers de dépendances, comme avec NodeJS, des outils comme Yarn ou PNPM sont plus efficaces
Concernant le point 4, une dépendance connue comme SQLite survivra probablement bien plus longtemps que le produit que je construis. Penser que mon produit durera plus longtemps que cet open source-là serait une forme d’arrogance. Je n’ai pas non plus l’intention de compiler moi-même le noyau Linux
Tout le code devrait au minimum pouvoir être compilé sans connexion réseau. L’idéal est de ne pas dépendre d’artefacts binaires, mais ce n’est pas toujours réaliste
Excellente remarque. Je vais ajouter cela à mon document de procédure d’adoption des dépendances. Le problème, ce sont les cas comme JavaScript où l’arbre de dépendances est beaucoup trop profond
Avec l’expérience, tout finit toujours par relever du « ça dépend » (It Depends™). Quand j’étais plus jeune, je m’attachais aux principes sans aucune exception, et j’ai parfois produit le pire code possible en forçant l’usage de bibliothèques ou de paradigmes inadaptés. Aujourd’hui, je transforme en bibliothèques maison ce que j’utilise souvent et je le sors en packages, puis je ne prends en dépendances externes que ce que je ne sais pas faire moi-même. Tant que la qualité et la maintenabilité me semblent acceptables, j’accepte volontiers aussi des solutions externes
Dans le secteur de l’énergie, on a tendance à éviter délibérément les dépendances. Dès qu’on introduit une dépendance externe, il faut examiner tous les changements. Les outils de code IA nous ont beaucoup aidés, surtout pour générer des outils CLI. On utilise aussi des LLM pour produire la documentation OpenAPI, puis on la sert avec la bibliothèque standard de Go. Le LLM lui-même est une dépendance externe, mais les outils CLI générés avec ne sont pas liés au code réel, donc l’exigence qualité y est plus faible. Bien sûr, les développeurs frontend ne veulent pas travailler sans React, mais comme ces produits sont traités à part, c’est une exception. Si on fournit aux ingénieurs de petits outils qui réduisent leur obsession des dépendances de qualité, une politique de minimisation des dépendances devient plus facile à appliquer
Savoir distinguer les bonnes dépendances des mauvaises est une compétence importante. À mon avis, les dépendances payantes sont généralement défavorables. L’entreprise qui les fournit a de fortes chances de les avoir conçues pour créer du lock-in. Le « minimalisme des dépendances » est un bon concept (tweet lié de VitalikButerin)
Les dépendances payantes n’ont qu’une seule source de support ; si l’entreprise qui les fournit ferme, l’ensemble du projet est menacé. Comme la plupart des entreprises ne sont pas éternelles, il faut absolument se demander si l’avenir de cette dépendance influence la trajectoire de mon projet
J’ai eu de mauvaises expériences avec des dépendances payantes imposées par des équipes non techniques. À l’inverse, des dépendances « open core » largement utilisées par la communauté, comme Sidekiq, ont bien moins de chances de disparaître soudainement, donc elles inspirent davantage confiance. L’avantage du payant, c’est que tant que l’entreprise fonctionne sainement, on n’a pas à s’inquiéter du support
Qu’ils soient payants ou gratuits, les composants fournis par une entreprise créent tous du vendor lock-in. La gestion du risque revient à l’équipe d’intégration, qui doit trouver des alternatives ou modulariser pour mieux contrôler ce risque
Pour une dépendance payante, il faut impérativement qu’elle soit livrée via une interface fondée sur un standard ouvert ou avec des implémentations alternatives, afin d’éviter le lock-in. S’il existe d’autres options, on conserve une possibilité de migration
Dire qu’une dépendance payante est mauvaise peut aussi signaler qu’on manque de budget. Je veux que le support de mon code soit le « travail » de quelqu’un. Si beaucoup de gens postulent ou si de nombreux développeurs prennent spontanément la responsabilité, c’est plus stable ; et pour un projet personnel, même si le code est ouvert, il faut quand même quelqu’un pour aider quand il y a un problème. Ce qui compte, c’est le sens des responsabilités pour que le projet ne soit pas abandonné même si l’entreprise s’arrête
Beaucoup de gens ont tendance à s’obséder sur l’écriture de nouveau code, alors qu’en pratique, même une dépendance médiocre est dans 90 % des cas bien plus efficace à utiliser
Les dépendances sont une arme à double tranchant. Dans les grandes entreprises logicielles, il arrive souvent qu’abandonner la maintenance d’un code et le réécrire soit moins coûteux, d’où cette pratique. Dans les petites agences web ou de branding, un backend de haute qualité n’est en réalité presque jamais nécessaire. En revanche, si les redoutés patterns enterprise existent, c’est parce qu’ils permettent d’isoler et de maintenir un code qui restera préservé sans dépendances externes, même cinq ans plus tard, quand la documentation et la mémoire du secteur auront disparu. Une dépendance externe implique toujours deux risques : l’arrêt du support, ou l’apparition de changements cassants. Au final, cela affecte le flux de développement des fonctionnalités. Avec des composants internes, ces compromis restent contrôlables en interne. Pour un SaaS, utiliser rapidement des dépendances est le bon choix pour réussir à court terme ; si la sûreté et le support long terme sont indispensables, il faut voir plus loin pour réussir. L’écriture de nouveau code est rarement le vrai goulet d’étranglement d’une organisation
Je me demande jusqu’à quel point votre entreprise prend au sérieux les vulnérabilités de sécurité et les licences. Avant, j’étais assez permissif avec les dépendances, mais depuis que je suis passé dans une entreprise très stricte sur la sécurité et les licences, ma vision a énormément changé
La différence entre bibliothèque et framework est aussi importante. Une bibliothèque est un outil qui fait bien une chose ; un framework, lui, impose la structure globale de l’application. La communauté Go se méfie des gros frameworks et préfère la bibliothèque standard, des bibliothèques légères, et au besoin le copier-coller du code source. Par exemple, des frameworks comme Gin (web API) ou GORM (ORM) sont agréables à utiliser, mais ils limitent la structure interne et augmentent la complexité. Comme le SDK standard de Go est déjà assez puissant, je pense qu’il vaut mieux éviter d’ajouter des dépendances au-delà du nécessaire
L’auteur est néo-zélandais. On sent en arrière-plan l’esprit du Number 8 wire en Nouvelle-Zélande, c’est-à-dire l’attitude consistant à se débrouiller avec ce qu’on a et avec son ingéniosité (article Wikipédia sur le Number 8 wire)
Même parmi les développeurs expérimentés d’autres pays que la Nouvelle-Zélande, beaucoup sont d’accord de la même façon. Presque tout le monde a souffert d’un mauvais choix de dépendance ou d’une mise à jour inutile de bibliothèque
En tant que Néo-Zélandais, j’ai l’impression que la mentalité Number 8 Wire a déjà disparu il y a vingt ans
Je l’apprends aujourd’hui. Ça me fait penser à l’expression australienne « She'll buff out, mate »
L’ampleur de l’adoption et la base commerciale d’une dépendance influencent aussi la scalabilité. Si un outil est utilisé par des organisations qui déploient à une échelle 100 à 1000 fois supérieure à la mienne, il y a peu de chances qu’il atteigne ses limites sur mon problème. À cette échelle, les bugs ont aussi plus de chances d’avoir déjà été découverts ou corrigés, ce qui le rend au final plus sûr pour moi
Il arrive aussi que de grosses bibliothèques ne fonctionnent pas du tout dans de petits environnements. Par exemple, le compilateur Swift Protocol Buffers plantait autrefois sur des champs inattendus. Beaucoup de grandes entreprises ne testent pas directement ce genre de parcours en dehors de leurs objectifs à grande échelle
J’ai déjà découvert des bugs graves dans des bibliothèques célèbres écrites par de grandes entreprises (Meta, Google, Microsoft, etc.). Même en signalant le problème, la correction prenait du temps et la résistance au changement était forte. Dans ce genre de situation, le fait d’implémenter moi-même a finalement été plus rapide et a même amélioré les performances. Dans le conseil en particulier, la direction du travail est souvent perturbée par des demandes déraisonnables des clients. En tant que développeur, plus j’ai confiance dans ma capacité à faire moi-même, plus il m’arrive de penser qu’il vaut mieux implémenter plutôt que dépendre d’énormes briques externes. Bien sûr, je ne m’attaque pas à des sujets vraiment massifs comme les navigateurs ou les modèles d’IA, mais, par exemple, j’implémente moi-même des moteurs d’inférence locaux, des moteurs de rendu HTML ou même des bases de données orientées graphe. Ce que les clients attendent n’est pas la nouveauté ou l’innovation, mais la réduction du risque. Quand je développe moi-même, il est beaucoup plus facile de tenir les délais. C’est plus efficace que de passer mon temps à éplucher la documentation de Google ou d’autres géants. Ces trois derniers mois, à force d’enchaîner des journées de 12 heures pour sauver un projet abandonné par l’équipe précédente, j’ai beaucoup pensé à tout cela tard dans la nuit