L'illusion du fullstack web

Le fullstack est une illusion dangereuse. Les technologies back et front sont aujourd'hui deux univers trop complexes pour être maîtrisés par un seul humain. L'ignorer est la certitude de produire un "truc" loin des coûts, des délais et du service envisagés pour l'utilisateur.

Un développeur full-stack web est un développeur qui sait développer le backend et le frontend d’une application web. Une sorte d'homme magique à tout faire. Les directions informatiques françaises adorent cela. Leur méconnaissance de l’informatique frontend leur donne l’illusion que java et javascript, tout ça c’est pareil, c’est de l’informatique, et qu’un bon backender sera forcément un excellent frontender, car le backend c'est quand même bien plus sérieux. En effet fabriquer des pages HTML, même actives, leur semble élémentaire (allez savoir pourquoi) plutôt que d’attaquer des bases de données et de servir des API, avec les problèmes de sécurité, de réseau et de performances que cela impose, sans parler du devOps et des mises en production.

À leur intention il n’est donc pas superflu de rappeler que le développement frontend est un univers particulier, aussi complexe que celui du backend et qui nécessite l'intervention se spécialistes dédiés. Développer un frontend n’a rien à voir avec un développement classique (Java, Python, PHP, .Net, ...) car il est soumis à des contraintes fortes, inconues dans les autres informatiques. Croire qu'un backender saura les appréhender naturellement relève de l'illusion. Qui sait tout faire le fait moyennement, qui se consacre à une activité peut atteindre l'excellence.

Rappelons donc certains éléments du javascript qui provoquent des catastrophes lorsqu'on les confie à un backender.

De piètres performances

Le javascript ne tourne pas sur le processeur du terminal (PC, tablette, mobile) mais sur la machine virtuelle du navigateur Internet (Chrome, Firefox, IE, Safari, ,,,). Par conséquent ses performances sont pitoyables à côté de celles d’un développement classique, Java par exemple. Dès lors si on veut procurer de bonnes performances à l’utilisateur il est absolument nécessaire de réduire au maximum la quantité de code à exécuter sur le frontend. Une erreur typique est de lui expédier par API un datamodel (fichier JSON) qu’il devra processer avant de pouvoir finalement l’utiliser. Pire même, de lui demander d’appeler différentes APIs pour qu’il les réconcilie afin d’y trouver ce dont il a besoin. À cause des piètres performances du javascript, quel que soit le framework utilisé, l’idéal pour le frontend est de recevoir des APIs un datamodel qui correspondent exactement aux besoins de l’application, qu’il n’ait plus qu’à afficher sans devoir le processer, sinon c'est le drame. Une autre erreur classique est de développer la fonctionnalité "au cas où", qui n'est pas réclamée expressément par le product owner, mais qui pourrait être utile un jour : cela utilise des ressources inutilement et dès lors baisse les performances. Le développeur Java oublie souvent cette contrainte de performances lorsqu'un frontender spécialisé l'aura systématiquement en tête.

Pas de lecture/écriture sur le disque dur

Le javascript ne permet ni d’écrire ni de lire sur le disque dur du terminal, et cela pour des contraintes de sécurité évidentes : n’importe quelle page web serait susceptible d’apirer les données de votre disque dur, ou d’y déposer un virus. Il n’existe que quelques espaces de stockages prédéfinis utilisables : cookies, sessionStorage, localStorage et IndexedDB. C’est espaces sont classifiés par URL, c’est à dire qu’un cookie par exemple, établi pour un domaine ne pourra pas être lu par un autre domaine. Idem pour les autres stockages. On comprend ici encore que la cause en est la nécessité de sécurité. Les espaces de stockage sont en général limités, 4 ko pour un cookie, 5 Mo pour les Storages, mais autant que vous voulez pour l’indexedDB, qui possède néanmoins la contrainte d’être asynchrone (voir plus loin) et forcément au fromat JSON. Le développeur Java sera surpris par ces nouvelles formes de stockage, notamment avec asyncronisme, quand le frontender spécialisé saura très exactement utiliser l'un ou l'autre selon les besoins.

Pas, ou extrêmement peu, de gestion de la mémoire

