Introduction : pourquoi le refactoring du code legacy est une nécessité
Chaque développeur est tôt ou tard confronté à du code legacy — obsolète, mal documenté et souvent illisible. Dans le monde de JavaScript et Node.js, c'est particulièrement pertinent : les projets grandissent rapidement, les technologies changent, et les anciennes parties du code se transforment en « marécage » qui ralentit le développement. Le refactoring du code legacy n'est pas un simple caprice, mais une nécessité pour maintenir la performance, la sécurité et l'évolutivité de l'application.
Dans cet article, nous allons voir comment aborder le refactoring du code legacy en JavaScript (Node.js) de manière sécurisée et efficace. Vous découvrirez les étapes clés : analyse, découpage en modules, remplacement des constructions obsolètes et tests. Tous les exemples sont écrits en JavaScript moderne (ES6+) et destinés à l'environnement Node.js.
Étape 1 : Analyse et compréhension du code legacy
Avant de modifier le code, il faut le comprendre. Le code legacy est souvent écrit dans un style « spaghetti » : des fonctions de centaines de lignes, des variables globales et un mélange de logiques. Commencez par un audit.
1.1. Créez une carte des dépendances
Utilisez des outils comme madge ou dependency-cruiser pour visualiser les liens entre les modules. Cela aidera à identifier les dépendances cycliques et les « objets divins ».
// Exemple : installation de madge
npm install -g madge
madge --image graph.svg src/
1.2. Identifiez les « zones à risque »
Trouvez les parties du code qui changent le plus souvent ou qui provoquent des erreurs. Utilisez l'historique des commits dans Git :
git log --oneline --follow src/legacyModule.js
Faites également attention à :
- Les fonctions de plus de 50 lignes
- Les variables globales (par exemple,
varsanslet/const) - Le callback hell (appels asynchrones imbriqués)
1.3. Écrivez des tests pour le comportement existant
Sans tests, le refactoring est une bombe à retardement. Utilisez jest ou mocha pour couvrir les fonctions clés. Les tests doivent vérifier le comportement actuel, pas le comportement « idéal ».
// Exemple de test pour une fonction legacy
const sum = require('./legacyModule').sum;
test('sum adds two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
Étape 2 : Amélioration progressive de la structure
N'essayez pas de tout réécrire d'un coup. Utilisez la stratégie « Strangler Fig » — remplacez les parties du code progressivement, en conservant la rétrocompatibilité.
2.1. Divisez le monolithe en modules
Isolez les blocs logiquement liés dans des fichiers séparés. Par exemple, extrayez le travail avec la base de données dans db.js et le traitement des requêtes dans handlers.js.
// Avant : un seul fichier de 500 lignes
function handleRequest(req, res) {
// 100 lignes de logique
// 50 lignes de travail avec la BDD
// 30 lignes de formatage de réponse
}
// Après : découpage en modules
// db.js
exports.getUser = (id) => { /* ... */ };
// handlers.js
const db = require('./db');
exports.handleRequest = (req, res) => {
const user = db.getUser(req.params.id);
// formatage de la réponse
};
2.2. Remplacez var par let/const
C'est une étape simple mais importante. var a une portée fonctionnelle, ce qui entraîne souvent des bugs. Utilisez let pour les variables et const pour les constantes.
// Avant
var x = 10;
if (true) {
var x = 20; // redéfinit la variable externe
}
// Après
let x = 10;
if (true) {
let x = 20; // variable locale
}
Étape 3 : Modernisation du code asynchrone
Le code legacy sur Node.js souffre souvent du « callback hell ». Remplacez les callbacks imbriqués par des promesses ou async/await.
3.1. Transformation des callbacks en promesses
Utilisez util.promisify pour les fonctions qui suivent le style Node.js (premier argument — l'erreur).
const fs = require('fs');
const { promisi