J'ai indiqué framework, mais j'aurai pu mettre outil ou librairie, l'idée est la même : aucun des outils construit par quelqu'un qui n'est pas sur votre projet n'est fait pour votre projet. Aujourd'hui je trouve qu'on empile trop vite des briques construites par des tiers sans réfléchir à ce que ça implique, et je pense que c'est pourtant important de prendre un peu de recul sur tout ça.

Disclaimer : je vais beaucoup parler de dépendance frontend et un peu Java ici, pas que ça ne s'appliquerait pas ailleurs (backend ou même ops / cloud / infra) mais juste parce que je maitrise plus le sujet, parce que c'est là que j'ai le plus vu de liste de dépendance à rallonge et que j'ai le plus vue un manque sur les mises à jours alors que tout évolue très vite paradoxalement…

Angular / React / Vue ?

Note : si vous ne considérez pas React et/ou Vue comme des frameworks, mettez Next et/ou Nuxt dans la liste aussi 😇

Je mets les pieds dans le plat directement : a-t-on besoin d'un framework ? 🙈

Attention, je ne suis pas anti-framework ! Loin de là ! Je trouve qu'ils sont de très bons outils, et qu'ils résolvent très bien certains problèmes ! Mais parfois ils sont juste overkills.

Prenons un cas simple : vous voulez une page statique pour faire une vitrine d'un magasin physique (par exemple), non vous n'avez pas besoin de Next et de Server-Side Rendering (SSR) ou de génération statique, du HTML, du CSS et éventuellement une pointe de JavaScript vanilla fonctionnera très bien, voir même beaucoup mieux ! La plateforme web est très puissante, propose beaucoup de chose de base donc pourquoi ne pas l'utiliser ?

Imaginons que du vanilla ça ne vous suffise pas (ce qui est surement votre cas aussi) : est-ce que vous vous êtes déjà demandé qui et pourquoi Angular, React et Vue sont développés ?

Angular est un framework développé par Google pour un besoin de création d'applications de gestions, à large échelle, avec une certaine uniformité, permettant de mutualiser au maximum des briques entre les différentes applications, en faisant beaucoup de validation à la compilation de sorte à pouvoir facilement maintenir dans le temps l'ensemble des applications tout en détectant très vite que quelque chose est cassé.

React est développé chez Facebook, puis Meta (la maison mère, comme on trouve du React hors de Facebook) dans le but d'avoir un outil simple pour créer des interfaces web, mais pas que (d'où le découpage react / react-dom). Les contraintes de Facebook sont essentiellement le besoin de diffuser au maximum les interfaces pas de faire des applications complexes.

Vue est développé par Evan You et quelques personnes indépendantes ou salariés de différentes entreprises, dans le but créer un socle facile à prendre en main pour créer des applications web et ajouter au besoin des briques supplémentaires pour ajouter des fonctionnalités. Vue est très orienté vers les besoins des utilisateurs et/ou contributeurs de Vue.

Et vous dans tout ça vous êtes où avec les besoins de votre application React ? Nulle part. Pourquoi ? Parce que React c'est le framework de Meta pour Meta et éventuellement quiconque aurait des besoins similaires par hasard. Et attention : pour moi, c'est parfaitement normal. C'est Meta qui paie les développements, c'est Meta qui doit être la priorité du projet, et si ça ne vous convient pas, il existe d'autres options. Par contre si vous choisissez React il faut que ce soit en connaissance de cause et en acceptant que le projet n'ira pas dans votre direction mais dans la direction dont Meta a besoin même si ça vous complique la vie au quotidien. 🤷

A noter aussi que je dis que React est piloté par Meta mais Vercel est aussi devenu un très gros contributeur de React, en poussant Next.js et en débauchant une partie de la Core-Team React. Le business de Vercel c'est le cloud, je trouve ça assez peu surprenant que de plus en plus React pousse à l'utilisation de Next.js et/ou de brique backend (comme les React Server Component) qui vont vous inciter à peut-être aller chez eux.

Build or buy or take open source ?

