Éliminer le code copié-collé en toute sécurité avec des utilitaires partagés
Éliminez le code copié-collé en toute sécurité grâce à un plan de refactorisation par petites étapes qui déduplique les fonctions générées par l'IA, conserve un comportement identique et vérifie chaque changement.

Ce que le « code copié-collé » casse avec le temps
Le code copié-collé, c’est quand la même logique apparaît à plusieurs endroits avec de petites modifications : variable renommée, valeur par défaut différente, message d’erreur légèrement modifié. Au début, ça paraît plus rapide que d’écrire une fonction réutilisable. Quelques mois plus tard, c’est un piège.
Dans les projets réels, ça ressemble souvent à un même helper de requête répété dans trois fichiers, cinq versions de « formater une date », ou deux contrôles d’auth quasi identiques qui ne sont pas d’accord sur ce que signifie « connecté ». Le code fonctionne jusqu’à ce qu’une copie soit corrigée et que les autres ne le soient pas.
Les applications générées par l’IA ont tendance à dupliquer la logique parce que le modèle résout le problème qu’on lui présente à chaque fois. Demandez une nouvelle page et il peut recréer des helpers au lieu de réutiliser ceux qui existent. Les outils qui génèrent du code par rafales (page par page, fonctionnalité par fonctionnalité) favorisent encore plus la répétition. Vous vous retrouvez avec une base de code pleine de quasi-clones qui ont l’air cohérents, mais ne le sont pas.
La duplication augmente les bugs et ralentit les changements de façon simple : chaque modification devient une chasse au trésor multi-fichiers. On en rate une copie et on livre un comportement incohérent. Les différences sont aussi faciles à manquer en revue car elles sont souvent minimes.
Quand on dit « comportement identique », on veut généralement dire « les utilisateurs ne doivent rien remarquer ». Ça inclut la sortie évidente, mais aussi des détails qu’on oublie :
- Les mêmes entrées produisent les mêmes sorties, y compris pour les cas limites.
- Les mêmes erreurs surviennent dans les mêmes situations, avec les mêmes messages ou codes.
- Les mêmes effets de bord ont lieu (logs, cache, retries, écritures en base).
- Le comportement sensible au temps reste dans les limites attendues.
Si vous supprimez le code copié-collé en toute sécurité, le véritable objectif n’est pas « du code plus propre ». C’est « pas de surprises en production ».
Choisir une bonne première cible de déduplication
Commencez par une petite zone facile à observer et difficile à mal interpréter. De bons premiers cibles sont des motifs de helpers comme la validation d’entrée, le formatage des dates ou d’argent, des vérifications d’auth répétées, ou le même wrapper d’appel API utilisé sur plusieurs écrans.
Choisissez quelque chose dont le code fait principalement une seule chose et que vous pouvez décrire en une phrase. Si vous avez besoin de trois paragraphes pour expliquer ce que ça fait, ce n’est pas une première cible.
Avant de toucher quoi que ce soit, définissez les limites. Écrivez ce qui entre (inputs), ce qui sort (outputs) et ce que ça affecte d’autre (effets de bord comme la journalisation, la mise en cache, la lecture de vars d’environnement, ou la mutation d’un objet partagé). Les fonctions générées par l’IA cachent souvent des effets de bord dans de petits endroits, comme des valeurs par défaut « utiles » ou l’absorption silencieuse d’erreurs.
Un bon candidat a généralement :
- Apparaît à 3+ endroits avec des lignes majoritairement identiques
- A des entrées et sorties claires (peu de lectures globales)
- Échoue d’une manière visible (erreur, code de statut, message)
- N’est pas sur votre chemin le plus critique et fragile (paiements, cœur d’auth, migrations)
- A des cas limites que vous pouvez lister sans deviner
Ensuite, inventorie les duplicatas. Ne vous fiez pas seulement aux résultats de recherche. Ouvrez chaque fichier et confirmez que c’est vraiment le même travail, pas seulement du code qui se ressemble.
Enfin, décidez de ce qui ne doit pas changer. Soyez précis : types d’erreurs exacts, messages exacts (si les utilisateurs ou les tests en dépendent), gestion des valeurs vides, et tout comportement « étrange » pour les cas limites. C’est là que se cachent les surprises : une copie tronque les espaces, l’autre non, et soudain la connexion échoue pour un petit groupe d’utilisateurs.
Verrouiller le comportement actuel avant tout changement
Vous avez besoin d’une preuve de ce que le code fait aujourd’hui, y compris les parties bizarres que vous souhaiteriez qu’il n’ait pas. Sans cette baseline, un « simple nettoyage » peut modifier silencieusement des sorties, messages d’erreur ou cas limites dont dépendent les appelants.
Commencez par capturer des exemples réels d’entrées et de sorties. Prenez une poignée d’exemples depuis les logs, les tickets de support ou vos propres tests manuels. Si vous n’avez pas de logs, lancez l’app et enregistrez quelques requêtes réelles ou parcours utilisateur (ce que vous avez entré, ce que vous avez vu, et la réponse du système).
Notez aussi les échecs attendus. Beaucoup de helpers générés par l’IA diffèrent surtout dans la façon dont ils échouent : l’un retourne un objet vide, un autre lance une exception, un troisième renvoie un 200 avec une chaîne d’erreur. Ces différences importent lorsque d’autres parties de l’app ont été construites autour d’elles.
Créez un petit ensemble de scénarios « dorés » que vous pouvez relancer après chaque petite modification. Gardez-le court et réaliste :
- Une entrée courante qui doit réussir
- Une entrée limite (vide, chaîne longue, zéro, champ manquant)
- Une mauvaise entrée connue qui doit produire une erreur spécifique
- Un scénario où des flags optionnels ou des en-têtes changent le comportement
- Un cas sensible à la performance (payload volumineux ou boucle répétée)
Notez aussi les dépendances cachées qui peuvent rendre le comportement incohérent entre exécutions : variables d’environnement, feature flags, heure actuelle et fuseaux horaires, IDs aléatoires, caches globaux et appels réseau.
Exemple : si trois helpers de requête dupliqués ajoutent tous un header d’auth, vérifiez s’ils diffèrent quand le token est manquant (lance une erreur vs retourne null) et d’où ils lisent le token (global, localStorage, var d’environnement). C’est ce comportement que vous devez préserver lors de la déduplication.
Cartographier les duplicatas et leurs petites différences
Le code copié-collé ne correspond rarement parfaitement. Avant de fusionner quoi que ce soit, établissez une carte claire de ce qui est partagé et de ce qui a changé subtilement entre les versions.
Placez les fonctions dupliquées côte à côte et comparez-les ligne par ligne. Ne survolez pas. Le code généré par l’IA change souvent un petit détail (une valeur par défaut, un nom d’en-tête, un contrôle null manquant) qui n’apparaît qu’en production.
La plupart des différences tombent dans quelques catégories :
- Nommage et forme (noms de paramètres, champs retournés, messages d’erreur)
- Valeurs par défaut (timeouts, retries, chaîne vide vs null, valeurs de secours)
- Gestion des cas limites (champs manquants, réponses 204, vars d’environnement indéfinies)
- Effets de bord (logs, métriques, cache, écritures de stockage)
- Formatage entrée/sortie (trim, encodage, parsing de dates)
Une fois les différences listées, choisissez une version comme baseline. “Baseline” ne veut pas dire « la mieux écrite ». Ça veut dire « celle sur laquelle le reste de l’app s’appuie le plus », souvent celle utilisée au plus d’endroits ou celle qui correspond à l’expérience utilisateur actuelle en production.
Puis décidez quelles différences sont intentionnelles vs accidentelles. Si une différence est documentée, testée ou clairement requise par un appelant spécifique, traitez-la comme intentionnelle. Si elle ressemble à une variation aléatoire (timeout par défaut différent sans raison, mapping d’erreur incohérent), supposez qu’elle est accidentelle jusqu’à preuve du contraire.
Exemple concret : deux helpers de requête ajoutent un header Authorization, mais un seul envoie aussi les cookies par défaut. Cette différence d’une ligne peut modifier qui est « connecté » en production.
Concevoir un utilitaire partagé qui reste simple
L’utilitaire partagé doit sembler presque ennuyeux. La version 1 ne vise pas une « meilleure conception ». Elle reproduit le même comportement en un seul endroit.
Garder une petite surface d’exposition
Commencez par la plus petite unité qui se répète. Si cinq fonctions normalisent les mêmes entrées ou construisent le même payload, extrayez seulement cette partie. Laissez les règles de validation, les retries et les cas spéciaux là où ils sont jusqu’à ce que vous ayez prouvé qu’ils sont vraiment partagés.
Un petit utilitaire est plus facile à revoir car il y a moins de façons de changer le comportement par accident. Signes que vous l’avez gardé petit :
- Le nom décrit une seule action.
- Il prend des entrées explicites et retourne une valeur.
- Il ne connaît pas les routes, écrans ou tables de base.
- Il évite des valeurs par défaut cachées qui devinent ce que vous vouliez.
Préférer des paramètres explicites aux globals
Le code généré par l’IA fouille souvent les globals, les variables d’environnement ou des singletons partagés. Ça rend la déduplication risquée parce que chaque appelant peut dépendre d’un état caché légèrement différent.
Au lieu de cela, passez ce dont l’utilitaire a besoin en paramètres. Par exemple, passez baseUrl, headers ou timezone. Ça peut sembler répétitif au début, mais ça rend les différences visibles et rend l’utilitaire honnête.
Garder les effets de bord chez l’appelant
Si le code dupliqué logge, écrit en BDD ou déclenche de l’analytics, conservez ces effets de bord en dehors de l’utilitaire partagé quand c’est possible. Visez « données en entrée → sortie ». L’appelant décide de logger, stocker ou absorber une erreur.
Exemple : si trois endpoints construisent un message d’erreur et le loggent, extrayez seulement buildErrorMessage(details). Chaque endpoint garde sa propre journalisation pour éviter de changer par erreur le volume ou le timing des logs.
Étapes pas à pas : petites modifications vérifiées
Traitez cela comme une série d’échanges petits, pas comme une réécriture massive. Vous voulez le comportement d’aujourd’hui, juste déplacé en un seul endroit.
-
Choisissez un duplicata « source de vérité ».
-
Copiez exactement cette implémentation dans un nouveau fichier utilitaire partagé. Ne nettoyez pas encore. Pas de renommages, pas de formatage, pas de « tant que j’y suis ».
-
Migrer par petites étapes :
- Créez l’utilitaire partagé en copiant une fonction existante telle quelle, et laissez les anciens duplicatas en place.
- Mettez à jour un seul point d’appel pour utiliser le nouvel utilitaire.
- Lancez vos scénarios dorés et comparez sorties, logs et effets de bord.
- Répétez : déplacez un autre point d’appel, relancez les mêmes vérifications.
- Après que chaque point d’appel utilise l’utilitaire, supprimez les anciens duplicatas et enlevez les imports inutilisés.
Entre chaque étape, conservez un commit propre et relisible. Si quelque chose casse, vous pouvez revertir un petit changement au lieu de chercher dans un diff géant.
Si les points d’appel ont des formes d’arguments légèrement différentes, utilisez un wrapper de compatibilité temporaire. Gardez la conversion sale à la périphérie (près du point d’appel), pas à l’intérieur de l’utilitaire partagé.
Comment vérifier que le comportement reste identique
La façon la plus sûre de prouver que vous n’avez rien changé est de garder les deux chemins en place pendant un court moment et de les comparer avec les mêmes entrées.
Sauvegardez 10 à 20 entrées réelles que vous pouvez rejouer (fixtures provenant des logs sont idéales). Exécutez-les dans l’ordre sur l’ancienne fonction et sur le nouvel utilitaire, et comparez les résultats en incluant forme et types, pas seulement les valeurs.
N’arrêtez pas au happy path. Beaucoup de régressions n’apparaissent que quand quelque chose échoue. Comparez :
- Messages d’erreur et types d’erreur (y compris le libellé si votre UI l’affiche)
- Codes de statut pour les réponses API (200 vs 204 vs 404 compte)
- Gestion des vide, null et champs manquants ("" vs null vs undefined)
- Ordre des éléments (surtout si vous triez ou mappez des clés)
- Effets de bord comme logging, retries ou caching
Si les différences sont difficiles à repérer, ajoutez un toggle de debug temporaire qui exécute les deux chemins et imprime un diff compact quand ils divergent. Quand la migration est terminée, retirez le toggle.
Enfin, surveillez la performance sur les chemins courants. Chronométrez quelques appels typiques avant et après et confirmez que vous n’avez pas ajouté de requêtes BD supplémentaires, de parsings JSON répétés ou d’appels réseau inutiles.
Pièges courants qui changent le comportement sans qu’on le remarque
La plupart des refactors échouent pour une raison ennuyeuse : vous avez changé deux choses à la fois. Gardez comme objectif « même sortie pour la même entrée », pas « code plus joli ».
Ces pièges reviennent souvent :
- Changer des valeurs par défaut sans s’en rendre compte (
timeout = 0vstimeout = 30, ouundefinedtraité différemment denull) - Perdre la coercition de type ("0" chaîne devenant nombre
0, champ manquant devenant chaîne vide) - « Nettoyer » la gestion des erreurs d’une façon qui masque les échecs (remplacer une erreur lancée par un warning loggé)
- Construire un unique utilitaire qui fait tout (il gonfle avec des flags, des cas spéciaux et de l’état caché)
- Migrer 9 points d’appel et oublier le 10ᵉ (souvent un job en arrière-plan, un écran admin ou un endpoint peu utilisé)
Exemple : deux helpers de requête peuvent relancer sur erreurs 500, mais un seul relance aussi sur 429 rate limit. Si vous les fusionnez sans préserver cette exception, un flux de checkout peut devenir plus lent ou commencer à échouer sous pics de trafic.
Checklist rapide avant de merger le refactor
Avant de merger, vous voulez enlever la duplication sans changer ce que fait l’app.
Vérifications de conception de l’utilitaire
- Le nouvel helper fait un job clair. S’il en fait deux, scindez-le tant qu’il est encore petit.
- Les entrées et sorties sont prévisibles. Évitez les valeurs par défaut magiques dépendant d’un état global.
- Le nom et les paramètres correspondent aux termes utilisés par l’équipe.
- Il ne lit pas de variables d’environnement, fichiers, contexte de requête ou session utilisateur en interne. Passez les valeurs en paramètres.
- Les erreurs sont gérées comme avant (type, forme du message, code de statut si pertinent).
Si vous doutez qu’un comportement a changé, comparez avec ce que produisait l’ancien code, pas avec ce que vous souhaiteriez qu’il produise.
Vérifications de préparation au merge
- Tous les points d’appel sont migrés. Aucun fichier à moitié déplacé n’utilise encore un duplicata.
- Les anciennes copies sont supprimées (ou clairement marquées pour suppression immédiate) pour qu’elles ne puissent pas dériver.
- Les scénarios dorés passent, incluant au moins un chemin de défaillance (mauvaise entrée, champ manquant, timeout).
- Les logs, métriques et messages d’erreur n’ont pas été rendus plus bruyants ou plus silencieux au point de compliquer le debug.
- Un rapide scan du diff montre qu’aucun secret ou token n’a été accidentellement déplacé dans l’utilitaire partagé.
Scénario d’exemple : dédupliquer des helpers de requête générés par l’IA
Vous héritez d’une app où un outil IA a produit trois helpers similaires : getJson(), postJson(), et requestWithRetry(). Chacun « fonctionne », mais chaque endpoint appelle un helper légèrement différent, et des bugs n’apparaissent qu’en production.
En les comparant, vous voyez des différences importantes : headers (Bearer vs clé API, casse), timeouts (5 seconds vs 5000 ms), règles de retry (réseau seulement vs aussi 503), et mapping d’erreur (retourne { ok: false } vs lance vs wrappe dans message).
Au lieu d’imposer une version « meilleure », créez un constructeur de requêtes partagé avec des entrées explicites, par ex. makeRequest({ method, url, headers, timeoutMs, retryPolicy, mapError }). L’important est que les valeurs par défaut doivent correspondre au comportement actuel du premier endpoint que vous migrez, pas à ce que vous souhaiteriez.
Migrez un endpoint d’abord, de préférence un simple qui touche encore l’auth et la gestion d’erreur. Gardez l’ancien helper en place pour tout le reste.
Vos scénarios dorés attrapent vite les changements subtils. Exemple : un endpoint qui retournait 204 No Content renvoyait auparavant null, mais le nouvel utilitaire essaie d’appeler json() et lance une erreur. Le scénario échoue immédiatement et vous corrigez en gérant explicitement le 204.
Étapes suivantes si la base de code est trop embrouillée
Parfois, vous ne pouvez pas dédupliquer en toute sécurité car les duplicatas cachent des problèmes plus profonds. Si chaque fonction « similaire » a des effets de bord différents, une gestion d’erreur différente ou des corrections silencieuses, un utilitaire partagé peut casser des flux réels.
Une pause aide, mais elle n’exige pas une réécriture complète. L’objectif est un petit reset qui rend les refactors par petites étapes à nouveau possibles.
Signes qu’il faut durcir la sécurité pendant la refactorisation
Si vous touchez du code partagé, traitez comme non négociables ces vérifications :
- Authentification incohérente (certaines routes vérifient, d’autres oublient)
- Secrets exposés (clés API dans le code, les logs ou des bundles côté client)
- Entrée utilisateur utilisée dans SQL ou requêtes sans validation stricte (risque d’injection)
- Logique « admin » dépendant d’un flag front-end ou d’un contrôle de rôle faible
- Messages d’erreur qui fuguent des détails internes (stack traces, noms de tables)
Corriger des duplicatas sans traiter ces points peut propager un pattern risqué dans votre nouvel utilitaire.
Garder l’élan sans réécrire toute l’app
Visez d’abord une couche de stabilisation mince : un petit ensemble d’helpers partagés avec des règles strictes, plus des tests ou snapshots autour des flux les plus fréquentés. Ensuite, supprimez les duplicatas par grappes (tous les helpers de requête, puis tous les contrôles d’auth).
Si un module est vraiment trop sale, isolez-le derrière une interface simple et reportez le nettoyage interne jusqu’à ce que le reste de l’app soit stable.
Si vous avez hérité d’un prototype IA cassé généré par des outils comme Lovable, Bolt, v0, Cursor ou Replit, un audit externe peut vous faire gagner des jours. FixMyMess (fixmymess.ai) commence par un audit gratuit du code pour repérer duplications, différences cachées et problèmes de sécurité, puis aide à transformer le prototype en logiciel prêt pour la production sans changer ce dont les utilisateurs dépendent.
Questions Fréquentes
What exactly counts as “copy-paste code”?
Le code copié-collé, c’est la même logique dupliquée dans plusieurs fichiers avec de petites différences faciles à manquer. Ça fonctionne souvent au début, mais avec le temps des corrections apparaissent dans une copie et pas dans les autres, si bien que le comportement diverge et des bugs surviennent.
Why do AI-generated apps end up with so many duplicates?
Parce que le modèle a tendance à résoudre le problème à l’instant où on le lui demande, il recrée souvent des helpers au lieu de réutiliser ce qui existe déjà. Quand les fonctionnalités sont générées page par page, on obtient des quasi-clones qui paraissent cohérents mais gèrent différemment les cas limites, les valeurs par défaut ou les erreurs.
What’s the best first thing to deduplicate?
Commencez par quelque chose de petit, visible et facile à décrire en une phrase, comme la validation d’entrée, le formatage, un helper de requête API ou un contrôle d’authentification répété. Évitez d’abord les chemins les plus fragiles afin d’apprendre le modèle de refactorisation avec un risque réduit.
What should I document before I touch any duplicated code?
Notez les entrées, les sorties et les effets de bord de chaque duplicata avant de toucher quoi que ce soit. Incluez les détails qu’on oublie souvent : messages d’erreur exacts, codes de statut, journalisation, mise en cache et la façon dont les valeurs vides sont traitées.
What are “golden scenarios,” and how many do I need?
Ce sont un petit ensemble de scénarios réels que vous pouvez relancer après chaque petite modification pour prouver que le comportement est resté identique. Gardez-les pratiques : un succès courant, un cas limite, une défaillance connue avec une erreur précise, et un cas où des flags, en-têtes ou variables d’environnement changent le comportement.
How do I choose the “baseline” duplicate to merge into a shared utility?
Choisissez la version sur laquelle le reste de l’application s’appuie le plus, pas celle qui est la mieux écrite. Si un helper est utilisé à plus d’endroits ou correspond à ce que les utilisateurs voient en production, considérez-le comme le comportement à préserver en priorité.
What’s the safest way to migrate call sites to the new utility?
Avancez par petites étapes : copiez la fonction de référence dans un fichier partagé sans la nettoyer, migrez un seul point d’appel, et relancez vos scénarios dorés. Des commits petits et réversibles sont la meilleure défense contre les changements de comportement accidentels.
Why do refactors often break error handling even when output “looks the same”?
Parce que les refactorings modifient souvent le comportement en cas d’échec sans que personne ne s’en aperçoive, notamment entre erreurs lancées et objets retournés, ou à propos des réponses 204/vides. Votre nouvel utilitaire partagé doit préserver exactement les types d’erreurs, messages et traitements des valeurs vides dont dépendent les appelants.
Where should logging, retries, and other side effects live after deduplication?
Conservez les effets de bord comme la journalisation, les tentatives automatiques, les écritures de stockage et l’analytics dans l’appelant autant que possible, pour que l’utilitaire partagé reste prévisible. Cela évite des surprises comme doubler le volume des logs ou modifier le moment des retries.
When should I stop refactoring and get an outside audit or help?
Si les duplicatas cachent des problèmes de sécurité comme des contrôles d’authentification incohérents, des secrets exposés ou une validation d’entrée insuffisante, la déduplication peut propager ces risques dans la fonction partagée. Dans ces cas, arrêtez et réalisez un audit externe ou durcissez la sécurité avant de continuer.
When should I stop refactoring and get an outside audit or help?
FixMyMess (fixmymess.ai) peut démarrer par un audit gratuit du code pour cartographier les duplications, les différences de comportement et les risques de sécurité, puis réparer ou reconcevoir rapidement le code généré par l’IA, la plupart des projets étant terminés en 48–72 heures.