Introduction : pourquoi la refactorisation du code legacy vers TypeScript est une nécessité
Travailler avec du code legacy est l'une des tâches les plus fréquentes et complexes pour un développeur. Souvent, ce code est écrit en JavaScript pur sans typage strict, contient des dépendances complexes et manque de structure modulaire. Passer à TypeScript ne se limite pas à ajouter des types — c'est une amélioration fondamentale de la qualité du code qui réduit le nombre de bugs, simplifie la refactorisation et accélère l'intégration des nouveaux développeurs.
Dans cet article, nous allons détailler le processus étape par étape de la refactorisation du code legacy vers TypeScript. Vous apprendrez comment introduire les types en toute sécurité, décomposer les fonctions monolithiques et tester les modifications.
Étape 1. Analyse et préparation du code legacy
Avant d'écrire du code, il est nécessaire de comprendre l'architecture actuelle. Sans cela, la refactorisation deviendra chaotique.
Créez une carte des dépendances
Commencez par analyser les fichiers et les modules. Identifiez :
- Quelles fonctions sont "pures" (pure) et lesquelles ont des effets de bord.
- Où se trouvent les variables globales et les états.
- Quelles parties du code sont dupliquées.
Écrivez des tests pour les parties critiques
Une refactorisation sans tests, c'est jouer à la roulette russe. Écrivez au moins des tests unitaires pour les fonctions clés. Exemple de test en TypeScript :
// Code legacy d'origine (JavaScript)function calculatePrice(items) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i].price * items[i].quantity; } return total;}
// Test en TypeScript (Jest)describe('calculatePrice', () => { it('should calculate total price correctly', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculatePrice(items)).toBe(35); });});Si les tests passent — vous pouvez commencer la refactorisation.
Étape 2. Introduction progressive du typage strict
N'essayez pas de tout couvrir avec des types d'un coup. Utilisez une stratégie "ascendante" : commencez par les fonctions les plus simples et isolées.
Typage des fonctions simples
Ajoutez des types pour les paramètres et les valeurs de retour. Exemple :
// Avant (JavaScript)function greet(name) { return "Hello, " + name;}
// Après (TypeScript)function greet(name: string): string { return `Hello, ${name}`;}Travail avec les objets et les interfaces
Pour les structures de données complexes, créez des interfaces. Cela rend le code auto-documenté :
// Définition de l'interface pour un élément de commandeinterface OrderItem { id: number; name: string; price: number; quantity: number;}
// Fonction avec un paramètre typéfunction getTotalPrice(items: OrderItem[]): number { return items.reduce((sum, item) => sum + item.price * item.quantity, 0);}Utilisez `any` uniquement comme solution temporaire
Le type any est l'ennemi du typage strict. Si vous ne connaissez pas le type exact, utilisez unknown et vérifiez-le à l'exécution :
function safeParse(data: unknown): string { if (typeof data === 'string') { return data.trim(); } return '';}Étape 3. Division des fonctions et modules monolithiques
Le code legacy contient souvent des "fonctions divines" (god functions) qui font tout à la fois. Décomposez-les en petits modules réutilisables.
Extraction de la logique pure
Exemple : une fonction qui valide un email et envoie une requête. Séparons-la :
// Avant (monolithique)function processUser(email) { if (!email || !email.includes('@')) { throw new Error('Invalid email'); } // ... envoi de la requête console.log('Sending request...');}
// Après (séparation des responsabilités)function validateEmail(email: string): boolean { return email