On entend parfois un choix entre "build" (faire soi-même un outil maison) ou "buy" (prendre une licence pour une solution du marché), souvent on englobe dans le "buy" de prendre une solution open source, mais c'est un peu différent pourtant.

Pour moi il faut se poser les bonnes questions avant de décider : est-ce que c'est quelque chose qui va rester ? combien de temps ? Est-ce qu'on peut attendre ?

En gros si c'est pour faire un POC (Proof Of Concept), prendre une solution toute prête est surement la meilleure option, car peu importe le coût on cherche surtout à valider que c'est faisable ou que ça vaut le coût d'investir. Si on ne peut pas attendre de fabriquer la solution, la question ne se pose pas, mais si on peut attendre un peu / beaucoup, ça peut valoir la peine de se poser la question de construire soi-même.

En fait la question est toujours la même pour moi : si on veut prendre une solution commerciale ou open source, il faut d'abord se poser la question de pour qui a été fait cette solution. Un éditeur logiciel sera parfois être prêt à faire des développements spécifiques pour notre besoin (mais ça dépend aussi de ce qu'on demande et de l'éditeur, par exemple : SAP ou Salesforce ou Jira ne bougeront pas pour votre petit besoin ultra spécifique, ce sera à vous de vous adapter à eux). Côté Open source, il y a plusieurs types de projets : ceux qui sont des projets "publics", ceux qui sont "forkables", ceux qui sont "ouverts". Les projets "publics" c'est ceux que vous ne pourrez pas influencer, simplement voir comment c'est fabriqué (comme React par exemple). Les projets "ouverts" seront prêts à accepter des idées ou mêmes du code externes pour élargir le scope du projet. Ceux qui sont "forkables" seront des projets qui seront souvent assez petits pour être repris en interne pour une évolution différente du projet et qu'on pourra facilement considérer comme notre projet assez facilement.

Je ne vais pas non plus m'étendre sur les projets qui sont pour moi faussement open source : il y a un projet Github, parfois vide, parfois pas vraiment avec du code, on peut parfois poster des issues parfois pas vraiment (fermés systématiquement pour renvoyer sur un autre canal).

Comment on fera demain ?

Autre point auquel il faut toujours réfléchir c'est à ce qu'on fera dans quelques mois ? dans un 1 an ? 2 ans ? 5 ans ? 10 ans ? Est-ce qu'on pourra encore utiliser la dépendance ? est-ce qu'elle sera mise à jour ? est-ce qu'elle sera toujours accessible ? Combien ça couterait de ne plus utiliser cette dépendance dans le futur ? Ce serait quoi l'impact de ne plus mettre à jour du tout ?

C'est des questions pour lesquelles je vois malheureusement peu de gens répondre, tout le monde prend la dépendance et ne se pose pas plus de question que ça… Pourtant c'est ça qui est important ! Si vous gagnez 10min aujourd'hui, mais vous liez pieds et poings à une dépendance qui va vous prendre des jours et des jours mis bout à bout dans le futur ça ne vaut pas l'investissement à mon avis.

Si à l'inverse c'est un choix fort et structurant, que vous savez vous fera gagner un temps conséquent et qu'il vous coûtera toujours moins en maintenance que ce qu'il vous fait gagner comme temps, ça vaut le coût !

Et à l'inverse si vous coder vous-même l'outil dont vous avez besoin ? C'est un investissement à faire maintenant, mais aussi dans le futur, car votre futur vous ou les futurs mainteneurs du projet devront aussi le maintenir. Soyons honnêtes il y a toujours des briques internes abandonnées, c'est typiquement le cas des librairies de Design System : dans toutes les entreprises où je suis passé, il y a toujours une à trois librairies de composants, jamais complète, jamais correctement maintenu, qu'on nous promet comme "la librairie qui vivra dans le temps et qui résout les problèmes de X" mais en fait non, parce qu'elle aussi finira par être abandonnée. Pourtant on aura investi du temps pour la mettre en place, investi du temps parfois pour corriger la librairie mais au bout du compte on va de nouveau investir du temps (généralement beaucoup de temps) pour la remplacer par une autre.