Une fois encore pour des contraintes de sécurité le javascript ne peut pas utiliser directement la RAM de votre terminal, sinon vos données seraient visibles par n’importe quelle page web. Il n’est pas possible d’alouer/libérer de la mémoire simplement. Chaque instance de variable déclarée reste en mémoire, tant que la variable n’a pas été explicitement détruite dans le code, puis nettoyée par le « garbage collector », un robot interne au navigateur qui nettoie la mémoire périodiquement mais sur lequel vous n'aurez jamais la main. Un code javascript mal conçu pourra instancier plusieurs fois une variable au lieu de réutiliser des instances existantes, ce qui provoquera une utilisation excessive de la mémoire. C’est un piège classique que vous mesurez aisément sur votre navigateur, vous savez lorsqu’il « rame » c’en est généralement la raison (pour résoudre redémarrez-le). N’oubliez pas qu’à cela s’ajoute que votre page n’est en général pas la seule à être affichée dans le navigateur, et que la mémoire est donc le plus souvent déjà bien occupée. Si votre appli se vautre alors sur ce qui reste de mémoire, l’utilisateur va trouver cela vraiment très pénible, et changera d’appli. Un développeur Java est en général fort peu rompu à une telle gestion hirsute de la mémoire, lorsqu'un frontender spécialisé sait l'utiliser à son avantage.

Structurellement asynchrone

Un navigateur internet considère que le réseau peut mettre plus ou moins de temps à renvoyer une information qui lui est réclamée. Pour répondre à cette contrainte il considère l’asynchronisme comme le comportement par défaut : la requête d’une information ouvre un thread qui ne sera refermé qu’au retour de cette information. Un clic sur l’interface est lui aussi géré de façon asynchrone, tout comme les accès à l’indexedDB (espace de stockage JSON illimité, voir plus haut). Le problème est que chaque thread est ignorant des autres, et donne sa réponse en un temps indéterminé et indéterminable. Il est alors très complexe de réconcilier les retours de plusieurs APIs par exemple, sauf à utiliser des foules de codes redondants imbriqués en poupées Russes. La gestion de l’asynchronisme est crucial dans l’architecture d’un code frontend, c’est un des deux points qui vous posera forcément problème, avec le partage des variables entre composants HTML. Le résultat peut être catastrophique en terme de performance, de fiabilité, de maintenace et d’évolutivité. Attention donc car ce concept d’asynchronisme est presque absent du backend, et un développeur Java tombera en général dans tous les pièges que saura éviter un frontender spécialisé.

Pas de variable global au sens classique

Un composant affiché sur une page web est un morceau de code HTML associé à un morceau de code javascript. Malheureusement une variable utilisée dans un composant n'est reconnue que dans ce composant et dans aucun autre. Imaginez par exemple un tag <input> dans lequel l'utilisateur peut écrire son nom, et un autre tag <div> dans lequel il peut afficher ce même nom. Si je modifie l'input, le div ne sera pas modifé tant que je n'aurai pas développé un code spécifique imposant que le div soit remis à jour lorsque l'input est modifié, même si j'utilise la même variable. En d'autre termes il n'y aura par défaut aucune communication automatique entre la variable "name" utilisée dans l'input et la même variable "name" utilisée dans le div, ni même entre le HTML et le javascript. Ceci représente le second écueil incontournable qu'il vous faudra forcément franchir pour développer un frontend qui plaira à l'utilisateur, le premier étant l'asynchronisme. C'est ici que la plus grande partie de votre stratégie de développement sera fixée : quelle technologie devrez-vous utiliser pour simplifier ce problème, quel framework ? Pour ma part j'ai choisi Angular car il embarque une fonctionnalité très simple mais d'une efficacité redoutable : le service partagé. Angular est doté d'une boucle temporelle de quelques dizaines de millisecondes qui scrute si une variable déclarée dans un service a été modifiée par un composant qui l'avait importé. Si c'est le cas il force la mise à jour de cette variable dans tous les autres composants qui eux aussi importent le même service contenant la variable, DOM HTML compris. On croirait être revenu à une variable Java. Cela simplifie considérablement la programmation au regard de technologies comme React par exemple, où la gestion des variables globales se fait par l'intermédiaire de stores Redux-like, extrêmement complexes à gérer, fastidieux à programmer et incapables de résoudre les asynchronismes. Un développeur Java découvrant cette particularité du javascript a peu de chance de faire le bon choix pour une problématique qui lui était inconnue jusque là, tandis qu'un frontender spécialisé ira droit au but et saura justifier son choix technologique.

Javascript est un langage de bas niveau

