Dependence Day,
(en matière de code)

Publié le 19 avril 2021, par Richard Carlier
Programmation
#code #dépendance

De plus en plus souvent (systématiquement?), les projets en programmation sont basés sur ce que l’on appelle les dépendances (ou packages, selon les langages).

Si, dans le principe de ne pas réinventer la roue, c’est plutôt bénéfique, qu’installe-t-on réellement? Souvent, on ne se pose pas trop la question…

Alors faisons-le, analysons un peu ce que cache un npm install… !

Les grands principes

Les projets de code commencent de moins en moins souvent par un dossier vide.

De nombreux développeurs ont déjà eu votre problématique, et ont mis à disposition des morceaux de code qui peuvent vous aider à la résoudre. Souvent open source, en accès libres. Assez souvent soutenus par des équipes de développeurs, parfois des entreprises…

Nous parlons, selon le contexte, de :

  • bibliothèque
  • framework
  • dépendances
  • dépendances de développement
  • package

Il y a des nuances entre chaque, mais ne nous encombrons pas de cela (je n’emploierai que dépendance pour la suite).

Ainsi, un projet Python va souvent commencer par python -m venv venv (sans oublier l’activation), et une série de pip install... ; là où dans un site web en html/css/js nous verrons une série de liens vers des scripts externes (jquery, plugins de slider, normalize, bootstrap, …).

Quelques imports en python…

Un projet NodeJS verra lui un npm init suivi de npm install , etc.

A quoi sert toute cette cuisine (et dépendances?)

La première instruction (venv ou init) servant à créer un environnement virtuel, pour en quelque sorte cloisonner notre projet par rapport à la machine complète (et construire un environnement reproductible), les secondes (pip, npm…) servant à installer les fameuses dépendances.

Pour la suite, je pars sur l’étude d’un petit projet NodeJS, utilisant socket.io (pour illustrer un cours sur l’échange temps réel bidirectionnel entre client serveur, mais peu importe).

Nous avons donc commencé par initialiser notre projet (npm init), ce qui a créé un fichier package.json. Comme mon projet a besoin d’un serveur http et de communiquer via des websockets, j’ai enchainé par (car je ne vais pas réinventer la roue) :

npm install express socket.io

L’outil npm va alors télécharger quelques fichiers (les fameuses dépendances) et mettre à jour le fichier de configuration (package.json) qui, après nettoyage, ressemble à ceci:

{
    "name": "sockettest",
    "version": "1.0.0",
    "main": "index.js",
    "dependencies": {
        "express": "^4.17.1",
        "socket.io": "^3.0.1"
    }
}

Une fois ceci fait, je peux attaquer mon propre code, qui commencera par quelque chose du genre:

const app = require("express")();
const server = require("http").createServer(app);
const io = require("socket.io")(server);

En gros, require signifie que j’incorpore le code de express et socket dans mon propre code ; cela peut prendre la forme d’instruction require, import, include... selon les langages.

Les dépendances pouvant être directe (notre projet en a besoin) ou de développement (notre code en a besoin, mais ne sera pas inclus dans le projet final). Je n’entrerai pas plus dans les détails.

Mais du coup, on installe quoi ?

Ben, on vient de le dire, express et socket !

Non.

Pas que.

Justement, car le grand principe, est que notre dépendance peut avoir elle même besoin d’une autre dépendance, qui dépend elle même d’une autre, etc.

Ainsi, Socket.IO va posséder 9 dépendances (et 15 dépendances de développement).

Et engine.io en possède 7, etc…

Certaines dépendances étant partagées, elles ne seront pas installées plusieurs fois (heureusement).

Alors, à l’arrivée ?

Les dépendances de chaque dépendance (!) étant précisées dans un fichier package.json, j’ai remonté la piste.

Ce qui nous donne:

  • 72 dépendances (pouvant donc être utilisées à plusieurs reprises)
  • 572 fichiers
  • 4 Mo de dépendances (environ, le poids du dossier node_modules)
  • 100.194 lignes de code (environ aussi, certains fichiers étant compilés ou non textuels, leur nombre de lignes n’est pas comptabilisées)

Tout un petit univers en quelque sorte:

Cela peut paraître beaucoup, mais du coup mon projet ne fait plus que  27 lignes et 998 octets

Écrire moins pour produire plus?

Un projet VueJS ?

Un projet Vue relativement standard, (avec router, vuex, axios, bootstrap et fortawesome), et selon le même genre d’analyse:

Dépendances directes du package.json :

{
    "@fortawesome/fontawesome-free": "^5.15.3",
    "@popperjs/core": "^2.9.2",
    "axios": "^0.21.1",
    "bootstrap": "^5.0.0-beta3",
    "core-js": "^3.6.5",
    "vue": "^3.0.0",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
},

Quelques valeurs :

  • 1.381 dépendances
  • 28.210 fichiers
  • 166.688.173 octets
  • 3.279.456 lignes de code

Et donc:

Pas mal, non ?

Autre exemple, React ?

Très à la mode, un petit projet React ?

Le package.json est assez modeste, aucune dépendance de développement… et le minimum en installation…

"dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "web-vitals": "^0.2.4"
  },

Et allons-y…

  • 1.780 dépendances (en cascade)
  • 50.867 fichiers
  • 197.716.804 octets
  • 4.711.825 lignes de code

Ce qui nous donne:

Un petit côté étoile de la mort non ?

J’ai tout à fait conscience que ces deux graphes sont illisibles.

Mais en fait si: ils représentent le bord… bazar que peut représenter trop de dépendances…

Transition toute trouvée…

Les problèmes liés aux dépendances…

Car l’abus de dépendance, en code (comme ailleurs) entraine de potentiels problèmes:

  • Bugs : votre code est parfait, mais si une dépendance possède un bug, votre code ne marche pas. Où si la dépendance de la dépendance…
  • Conflits et problèmes de versions : c’est peut être le plus fréquent. Votre code utilise une version d’une certaine dépendance, dont certaines dépendances ne sont pas à jour, ou pas compatible avec votre système…
    • En verrouillant les versions, on se préserve un peu (« express »: « ^4.17.1 » signifie qu’il nous faut au moins la version 4.17.1, ce qui évite les trop anciennes)
    • En n’installant pas les dépendances de façon globale c’est mieux aussi
  • Mise à jour du système : même genre de problème que le précédent.
  • L’abandon d’un projet : une dépendance du fin fond de la galaxie disparait de l’écosystème, pouvant entraîner la panique chez toutes celles qui en dépendent. Voir le projet est repris par quelqu’un qui n’est pas de confiance…
  • Confusion de dépendances : il s’agit là d’un autre problème, celui du piratage. Un petit article du Monde Informatique Confusion de dépendance : un hack ébranle les SI de Microsoft, Apple ou Netflix (fév 2021)
  • Maturité des dépendances : certaines sont peut-être très intéressantes, mais jeunes; comment vont-elles évoluer ?

Faut-il pour autant remettre en cause le développement par dépendance?

Et pourquoi ne pas retirer les extensions de WordPress (qui pourrait faire l’objet d’une autre étude, mais j’ai plus de mal à trouver des chiffres)?

Nous ferions de plus grands progrès en toutes choses, si nous étions moins dépendants des objets qui nous environnent.

David Augustin de Brueys (1721)

Top