Le blog

Ce qu'il faut retenir pour réussir sa migration de code

Publié le 09 mai 2023

La migration du code d’une application peut rapidement devenir une tâche complexe et stressante, mais avec une planification minutieuse et une bonne compréhension des étapes nécessaires, elle peut se dérouler en douceur. 

Que vous soyez débutant·e ou plus expérimenté·e, nous vous proposons dans cet article quelques conseils pratiques pour réussir votre migration. Nous aborderons les problématiques les plus courantes à éviter, les meilleures pratiques à appliquer ainsi que les outils et techniques que vous pouvez utiliser pour faciliter ce processus. 

Pourquoi faire une migration ? #

Versions en fin de vie

La plupart des langages et frameworks sont découpés en versions avec des cycles de vie. Même si certaines versions peuvent être maintenues plus longtemps, on les appelle les LTS pour long-term support, elles finissent par être placées en fin de vie (on parle d’EOL pour end-of-life). Concrètement le code n’évoluera plus, il n’est pas maintenu, si une faille de sécurité est découverte, elle ne sera pas corrigée. On s’expose dès lors à des risques potentiels. Quelques exemple de cycle de vie : 

Problème de sécurité

Pour continuer sur les risques, il arrive que des failles de sécurité soient découvertes. Même si le danger peut être mitigé si la faille provient d’un composant inutilisé sur le projet, il est préférable de migrer vers la version corrigeant le problème de sécurité. S’agissant des outils populaires, les failles devraient être systématiquement déclarées en tant que CVE (Common Vulnerabilities and Exposures), afin d’être connues de tous. Des listes de vulnérabilités existent sur différents sites web mais regroupant l’ensemble des logiciels, langages ou paquets, il est souvent préférable de cibler les canaux des outils. Un exemple avec Symfony, une catégorie spécifique existe sur le blog pour communiquer sur les failles découvertes : https://symfony.com/blog/category/security-advisories

Évolutions

Une dernière raison non négligeable est l’ajout d’évolutions. Aussi bien pour les solutions propriétaires que pour celles en open-source, il est commun d’améliorer le produit avec des fonctionnalités répondant à des besoins d’utilisateurs. D’autres évolutions visent à améliorer les performances ou bien encore simplifier l’expérience en tant que développeur. En somme, les outils et les langages s’améliorent au rythme des versions, la migration devient une étape pour améliorer également son projet. Quelques exemples avec les ajouts dans Next.js directement sur leur blog et sur notre blog avec les ajouts à API Platform sur sa dernière version.

Problèmes d’une migration #

Rupture de rétro-compatibilité

Un système, une bibliothèque, un framework peut être amené à rompre le contrat de rétro-compatibilité, si par exemple un défaut de conception est détecté et nécessite une ré-écriture complète. Pour les dépendances qui utilisent le versionnage sémantique, il est simple de détecter ce genre de changements. Sinon, il faudra se reposer sur les notes de versions, quand elles existent. L’ultime solution, la plus fastidieuse également, étant la lecture du code source.

Le code de l’application devra être adapté pour convenir aux évolutions du langage ou des concepts introduits par les nouvelles versions. Plus le code de l’application va s’écarter des bonnes pratiques, des recommandations ou de l’utilisation nominale d’une dépendance, plus la migration va être compliquée. Il va falloir d’abord étudier comment le code s’est écarté de la dépendance, quelle était l’intention initiale et essayer de concilier la nouvelle implémentation et le besoin.

Les dépendances abandonnées

Il arrive que des dépendances soient abandonnées au cours du temps. Que ce soit pour des raisons techniques (obsolescence, par exemple) ou pour d'autres raisons (disparition du seul mainteneur, par exemple), le problème est le même, le code n’est plus compatible avec la version suivante du langage ou framework. Il faut trouver une solution de substitution. Plusieurs choix existent mais, suivant le cas de figure, l’un d’eux conviendra certainement le mieux.