Et la plateforme ?

Pour l'instant je parlais du fait de toujours composer des dépendances, mais on peut raisonner autrement : la plateforme web fait déjà beaucoup de chose pour nous, donc pourquoi ne pas se contenter de ça ?

On se retrouve souvent face à des applications qui sont des gros formulaires, donc on sort notre super framework, avec une super librairie de gestion de formulaire dédié au framework pour se faciliter la vie, puis on va ajouter une super librairie qui sert à faire des appels http pour soumettre notre formulaire à une API. Et bien sûr on va faire tout un tas de composant pour organiser tout ça. Et on va devoir créer une petite surcouche à notre librairie de formulaire parce qu'elle est bien, mais elle est quand même pas mal verbeuse quand il s’agit de déclarer un simple champ de texte. Et pareil pour notre librairie pour faire des appels http, on va faire notre surcouche, parce que quand même faire un simple get / post c'est pas si simple.

Mais en fait, la plateforme sait faire tout ça non ?

<!DOCTYPE html>
<html>
    <head>...</head>
    <body>
        <form method="POST" action="/api/v1/login">
            <div class="field">
                <label for="form--username">Username:</label>
                <input id="form--username" name="username" type="text" />
            </div>
            <div class="field">
                <label for="form--password">Password:</label>
                <input id="form--password" name="password" type="password" />
            </div>
            <button type="submit">Login</button>
        </form>
    </body>
</html>

Ici j'ai à la fois fait des choses pas nécessaire (les block field, le type="submit") et utilisé des comportements automatiques (comme le fait que par défaut un formulaire post est soumis en application/x-www-form-urlencoded qui se paramètre avec l'attribut enctype, ou ne pas mettre de placeholder). En tout cas, ici j'ai bien une page web, osons dire une petite application web front, qui offre une page avec un formulaire de connexion, qui va soumettre à l'API le contenu du formulaire. Aucun mapping de donnée, aucune librairie, tout est géré nativement par le navigateur.

Vous allez me dire "et les contrôles frontend ?", alors déjà il faut dans tous les cas en faire côté backend aussi côté tout ce qui est fait côté client est contournable et c'est possible d'en faire côté front aussi :

    <!-- Ici le champ ne sera valide que si on a un contenu qui fait entre 4 et 20 caractères -->
    <input id="form--username" name="username" type="text" required pattern="\w{4,20}" />

Et pour les erreurs ? Le style ? Vous savez que CSS propose beaucoup de chose pour faire ça sans aucune ligne de JavaScript ?

Oui ça peut vous demander d'apprendre des choses en plus, mais il a bien fallu que vous appreniez à le faire avec votre stack habituelle, et l'avantage ici c'est que vous êtes sûr que jamais ça ne cassera, car c'est une des règles du html/css : ça doit toujours être rétro-compatible pour faire partie de la spécification.

Et le langage ? le compilateur ? la plateforme ?

On a tendance à oublier que le langage de programmation est notre première dépendance !

Parce que oui, même si vous faite du "vanilla TypeScript" ou "vanilla JavaScript" ou même du "vanilla Java", vous dépendez de votre langage et de la plateforme et/ou compilateur.

Si vous faites du JavaScript, vous devez composer avec votre runtime, côté frontend c'est plutôt ok, on est habitué à jongler entre les navigateurs, côté backend, Node.js est plutôt stable en termes d'API mais est-ce Node.js sera toujours là dans les années à venir (je pense que oui). Mais peut-être que vous n'êtes pas en Node.js mais sur des fonctions sur un cloud provider ? Du coup vous dépendez de leur plateforme, est-ce que l'API est stable dans le temps ? même à plusieurs années ? Là je n'en sais rien personnellement…