Javascript n'inclue aucun plugin, aucune extension standard qui puisse générer les composants affichés à l'écran : menus, accordéons, carrousel, tableaux, checkbox, etc. Ces composants doivent être codés en HTML et indispensablement activés par un code javascript qui lui corresponde. Il y a bien sûr les input ou autre select du langage HTML, mais leur affichage standard opéré par votre navigateur est pour le moins sommaire (pas de couleurs, pas d'images, pas d'animation, pas de connection par défaut au javascript, ...). Il vous faudra donc développer vous même vos composants, ou bien intégrer une bibliothèque proposée sur internet, par exemple Material, PrimeNg ou NgBootstrap pour Angular. Mais ne soyez pas trop optimiste car sauf à produire des pages relativement banales, aucune de ces bibliothèque ne répondra à 100% à vos souhaits. Il est rare qu'un développeur Java soit efficace pour développer/corriger ces composants, tandis que c'est le métier d'un développeur frontend spécialisé.

Javascript est non typé, illusion du Typescript

À l'origine le javascript est simplement le langage du moteur d'affichage du code HTML sur le navigateur. Comme il ne s'agissait que d'afficher, il n'y avait aucune différence à faire entre un numérique, un alphanumérique ou un booléen, tout ça se termine en chaîne de caractères à afficher sur l'interface. Le javascript est donc non typé, ce qui décontenance les développeurs Java mais permet au frontender d'en tirer avantage. En effet si nous devons réduire l'utilisation de la mémoire comme expliqué plus haut, il vaut mieux déclarer une seule variable qui puisse être alphanumérique, puis modifiée en numérique ou en booléen, plutôt que de déclarer deux variables, car cela sera moins lourd en mémoire. Donc loin d'être une hérésie insupportable le non typage de javascript a ses raison qui sont très pragmatiques, que le backender comprend rarement. Certes le typage doit être considéré comme une bonne pratique de développement pour un codage collectif et collaboratif plus efficace, nul n'en doutera, mais c'est un autre aspect des choses, qui n'est pas destiné à économiser de la mémoire. Le développeur Java se sentira donc mieux dans l'univers Typescript typé que dans celui du javascript non typé, au point qu'il oubliera souvent que le premier est compilé dans le second et que c'est ce dernier qui s'exécute chez l'utilisateur. Ce faisant il risque d'oublier l'impératif de réduction de la mémoire, programmant naturellement comme en Java. Le frontender quant à lui saura jouer sur les deux tableaux, profiter de la structuration du typage pour l'organisation de son code, mais accéder au non typage lorsque l'avantage sera flagrant.

Des bibliothèques plus incompatibles les unes que les autres

Puisque le javascript est un langage de très bas niveau, comme expliqué ci-dessus, et que les coûts et délais sont comptés, il est souvent appréciable d'intégrer une bibliothèque extérieure à son propre développement, lorsqu'on a besoin d'un composant un peu complexe par exemple. Malheureusement à cause de la permissivité causée par le non typage du javascript, deux bibliothèques trouvées sur internet ont très peux de chance d'être compatibles entre elles, même si elles traitent du même composant. Très peu de chance aussi que vous puissiez l'intégrer sans effet de bord et sans méthode et code d'intégration. Le développeur Java aura souvent tendance à prendre ce qui vient de l'internet comme du bon pain et multipliera les intégrations extérieures, jusqu'à ne plus pouvoir tant les incompatibilités finiront par nuire. Le frontender quant à lui est habitué à cet univers instable et saura faire la part des choses, ou redévelopper le composant si ce que lui propose l'internet ne lui convient pas.

J'arrête ici cette liste, bien incomplète tant l'univers du frontend est très particulier et ne ressemble en rien à l'informatique classique, notamment du backend. Retenez que si vous choisissez le fullstack au lieu de ressources back d'un côté et front de l'autre, vous obtiendrez un "truc" très moyen, difficile à débugguer, à maintenir et à faire évoluer. Quant à vos délais et budgets, préparez-vous au pire, pas moins. Je vous parle d'expérience.

Je ne saurais trop vous faire considérer que les webapp à succès (Office 365, What'sapp, Bitbucket, Facebook, GitHub, Wikipedia, Le Bon Coin, ...) doivent leur succès à une parfaite connaissance des problématiques frontend/backend et que, jamais au grand jamais, ils ne feront confiance à un développement fullstack. Condidérez plutôt que le frontend est un véritable univers de spécialiste, vous y gagnerez temps et argent.