L’utilisation d’une nouvelle dépendance est souvent celle qui paraît la plus intuitive. On pense à un remplacement d’un pour un. Même si les deux paquets solutionnent le même problème, ils ne le font peut-être pas de la même façon. Ainsi ce sont peut-être des nouveaux contrats d’interface, objets ou fonctions, voire un paradigme d’implémentation complètement différent, qui seront mis à disposition du développeur. Le degré de couplage de votre code avec ce sous-système ainsi que les différences entre les deux alternatives vous donneront une idée de la complexité de mise en œuvre.

Une autre approche peut consister à faire migrer le code de la dépendance vous-même, avec la responsabilité qui vous incombe immédiatement de devoir maintenir ce code qui fait partie maintenant de votre projet. Une solution plus ouverte est celle de créer un fork de la bibliothèque et de la publier afin de partager votre travail avec le reste de la communauté.

L’enfer des dépendances (dependency hell)

En PHP, le gestionnaire de dépendances Composer ne permet pas d’avoir plusieurs versions d’un même paquet. Généralement, cela ne pose que peu de problèmes, il existera au moins une version qui satisfasse les différents pré-requis. Cependant, lors d’une migration, des problèmes peuvent survenir lors de la résolution des dépendances secondaires, suivant la réactivité des paquets principaux à migrer vers une nouvelle version de langage ou framework. Pour continuer avec le PHP, le site packagist.org liste les pré-requis des dépendances et pour chacune des versions, Github est aussi une bonne source d’informations pour suivre une éventuelle migration. Pour résoudre ces problèmes, on retombe sur les solutions précédentes : contribuer à l’évolution du paquet, le remplacer par un autre ou reprendre dans votre base de code les fonctions requises.

Les fonctionnalités en cours de développement

Lors d’une montée de version de langage, tout le code de l’application doit être revu et migré. Quelle que soit l’approche, il paraît complexe de devoir travailler sur deux fronts en même temps, les tâches techniques de migration de code et les tâches fonctionnelles d’évolution ou de maintenance applicative. Si du code continue à être ajouté en marge de la migration, il faudra également migrer ce code. Même si certaines tâches de migration peuvent être automatisées, la migration risque de s’étirer dans le temps.

Une solution idéale techniquement est de geler la partie fonctionnelle tant que la partie technique n’est pas terminée. Cependant, il faut pouvoir anticiper une période pendant laquelle aucune modification ne pourra être apportée, ou alors en risquant un décalage de la migration technique. Toutefois, le processus peut être assoupli pour s'autoriser à appliquer des correctifs urgents (correctif de bugs bloquants, ou correctifs de sécurité).

Quelques conseils #

Préparer

Une étude préliminaire doit être réalisée afin de définir une feuille de route. On ne rattrape pas deux ou trois versions majeures sur un langage et des frameworks en une seule itération. Au-delà de la quantité de travail nécessaire, il sera alors compliqué de déterminer la cause racine d'un problème. Il faut identifier des jalons intermédiaires et valider pour chacun d’entre eux, l’ensemble de l’application par des tests qu'ils soient automatisés ou non. Pour chacune de ses étapes, une liste de tâche doit être établie en déterminant ce qui peut être isolé, ou à l’inverse, ce qui va bloquer la suite du travail. En somme, la même méthodologie employée lors du développement d’une fonctionnalité. L’étude des guides de montée de versions, des informations de mise à jour ou de la documentation permettra de préciser le plan de migration.

Automatiser

Que vous procédiez par itérations successives ou pas, l’automatisation va permettre de gagner du temps sur une partie de la migration ou aussi de vous en laisser pour vous concentrer sur les difficultés. Si vous disposez déjà d’une intégration continue avec des outils d’analyse, vous devrez certainement mettre à jour ces outils, voire paramétrer le niveau d’analyse ou de sévérité.