TypeScript est plutôt stable comme JavaScript est plutôt stable aussi. Par contre vous n'avez quand même aucune garantie qu'un code qui compile en TypeScript 5 compilera en TypeScript 6 ou même 7. Je peux même affirmer qu'à la marge certains morceaux de code ne fonctionneront plus, comme c'était le cas il y a quelque temps au passage de certaines versions de TypeScript qui devenait un peu plus strict sur la validation de type et donc on devait ajuster un peu les typings pour que tout fonctionne. Pour la version 7, c'est déjà annoncé que le compilateur va être ré-écrit en Go sans garantir 100% de compilation, mais seulement la grande majorité.

Côté Java c'est un peu pareil. Le code Java 8 compile avec un JDK 9, mais il y a l'ajout de la partie module Jigsaw qui a obligé à faire évoluer quelques éléments pour gérer ça. Et le passage à Java 9 a aussi interdit la définition de package dans plusieurs modules / jar donc forcément ça a cassé des projets existants.

Sans même parler d'API : la mise à jour des plateformes d'exécution est aussi importante pour l'aspect sécurité, donc c'est un point à ne pas négliger ! En effet les CVE qu'on découvrirait aujourd'hui sur un OpenJDK qui n'est plus supporté (et pour rappel : il n'y a pas de LTS sur les OpenJDK, donc chaque nouvelle version implique le retrait du support de la version précédente) ne sera pas corrigé.

À l'inverse : vous voulez une plateforme super stable ? Cobol + les machine zOS d'IBM. Le code écrit y'a 40-50 ans tourne encore sans aucun souci. Je pourrais aussi citer l'API Win32, elle n'a presque pas bougé depuis Windows 95, ce qui me parait plutôt pas mal !

Et côté tests ?

Et côté tests ça donne quoi à votre avis ? Sachant que si on suit des principes crafts, on va très vite se dire que les tests devraient pouvoir survivre au projet, comme il s'agit d'une documentation vivante !

Comme côté langage je parlais Java et JavaScript, restons là-dessus.

Côté Java, je vois du JUnit depuis plus de 10 ans, mais déjà j'ai vu quelques évolutions se produire ! En particulier le passage de JUnit 4 à 5 : changement de tous les imports, changement de nom pour certaines annotations, etc. C'est clairement pour le mieux, car c'est beaucoup plus confortable de travailler en JUnit 5 que 4, mais pour autant c'est bien une dépendance qui évolue, qu'il faut maintenir, qu'il faut suivre, et qui a un coût.

Côté JavaScript, désolé mais c'est un enfer. J'ai vu passer : Karma + Jasmine, Mocha + Chai (+ Sinon), Jasmine seul, Jest et récemment Vitest et bien sûr sans compter les projets un peu plus à la marge comme tape ou même simplement npm test. Et chacun a ses propres caractéristiques, sa propre syntaxe, pas compatible… La syntaxe Jasmine est sûrement la plus largement utilisable mais ce n'est pas non plus complet pour tous. C'est assez difficile de se dire qu'on peut maintenir les tests au-delà de la vie du projet dans ces conditions malheureusement…

Et là je ne parle globalement que de "Runner", mais il faut aussi compter avec les extensions en tout genre qui permettent de faire des mocks ou s'adapter à des frameworks. Eux aussi doivent être maintenus, et eux aussi peuvent évoluer dans le temps. Même pour une bonne extension, il faut aussi prendre en compte qu'il y aura à un moment une incompatibilité avec votre "Runner" qui va évoluer dans une direction différente.

Quelques exemples

En fait c'est toujours très variable. Prenons quelques exemples, car un exemple est souvent plus parlant que des longues explications.

Prenons le cas de Redux (avec ou sans Redux ToolKit) : c'est une librairie très structurante, elle aide fortement à gérer l'état d'une grosse SPA, c'est une librairie stable et agnostic de tout framework depuis très longtemps (2015), c'est assez peu probable que ça change dans les années à venir (Redux ToolKit étant une surcouche optionnelle), le modèle de fonctionnement est simple mais efficace, il s'adapte bien à n'importe quel framework ou même à ne pas utiliser de framework du tout. Maintenir la dépendance à jour est très facile, tout est toujours documenté. C'est une dépendance qui apporte beaucoup plus qu'elle ne coûte à mon sens (sans pour autant dire qu'il faut l'installer sur tous les projets !).

