Joel Spolsky, un développeur et écrivain américain, fait partie de la première école. Pour lui, écrire un programme à partir de zéro est la pire erreur stratégique qu'un développeur puisse commettre. Dans un billet, il a énuméré plusieurs cas pour mieux se faire comprendre entre autres, le navigateur de Netscape qui n'existe plus aujourd'hui. Netscape 6.0 allait entrer dans sa première version bêta publique à l'époque en sautant la version 5.0. La version 4.0 fut sa dernière version majeure et avait été publiée trois ans plus tôt. Trois ans, c'est terriblement long dans le monde de l'Internet, déclarait Joel : « Pendant ce temps, Netscape était assis, impuissant, alors que sa part de marché s'effondrait », ajoutait-il. La cause, selon Joel, est que ses développeurs ont décidé de réécrire la base de code à partir de zéro.
Vingt ans plus tard, Nicholas Tietz-Sokolsky a publié un billet sur le blog d’ingénierie de Remesh où il vient remettre en cause cette philosophie ne serait-ce que par l’expérience même de Remesh :
« Notre histoire commence en janvier 2019. Remesh était alors une entreprise beaucoup plus petite. Nous avions récemment embauché quelques ingénieurs et nous avions 5 ingénieurs focalisés sur le produit, et une poignée d'ingénieurs focalisés sur l'apprentissage automatique (ML) ou DevOps. Malgré l'embauche récente de ces ingénieurs, notre vitesse était toujours douloureusement basse. L'ajout de fonctionnalités simples a pris du temps. Nous avons eu beaucoup de bogues dans le produit que nous avons simplement reconnu comme étant ‘connus’ et que nous n'avons pas corrigés. Et le produit dans son ensemble semblait n'avoir pas changé de manière significative depuis un certain temps.
« Il est important de comprendre pourquoi nous avons eu ces problèmes. Nous avons supposé (et après la réécriture, validé) que le problème ne venait pas de notre côté : après tout, nous avions embauché des ingénieurs talentueux. Le problème venait en grande partie notre base de code et notre processus. La base de code héritée dans laquelle nous travaillions n'était pas adaptée à la fois aux compétences de notre équipe et aux problèmes que nous résolvions, et notre processus encourageait et s'appuyait sur des connaissances cloisonnées: il n'y avait pas de ‘stack full’ à Remesh ».
L'état de la base de code de Remesh en janvier 2019
L’ancienne application de Remesh a été conçue à l'origine pour quelque chose de très différent de ce à quoi elle sert actuellement. Au départ, Remesh permettait aux utilisateurs de tenir des conversations bidirectionnelles entre des groupes entiers ou un individu et un groupe. Nicholas a illustré le premier cas d’utilisation (conversations bidirectionnelles entre groupes) en parlant des démocrates et des républicains qui se parlent pour trouver un terrain d’entente, le second (conversations bidirectionnelles entre des groupes et un individu) en parlant du maire d’une ville qui s’adresse à ses citoyens pour mieux comprendre leurs besoins.
« Cependant, comme nous avons trouvé l'adéquation au marché des produits, le cas d'utilisation a changé. Nous nous sommes penchés sur un modérateur singulier parlant à un groupe de personnes », a-t-il noté.
À la suite de ce changement, certaines anciennes décisions de conception n'avaient plus de sens et le schéma nécessitait des changements majeurs. Au-delà des problèmes de base de données, il reconnait que la base de code elle-même était assez difficile à comprendre, car les fonctionnalités avaient été boulonnées sans beaucoup de refactoring majeur : « nous avions une couverture de test très médiocre dans les zones qui avaient le plus besoin de refactoring car il s'agissait du code le plus ancien, écrit avant d'établir de bonnes pratiques de test ».
Au-delà de tout cela, les langages et frameworks utilisés ne marchaient pas avec leur équipe. La base de code backend a été écrite dans Elixir, que peu de leurs développeurs connaissaient très bien. L'une des bases de code frontend a été écrite dans une version très ancienne d'Angular et ils avaient deux autres frontend qui étaient en React : « peu de nos ingénieurs étaient à l'aise sur l’un d’eux, sans parler des trois. Les langages et les frameworks utilisés ne convenaient ni à notre équipe ni à notre problème, ce qui nous a un peu ralentis ».
Quelles étaient les options ?
À ce stade, Remesh a réalisé que sa base de code avait besoin d'un changement majeur. Dans ce cas de figure, trois options se posent à vous :
- Le refactoriser jusqu'à ce que le problème soit résolu
- Le réécrire d'une traite
- Le réécrire au coup par coup
« Pour le frontend, le refactoring n'était pas vraiment une option. Notre version d'Angular était suffisamment ancienne pour que, malheureusement, nous n'ayons pas vraiment de chemin de mise à niveau clair vers une version moderne d'Angular (de toute façon nous ne voulions pas être sur une version d'Angular). Et puisque nous anticipions des changements majeurs dans l'interface utilisateur et l'API, un refactoring ne serait pas réalisable. Donc, sur le frontend, nous devions décider entre réécrire d'un seul coup ou réécrire au coup par coup.
« Le back-end avait quelques problèmes que nous voulions résoudre - notre schéma, notre langage et notre base de code ne correspondaient plus au problème que nous résolvions. Nous avons utilisé Elixir pour son support massif de concurrence, pourtant nous n'en avons jamais eu besoin et cela a fini par nous porter préjudice : la façon dont la concurrence est gérée dans la machine virtuelle Erlang a rendu le profilage très difficile, car vous savez ce qui est calculé, mais pas d'où il est appelé - et bonne chance avec le réglage des performances. La base de code Elixir a également limité la contribution de nos ingénieurs ML à la base de code backend : ils travaillaient en Python tous les jours et n'avaient pas le temps de se plonger profondément dans Elixir. Pour faire court, nous voulions quitter Elixir et passer à Python, car alors toute notre équipe aurait pu contribuer, le langage prendrait en charge les problèmes et nous aurions plus de facilité à profiler le code ».
Au bout du compte, l’argument en faveur de la réécriture se résumait vraiment à cette confluence de facteurs :
- Remesh voulait que chaque membre de son équipe puisse contribuer à la base de code backend, et Python avait l’avantage d'avoir déjà une large adoption déjà au sein de l’équipe et d’être facile à apprendre
- L’ancienne base de code était si fragile et si peu testée que sa refactorisation serait un processus ardu.
- Remesh pouvait gagner en efficacité en passant à un framework « opinionated » comme Django, avec beaucoup de valeurs par défaut permettant de gagner du temps (comme Django Admin).
- Remesh voulait avoir l'opportunité de créer une toute nouvelle version influencée par ce que l’entreprise avait appris des clients et pourrait ensuite gérer la transition vers la nouvelle version plutôt que de mener une bataille pendant 12 mois avec beaucoup de clients à chaque changement.
« Pour arriver à cette décision, nous avons fait une planification assez approfondie », indique Nicholas qui note que l’équipe devait impérativement déterminer quel serait le niveau d'effort pour chacune de ces options. « Il est apparu assez rapidement que la réécriture de l'application en gros prendrait du temps, mais que sa refactorisation ou sa réécriture fragmentaire prendrait beaucoup plus de temps, il y avait beaucoup, beaucoup plus d'incertitude autour d'une telle approche. Si nous options pour la refactorisation, nous risquerions beaucoup plus ».
Autant d’éléments qui les ont conduits à tout réécrire : « nous étions déterminés à réécrire, car cela nous permettrait de corriger les erreurs des années passées, et cela nous permettrait de faire avancer le produit en même temps ».
Remesh a donc commencé à tout réécrire à partir de février 2019 « après avoir planifié les fonctionnalités que nous devrions inclure pour la parité des fonctionnalités avec la plateforme existante dans le cadre d'un effort de diligence raisonnable pour nous assurer que c'était la bonne voie ».
« Le processus réel de construction de la nouvelle version s'est déroulé sans heurts après un démarrage cahoteux. C'était douloureux pour tout le monde de passer à une nouvelle pile technologique. Bien que nous ayons choisi Python pour l'accessibilité de toute l'équipe, il y avait encore ceux parmi nous qui avaient besoin de l'apprendre. Et aucun de nos ingénieurs backend ou fullstack ne connaissait Django au départ (alors que notre ingénieur frontend principal le connaissait bien). De même sur le frontend, beaucoup d'entre nous connaissaient React, mais peu d'entre nous avaient une expérience approfondie avec TypeScript, vers lequel nous avons également choisi de migrer. Cela dit, après avoir eu un temps d'apprentissage initial, nous sommes tous devenus assez productifs assez rapidement et nous avons pu apprendre ensemble. Ce fut notre première validation : même avec moins d'expérience dans cette nouvelle pile, nous avons pu créer des fonctionnalités beaucoup plus rapidement. Il faudrait plus de temps pour s’assurer que les gains de productivité provenaient de la nouvelle pile et de la nouvelle base de code, plutôt que du fait que le projet soit entièrement nouveau, néanmoins nous y sommes finalement arrivés ».
Les leçons tirées de cette expérience
Après cette expérience, il a noté des leçons qu’ils ont tirées de leurs échecs pendant le processus, mais aussi de leurs succès :
« Nous avons réussi parce que nous avons commencé avec une vision claire de ce que nous construisions (un vrai MVP, où nous savions que l'ancien produit était «viable»), et nous y avons réduit la portée selon nos besoins afin de rester avec une vision claire. Bien que nous n'ayons pas livré « dans les temps » (d’ailleurs personne ne le fait), nous n’avons pas pris autant de temps que Netscape. La durée totale du projet était inférieure de deux fois au temps que nous avions prévu si nous avions fait une copie exacte (en termes de fonctionnalités) de l'ancien produit, mais nous nous sommes retrouvés avec quelque chose de bien meilleur et avec de nouvelles fonctionnalités très souhaitées, comme la possibilité de télécharger et d'envoyer des vidéos et la possibilité de télécharger un rapport PowerPoint généré automatiquement de votre conversation.
« L'une des autres clés de notre succès a été d'obtenir des commentaires tôt et souvent. Pendant la réécriture, nous avons utilisé le produit en interne très souvent, découvrant des bogues critiques et des problèmes de performances. Nous avons également organisé des démonstrations régulières pour l'ensemble de l'entreprise afin d'obtenir rapidement des commentaires du service customer success, ventes, recherche et éventuellement des premiers clients qui pouvaient tolérer les échecs.
Il a reconnu qu’ils se sont trompés parce qu’ils ont décidé de s’appuyer sur deux technologies que l’équipe n’avait pas beaucoup utilisées beaucoup : « nous avions déjà utilisé TypeScript dans un prototype, mais nous n'avions pas de connaissances approfondies à ce sujet. Tout s'est bien passé, mais nous ne sommes toujours pas convaincus que la productivité est plus élevée et le taux de défauts est plus faible; le temps nous le dira, et je pense que le jury est toujours sur le typage statique ».
L'autre erreur était d'utiliser GraphQL. « Nous avions des niveaux d'expérience assez élevés avec REST et Redux, mais nous n'avions auparavant utilisé GraphQL que dans un prototype. Rétrospectivement, GraphQL a rendu le prototypage initial beaucoup plus rapide, mais à long terme, car il y a des décisions de conception critiques dans Apollo avec lesquelles nous ne sommes pas d'accord (comme ne pas exposer la capacité de détecter les déconnexions / reconnexions dans les abonnements sur le frontend) et notre expérience de la mise au point des performances sur le backend était ... disons simplement que ce fut un mois ou deux difficile de ma vie que j’espère ne plus revivre ». L’équipe est en train de se séparer progressivement de GraphQL.
Réécrire ou ne pas réécrire ?
« Sur la base de mon expérience ici, vous ne devriez probablement pas le faire si vous croyez dans le battage médiatique indiquant que la réécriture n'est jamais la bonne décision. Dans tous les cas, vous devez par défaut choisir la position ‘non’, puis travailler très dur pour la justifier si nécessaire. Voici quelques scénarios où une réécriture pourrait être justifiée :
- Si votre architecture ou schéma est très loin de s’aligner avec ce dont vous avez besoin et qu'il n'y a pas de chemin de migration clair, car la mise à jour incrémentielle de l'architecture ou des schémas serait extrêmement difficile
- Si ces problèmes ralentissent considérablement votre équipe
- Si votre pile technologique actuelle empêche de nombreux ingénieurs de contribuer et les former à la pile technologique n'est pas une option.
« Même si vous cochez toutes ces cases, vous devez tenir compte des réalités commerciales et savoir si cela a du sens pour votre entreprise, votre équipe. Il peut y avoir plus de scénarios où une réécriture est justifiée. Il est difficile de le justifier, mais cela en vaut la peine et peut être couronné de succès ».
En définitive, une réécriture de code vous donne l'avantage de l'expérience : vous connaissez les faiblesses de l'ancien système, les défauts de conception, les exigences actuelles et la future feuille de route. Vous pouvez faire des planifications et concevoir un système qui surmontera les problèmes que vous avez anticipés. L'inconvénient est que vous devez maintenir deux systèmes pendant que vous écrivez le nouveau.
En revanche, le refactoring vous permet de remplacer lentement les anciens morceaux de code de votre système par de nouveaux. Prenez une fonction, une classe ou un module, et vous le/la réécrivez dans le cadre de votre projet. Tous vos tests et intégrations sont là, il est facile de vérifier que vous n'avez rien cassé, pour vous assurer que la fonctionnalité reste la même. Mais vous ne pouvez pas faire des merveilles avec le refactoring de code, vous ne pouvez pas choisir un langage de programmation différent et la plupart du temps, vous ne pouvez même pas remplacer le framework principal de votre projet.
Source : Remesh
Et vous ?
Êtes-vous pour le refactoring de code ou la réécriture d'un projet de zéro ? Dans quelle mesure ?
Que pensez-vous du cas de Remesh ?
Avez-vous déjà été amené à réécrire un code de zéro ? Pouvez-vous partager votre expérience ?
Quels sont les éléments qui pourraient pousser une équipe à envisager de réécrire son code ?
Voir aussi :
L'Allemagne a proposé en open source tout le code de Corona-Warn-App, son application de contact tracing qui va lui coûter 3 millions d'euros tous les mois
L'équipe du projet OpenZFS retire « les références inutiles à l'esclavage » de sa base de code en mettant de côté le terme « slave » qui fait remonter une expérience humaine douloureuse au souvenir
L'équipe du langage Go retire les termes "whitelist", "blacklist", "master" et "slave" de sa documentation et de sa base de code parce qu'ils véhiculent des stéréotypes raciaux
A-t-on besoin d'apprendre la programmation pendant 10 ans avant d'être un développeur accompli ? Partagez votre expérience