Un outil de migration de code automatique comme Rector en PHP, est un réel avantage. L’outil va analyser le code de l’application et lui appliquer des règles pour le modifier. Il existe des ensembles de règles pour chacune des versions de PHP et de Symfony (mais également d’autres règles). Un premier essai vous donnera une idée de la puissance de ce genre d’outils mais aussi de ses limites: certaines règles peuvent être superflues ou non-nécessaires, des modifications manuelles pourraient être tout de même nécessaires…

Si vous n’en disposez pas déjà, il est temps d’ajouter des analyseurs statiques (comme PHPStan ou Psalm) pour valider la syntaxe du code et détecter également des problèmes formels.

L’étape suivante est de valider le fonctionnement du code par des tests unitaires (ex: PHPUnit, Jest), fonctionnels (ex : PHPUnit ou Behat) ou de plus haut niveau comme des tests de bout en bout (ex: Symfony Panther, Cypress, Playwright…). Si vous n’avez pas d’intégration continue, c’est le moment opportun d’en mettre une en place. L’intégration continue est un processus se déclenchant à chaque ajout de code au dépôt centralisé. Ce processus est composé d’étapes, elles-mêmes composées en unité de travail, chacune permettant d’exécuter une commande, script, traitement…

Une exécution locale sur une machine peut aussi convenir mais atteindra rapidement ses limites notamment en temps et charge d’exécution.

Mesurer

Que ce soit pour comparer les performances avant/après migration ou plus globalement s’assurer de ne pas dégrader l’expérience à chaque nouvelle livraison, des tests spécifiques existent. Beaucoup d’outils différents sont utilisés pour effectuer ces tests. Souvent avec des approches dogmatiques propres à chacun, il convient d’établir des indicateurs de performances à observer techniquement mais aussi fonctionnellement. Les indicateurs donnent les métriques à récupérer et à agréger. Des seuils pourront être fixés éventuellement pour déclencher des alertes. Même si ces tests doivent être effectués avant la mise en production, ils sont nécessaires aussi après la livraison. Pour une application web publique, la réalité peut être différente des simulations. On peut citer parmi les nombreux outils existants : 

  • Lighthouse, disponible comme dans le navigateur mais intégrable également dans une CI, qui permet de réaliser un audit avec différentes métriques jugées pertinentes par Google. Sitespeed.io qui propose également de mesurer la performance du site avec une approche différente.
  • Gatling ou Octoperf plus orienté sur les tests de charge en simulant des connexions simultanées, permettant de déterminer le comportement de votre site en cas de forte affluence.
  • Toutes les solutions d’observabilité qui viennent en complément des outils précédents comme New Relic, Dynatrace, Datadog… 
Améliorer en continu

Maintenant que votre migration est terminée, vous devez faire le choix de ne pas recréer une dette technique. Et même si vous n’êtes pas parvenus à terminer le plan initial, l’équipe dispose dorénavant d’une méthodologie éprouvée. S’il n’était pas déjà présent, l’outillage de mesure et de tests est aussi un gain énorme pour le projet. À chaque version fonctionnelle ou itération, vous pouvez inclure des mises à jour de vos dépendances ou intercaler quelques périodes de migrations dans le planning global de votre projet. Vous êtes capables d’estimer la charge de travail et les risques associés, ce qui aidera à la priorisation de ce sujet.

Aller plus loin #

En espérant que cet article vous a permis d’appréhender votre migration de code plus facilement, il arrive cependant qu’il soit impossible de migrer le code. Se pose alors la question de la migration de l’application : développer une nouvelle application en partant de rien, utiliser une autre application (déjà existante) en étendant ses fonctionnalités… D’autres questions et problèmes avec d’autres réponses et solutions. Nos consultant·e·s et expert·e·s peuvent intervenir pour auditer votre application existante, vous aider avec votre projet de migration, pour encore améliorer vos procédés de travail. Nos services couvrent un large éventail de technologies. Contactez-nous !

Le blog

Pour aller plus loin