Moderniser du code legacy avec Claude Code sans casser la production
Workflow pratique pour moderniser du code legacy avec Claude Code: tests, TypeScript, risques, exemples exécutables et vérification.
La modernisation échoue quand elle commence trop large
Un code legacy n’est pas seulement un code ancien. C’est un code dont le comportement est difficile à prouver, avec peu de tests, des règles métier implicites et un risque élevé sur les commandes, la facturation, le support ou la publication. Claude Code est très utile dans ce contexte, mais il ne doit pas devenir un bouton de réécriture massive.
Le workflow sûr commence par l’observation, continue par des tests de caractérisation, puis avance par petites refactorisations. Un test de caractérisation capture le comportement actuel avant de décider si ce comportement est idéal. Le harness est l’échafaudage qui permet à l’agent de travailler: tests, permissions, commandes, règles du projet et checklist de revue.
La documentation officielle Claude Code common workflows couvre l’exploration d’une base de code, le refactoring, les tests, les PR et les sessions parallèles avec worktrees. Cette approche convient très bien au legacy: Claude Code accélère l’exécution, mais l’équipe garde le contrôle du risque et de l’acceptation.
Trois cas d’usage concrets
Ne modernisez pas parce qu’un fichier est laid. Commencez par les endroits où une meilleure maîtrise réduit un vrai risque métier.
| Cas d’usage | Objectif | Ce que Claude Code peut faire | Ce que l’humain doit vérifier |
|---|---|---|---|
| Commandes, facturation, paiement | Éviter les erreurs de montant et de statut client | Cartographier le comportement, ajouter des tests, trouver les limites | Taxes, remises, arrondis, conformité |
| Migration JavaScript vers TypeScript | Rendre les changements futurs plus sûrs | Ajouter des types, réduire any, corriger les erreurs par lots | Compatibilité de l’API publique |
| Callbacks ou fonctions géantes | Améliorer la maintenabilité sans changer le comportement | Séparer les responsabilités, proposer des noms, expliquer les diffs | Erreurs, retries, effets secondaires |
Chez ClaudeCodeLab, j’utilise le même schéma pour les scripts de publication, le code de checkout et les anciennes transformations de contenu. Le point important n’est pas que Claude Code écrive vite. Le point important est que chaque changement reste assez petit pour être relu et relié à une preuve.
flowchart LR
A[Explorer] --> B[Figer le comportement par tests]
B --> C[Refactoriser par petites étapes]
C --> D[Ajouter des types et traiter les dépendances]
D --> E[Revue humaine des risques]
E --> B
Commencer par un audit en lecture seule
Le premier prompt doit interdire les modifications. À ce moment, vous voulez une carte du système, pas un patch produit avec un contexte incomplet.
Lis @src/legacy et @test.
Ne modifie aucun fichier pour l'instant.
Retourne cet audit:
1. Fichiers principaux et responsabilités
2. I/O externes, base de données, API, écritures de fichiers et effets de bord
3. Comportements qui doivent rester compatibles
4. Tests manquants et branches à risque
5. Ordre le plus sûr pour de petits changements
Si une règle est floue, écris "confirmation humaine nécessaire" au lieu de deviner.
La page officielle How Claude Code works explique que Claude Code peut lire les fichiers, exécuter des commandes et modifier le code. C’est puissant, mais dans un vieux système il faut limiter la première phase à la compréhension. Une correction qui semble idiomatique peut changer une réponse dont dépend déjà un client.
Exemple minimal exécutable
L’exemple suivant est volontairement petit, mais il respecte l’ordre utilisé dans un vrai chantier legacy.
mkdir legacy-modernization-demo
cd legacy-modernization-demo
npm init -y
npm install -D vitest typescript @types/node
npm pkg set type="module"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.typecheck="tsc --noEmit"
mkdir -p src/legacy test
Voici l’ancien processeur de commandes. Il mélange validation, calcul et construction de réponse. C’est le type de code qui fonctionne depuis longtemps et devient donc intimidant à modifier.
// src/legacy/orderProcessor.js
export function processOrder(order) {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return { status: "error", message: "items is required" };
}
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.qty;
}, 0);
const discount = order.customer?.type === "vip" ? subtotal * 0.1 : 0;
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Ajoutez ensuite des tests qui verrouillent le comportement actuel. Ils ne redessinent pas le domaine; ils protègent les contrats existants.
// test/orderProcessor.test.ts
import { describe, expect, it } from "vitest";
import { processOrder } from "../src/legacy/orderProcessor.js";
describe("processOrder legacy behavior", () => {
it("calculates total for a regular customer", () => {
const result = processOrder({
items: [
{ id: "A1", qty: 2, price: 1000 },
{ id: "B2", qty: 1, price: 500 }
],
customer: { id: "C1", type: "regular" }
});
expect(result).toMatchObject({
status: "confirmed",
total: 2500,
discount: 0
});
});
it("applies a 10 percent VIP discount", () => {
const result = processOrder({
items: [{ id: "A1", qty: 1, price: 10000 }],
customer: { id: "C2", type: "vip" }
});
expect(result.status).toBe("confirmed");
expect(result.total).toBe(9000);
expect(result.discount).toBe(1000);
});
it("returns an error when items are empty", () => {
const result = processOrder({
items: [],
customer: { id: "C3", type: "regular" }
});
expect(result.status).toBe("error");
expect(result.message).toContain("items");
});
});
Exécutez npm test. Quand les tests passent, seulement alors demandez une modification.
Lis @src/legacy/orderProcessor.js et @test/orderProcessor.test.ts.
Migre ce code vers TypeScript en gardant les tests au vert.
Règles:
- Garde le nom public processOrder
- Préserve status, total, discount et message
- Ajoute les types d'abord, puis sépare les responsabilités
- Exécute npm test et npm run typecheck
- Explique quelle compatibilité chaque diff préserve
Une structure TypeScript plus facile à relire
Après modernisation, les types, la validation, les calculs et l’orchestration sont séparés. L’objectif n’est pas une abstraction élégante, mais une logique financière lisible et vérifiable.
// src/orderTypes.ts
export type CustomerType = "regular" | "vip";
export type OrderItem = {
id: string;
qty: number;
price: number;
};
export type OrderInput = {
items: OrderItem[];
customer: {
id: string;
type: CustomerType;
};
};
export type OrderResult =
| {
status: "confirmed";
total: number;
items: OrderItem[];
discount: number;
}
| {
status: "error";
message: string;
};
// src/validators.ts
import type { OrderInput } from "./orderTypes";
export function validateOrder(order: OrderInput | null | undefined): string | null {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return "items is required";
}
return null;
}
// src/calculators.ts
import type { CustomerType, OrderItem } from "./orderTypes";
export function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
export function calculateDiscount(subtotal: number, customerType: CustomerType): number {
return customerType === "vip" ? subtotal * 0.1 : 0;
}
// src/orderProcessor.ts
import { calculateDiscount, calculateSubtotal } from "./calculators";
import type { OrderInput, OrderResult } from "./orderTypes";
import { validateOrder } from "./validators";
export function processOrder(order: OrderInput): OrderResult {
const validationMessage = validateOrder(order);
if (validationMessage) {
return { status: "error", message: validationMessage };
}
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer.type);
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Changez l’import du test vers ../src/orderProcessor, puis relancez npm test et npm run typecheck. Une diff de cette taille reste relisible. Si le même PR déplace des dossiers, met à jour des dépendances majeures, change le formatage et renomme des concepts métier, la revue perd en qualité.
Traiter les dépendances séparément
Une erreur courante consiste à mélanger refactoring et mises à jour majeures. Quand les tests cassent, il devient impossible de savoir si la cause vient de TypeScript, d’une API modifiée, du bundler ou de la logique métier.
Demandez d’abord un inventaire.
Lis package.json et le lockfile.
Ne mets rien à jour pour l'instant.
Retourne un tableau avec:
- package
- version actuelle
- version cible recommandée
- présence d'un major upgrade
- URL du guide officiel de migration
- fichiers probablement affectés
- tests à ajouter avant la mise à jour
Pour les opérations larges ou destructrices, gardez un modèle de permissions prudent. La documentation Claude Code permissions est utile avant d’autoriser migrations, suppressions ou déploiements. La vitesse d’un agent ne vaut rien si elle supprime l’étape d’approbation qui protégeait le système.
Pièges concrets
Le premier piège est de refactoriser sans tests. Un code plus propre reste une régression si un arrondi, une remise ou un message d’erreur change.
Le deuxième est de transformer une suggestion de Claude Code en règle métier. Une réponse plus idiomatique n’est pas forcément compatible avec les clients existants.
Le troisième est le PR géant. Migration de types, découpage logique, dépendances, déplacements de fichiers et formatage doivent être séparés quand c’est possible.
Le quatrième est d’améliorer trop vite la gestion d’erreurs. Dans un vieux système, un null, une chaîne précise ou un statut HTTP étrange peut être un contrat.
Le cinquième est de reporter la documentation. Demandez à Claude Code de rédiger les notes de compatibilité, les étapes de vérification manuelle et le plan de rollback dans la description du PR.
Revue, liens et CTA
Combinez ce workflow avec le guide d’automatisation du refactoring, TDD avec Claude Code et le guide de génération de documentation. Pour un usage en équipe, documentez zones interdites, commandes de test, vocabulaire métier et règles de revue dans les bonnes pratiques CLAUDE.md.
ClaudeCodeLab aide les équipes à introduire Claude Code dans des produits existants avec formation, modèles CLAUDE.md et conseil en modernisation. Le but n’est pas une réécriture par IA, mais un workflow répétable où tests, permissions et revue humaine rendent le travail de l’agent acceptable.
Résultat vérifié
J’ai testé ce flux dans legacy-modernization-demo: processeur JavaScript legacy, trois comportements verrouillés avec Vitest, migration vers TypeScript, puis npm test et npm run typecheck. Le gain principal venait de la séparation entre audit en lecture seule et prompt d’édition. Les diffs étaient plus petits, et il était plus facile de vérifier le total, la remise VIP et l’erreur de commande vide.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.