Une librairie comme immer qui permet de faciliter la mutation en copy-on-write (création d'un nouvel objet à chaque modification, pas besoin de faire du spread operator en masse, la librairie masque ça à travers une api en getter/setter permettant d'écrire du code comme si on éditait directement l'objet). C'est une librairie outil qui est assez peu structurante, elle est facilitatrice, mais elle n'est pas obligatoire. Sous le capot une version naïve pourrait être écrite assez facilement en quelques dizaines de lignes (comme on aurait pas besoin de gérer 100% des cas d'usage de immer). L'API est extrêmement simple et plutôt stable (de mon expérience j'ai vu un seul vrai break de l'API, il a été entièrement documenté et expliqué, sur mon projet de l'époque on avait corrigé le problème très vite et c'était pour le mieux, car le break d'API était lié à un meilleur usage de TypeScript pour éviter des erreurs). C'est donc encore une fois une dépendance qui aide mais avec un coût faible.

Angular / React / Vue, c'est simple vous ne pouvez pas en sortir sans jeter et ré-écrire l'application. Oui il y a des patterns pour limiter ça, il y a des techniques pour isoler un peu le framework des règles métiers, etc. Mais soyons honnêtes : on ne voit globalement jamais ça. Donc on jette toujours la solution d'avant pour la refondre dans la nouvelle technologie qui durera les 10-15 prochaines années devenant doucement le legacy de demain. Pourtant ces frameworks vous font souvent gagner beaucoup de temps, donc c'est un coût accepté.

Dans un autre genre, je peux citer Redis. Je pensais franchement que c'était un projet "parfait" au sens où il est Open Source, très performant, toujours bien pensé pour répondre aux besoins des utilisateurs. Et il y a eu le changement de license de la version 7.4 qui coupait l'Open Source, avec un rétro-pédalage partiel en version 8. Mais la confiance a été rompue. Et ça pourrait arriver sur d'autres projets / produits. Et là on ne parle que d'un changement de license, le code restait visible, le produit restait accessible. On pourrait imaginer un abandon pur et simple du produit. Sauf que changer de base de données de cache n'est pas forcément simple en fonction de comment vous l'implémentez / le déployez.

Conclusion

Je soulève beaucoup de point plutôt négatif avec le fait d'embarquer dans notre projet des briques extérieures, mais je suis aussi pragmatique : j'utilise aussi des solutions toute faites, parce que c'est plus pratique, parce que tout le monde les maîtrise, parce que ça fait économiser du temps à résoudre des problèmes techniques pour se concentrer sur des problèmes métiers.

Je ne vous recommanderai jamais de bannir à 100% toutes dépendances externes, mais pour moi il faut être pragmatique quand on en ajoute, car c'est un coût indirect qui s'ajoute au projet qu'en général personne ne voudra assumer et qui sera pas / mal compris par le métier.

Je pense que le mieux étant de se concentrer sur peu de dépendance mais bien les choisir, être pragmatique sur le besoin réel ou réfléchir au coût pour couper la dépendance. Avoir quelques dépendances bien choisies permet de garder un certain contrôle sur le périmètre, uniformiser les pratiques, avoir un cadre de travail confortable, en avoir trop c'est rendre tout confus et difficile à maintenir techniquement dans le temps.

Crédit photo : Générée via Mistral AI avec le prompt suivant

Imagine a lush, well-maintained garden where each plant represents a popular framework or library. At the center, a large, robust tree symbolizes the web platform, with strong branches and green leaves. Surrounding the tree, various plants and flowers represent frameworks like Angular, React, and Vue, each with distinct characteristics: some are well-maintained and thriving, while others appear neglected or overgrown with weeds. Winding paths meander through the garden, inviting visitors to explore and choose the plants that best suit their needs. In the background, a blue sky and white clouds add a touch of serenity, illustrating the importance of reflection and informed choice in project development. Additionally, include a darker section of the garden with thorns, dead shrubs, and an overall sense of neglect to represent poorly maintained or abandoned frameworks.