Découper un fichier utils avec des limites de modules itératives et claires
Découpez un fichier utils sans réécriture risquée : extrayez de petits modules étape par étape, définissez des limites claires et conservez des changements sûrs à livrer.

Pourquoi un fichier utils devient un problème
Un seul fichier utils commence généralement avec de bonnes intentions : mettre de petits helpers au même endroit. Puis les délais arrivent, l'équipe change, et tout ce qui n'a pas de place évidente finit dans utils. Avec le temps, « découper un fichier utils » cesse d'être une simple tâche de nettoyage et devient un obstacle pour des changements sûrs.
Le problème central, c'est la propriété. Quand tout vit au même endroit, rien ne semble appartenir à quelqu'un. Les gens ajoutent « juste un helper de plus » sans réfléchir au nom, aux dépendances, ou à savoir s'il appartient à une partie spécifique de l'app. Bientôt, un changement qui devrait être simple devient un jeu de devinettes : qui utilise ceci, et qu'est-ce qui va casser si je touche ?
Les symptômes typiques apparaissent vite :
- Responsabilités sans rapport mélangées (formatage, appels API, auth, base de données, ajustements UI)
- Imports circulaires parce que de nombreuses parties de l'app dépendent du même fichier
- Fonctions « minuscules » avec des effets de bord cachés (lecture de vars d'env, écriture en storage, mutation d'état global)
- Tests difficiles à écrire car importer un helper entraîne la moitié de l'app
- Logique sensible à la sécurité dispersée dans des helpers aléatoires (parsing de token, règles de mot de passe, gestion des secrets)
Le but n'est pas l'esthétique. C'est des changements plus sûrs, des tests plus simples et des limites claires pour pouvoir mettre à jour une zone sans surprendre une autre.
Fixez les attentes dès le départ : il s'agit d'un nettoyage itératif, pas d'une réécriture. Vous déplacez le code par petites étapes tout en conservant le même comportement.
Comment savoir que votre fichier utils est devenu une fourre-tout
Un fichier utils devient une fourre-tout quand il se transforme silencieusement en dépendance pour tout le reste. Le plus grand signe d'alerte n'est pas le nombre de lignes. C'est qu'un « petit » changement de helper peut casser la moitié de l'app.
Surveillez ces signaux :
- Il mélange des tâches sans rapport comme vérifications d'auth, requêtes DB, formatage et appels réseau.
- Les helpers importent des modules spécifiques à l'app (modèles, routes, contrôleurs) au lieu de rester génériques.
- Les fonctions ont des effets de bord mais semblent inoffensives d'après leur nom.
- Vous éditez utils pour livrer des fonctionnalités sans rapport avec des utilitaires.
- Il crée une friction constante : conflits de merge et « pourquoi ça a cassé ça ? ».
La taille n'est qu'un symptôme. Le couplage est le vrai problème. Quand un fichier connaît les règles d'auth, le schéma de données et le client HTTP, des dépendances cachées s'accumulent. Chaque petit changement exige des tests plus larges.
Les helpers purs comme capitalize(), clamp() ou toSlug() sont corrects à garder comme utilitaires. Si une fonction dépend de la base de données, de l'état d'auth ou de secrets, ce n'est pas un utilitaire. C'est un module qui attend d'exister.
Choisir des limites de modules claires avant de déplacer du code
Une limite de module est une promesse simple : tout ce qui est ici concerne un type de tâche, avec des entrées et sorties prévisibles. Vous n'avez pas besoin d'un design parfait. Vous avez besoin que le prochain geste soit évident et sûr.
Commencez par des catégories que les gens comprennent déjà. Pour beaucoup d'apps, ces bacs couvrent la plupart du désordre :
- strings
- dates
- validation
- api
- auth
La frontière la plus importante est entre les helpers purs et les helpers avec effets de bord.
Les helpers purs sont prévisibles : même entrée, même sortie, aucun effet externe. Ils sont les plus faciles à déplacer en premier car vous pouvez les tester avec des exemples simples.
Les helpers à effets de bord touchent le monde extérieur : variables d'environnement, local storage, cookies, temps, hasard, appels réseau, bases de données et état global de l'app. Ils peuvent aussi logger, muter des objets, ou lancer des erreurs dont d'autres parties dépendent. Traitez-les comme à risque élevé et regroupez-les par responsabilité (par exemple, ne mélangez pas auth et comportement du client API dans le même module).
Gardez une nomenclature cohérente, même si elle est ennuyeuse. Décidez tôt si vous voulez des dossiers ou des fichiers plats, et respectez une convention simple pour que les reviewers repèrent les modules « divers » avant qu'ils ne grossissent.
Faire une carte rapide de ce qu'il y a à l'intérieur (sans trop planifier)
Avant de scinder un fichier utils, obtenez une image rapide et honnête de ce qu'il contient. Vous n'avez pas besoin d'une documentation parfaite. Vous avez besoin d'assez de clarté pour déplacer le code sans deviner.
Commencez par un inventaire des exports. Listez chaque fonction exportée et où elle est importée. Si un helper est utilisé à 30 endroits, il demande plus d'attention. S'il est utilisé une seule fois, c'est un excellent candidat pour commencer.
Un inventaire léger suffit généralement :
- Nom de la fonction et une description en une ligne
- Où elle est importée (quelques appelants principaux)
- Effets de bord (env, storage, logging, réseau)
- Où elle devrait vivre (date, auth, formatage, db)
- Niveau de risque : faible (pur), moyen (config), élevé (à état ou sécurité)
Une fois la liste en place, cherchez le premier groupe à extraire : des fonctions pures qui ne lisent pas l'état global et n'effectuent pas d'I/O. Les déplacer en premier vous donne des gains rapides et un modèle propre à répéter.
Étape par étape : extraire des modules de façon itérative et à faible risque
La manière la plus sûre de scinder un fichier utils est de progresser par petites tranches faciles à relire et à annuler.
Choisissez un thème (dates, formatage, validation, helpers d'auth) et traitez-le comme pilote. Vous visez une routine répétable, pas une architecture parfaite.
Une boucle d'extraction à faible risque
- Créez un nouveau fichier module pour le thème choisi.
- Déplacez seulement 1 à 3 fonctions étroitement liées. Si quelque chose semble à moitié lié, laissez-le pour plus tard.
- Gardez les exportations stables en réexportant temporairement les fonctions déplacées depuis le fichier utils d'origine.
- Mettez à jour les imports dans une petite zone de l'app (une page, une fonctionnalité, un service). Committez.
- Supprimez les réexportations temporaires uniquement après que la plupart des imports ont été migrés.
Cela garde chaque commit petit : déplacez quelques fonctions, mettez à jour quelques imports, et stoppez.
Exemple concret
Si utils.ts inclut formatMoney, parseCurrency, roundToCents, plus des helpers sans rapport comme slugify, sleep, et fetchWithRetry, commencez par l'argent. Créez utils/money.ts, déplacez les fonctions money, et réexportez-les depuis utils.ts pour que rien ne casse. Puis migrez les imports dans le flux de checkout en premier, et étendez ensuite.
Conserver le même comportement lors du refactor
Le plus grand risque n'est pas de déplacer du code. C'est les modifications « pendant qu'on est là » qui changent silencieusement les résultats. Traitez ça comme déplacer des cartons : étiquetez, transportez, placez. Ne redécorez pas en plein déménagement.
Commencez par les fonctions pures. Avant d'en déplacer une, écrivez un petit test qui verrouille le comportement actuel, même s'il est étrange. Ce qui est connu mais bizarre vaut mieux que propre mais différent.
Les helpers de formatage sont adaptés aux tables d'entrées-sorties simples. Choisissez quelques cas représentatifs, incluant des cas limites comme valeurs vides, espaces en trop, et caractères non-ASCII.
Si vous n'avez pas d'infrastructure de tests, utilisez des contrôles runtime rapides au lieu de deviner. Une assertion temporaire ou un court log autour d'un point d'appel peut confirmer que les sorties n'ont pas changé après le déplacement. Retirez-le une fois que vous êtes confiant.
Un piège courant : « améliorer » formatPrice(amount) pendant le déplacement. Si ce helper impacte des factures ou des emails, un changement d'arrondi ou de symbole peut créer des totaux discordants ou de la confusion client. Geler la sortie, la déplacer, puis planifier les améliorations séparément est la bonne approche.
Gérer prudemment les helpers à effets de bord et sensibles à la sécurité
Les parties les plus risquées d'un fichier utils ne sont généralement pas les helpers de chaînes. Ce sont les fonctions qui touchent le monde extérieur : appels réseau, stockage, bases, temps, IDs aléatoires, et variables d'environnement.
Séparez les helpers purs du code à effets de bord. Les mélanger conduit à des bugs comme des appels API dupliqués, des headers manquants, ou des données sauvegardées au mauvais endroit.
Mettez le code à effets de bord dans des modules au nom explicite. Par exemple : auth (lecture/écriture de token, refresh, logout), configuration du client API (base URL, retries, headers), wrappers de stockage, et accès aux env.
Pour garder les appelants stables pendant les déplacements, introduisez de petites interfaces. Au lieu d'appeler localStorage.getItem('token') partout, créez un tokenStore avec getToken() et setToken(). Plus tard, vous pourrez changer la façon de stocker les tokens sans éditer la moitié de l'app.
Considérez ces helpers comme à haut risque même s'ils paraissent petits :
- Gestion des tokens (parsing JWT, refresh, vérifications d'expiration)
- Secrets (clés API, tokens de service, URLs privées)
- Logique de mot de passe (hash, comparaisons, flows de reset)
- Assainissement d'entrée et construction de requêtes
Si vous hésitez sur un déplacement, gardez l'ancienne fonction comme wrapper fin appelant le nouveau module, et retirez-la seulement après livraison en sécurité.
Pièges communs qui transforment un refactor en réécriture
Les refactors explosent quand les changements deviennent trop larges pour être raisonnés. L'objectif est des extractions petites et fiables.
Schémas d'échec courants :
- Mouvements en big-bang : déplacer des dizaines de helpers à la fois tue la capacité d'isoler des régressions.
- Mélanger nettoyage et changements de comportement : renommer et retoucher la logique dans le même commit cache les régressions.
- Créer un nouveau dépotoir :
shared/oucommon/trop tôt devient souvent utils v2. - Casser les imports sans plan de migration : préférez une période de pont avec réexportations ou alias.
- Dépendances cachées : des helpers qui lisent des vars d'env ou des singletons globaux peuvent casser à cause d'un ordre d'initialisation changé.
Un exemple simple : formatDate() semble inoffensif, mais dépend d'un paramètre global de locale et d'une var d'env timezone. Après l'avoir déplacé, les tests locaux passent mais la prod utilise une env différente, et maintenant les reçus affichent le mauvais jour.
Deux garde-fous aident : garder chaque extraction assez petite pour être relue en quelques minutes, et garder le comportement identique jusqu'à ce que la frontière soit stable.
Contrôles rapides avant de livrer chaque extraction
Traitez chaque extraction comme une petite release.
Assurez-vous que le nouveau module a un seul rôle et une surface d'API réduite. Si vous ne pouvez pas décrire ce qu'il fait en une phrase, il fait encore trop de choses. Soyez aussi délibéré sur les exportations. Beaucoup de helpers étaient « publics par accident » quand ils vivaient dans un gros fichier.
Une check-list pré-merge courte :
- But clair : nom et exports correspondent à un seul domaine
- Exports intentionnels : n'exportez que ce dont les appelants ont besoin
- Pas d'importations circulaires
- Aucun secret tiré dans les bundles client
- Build sans warnings suggérant des chemins dupliqués ou des imports morts
Ensuite faites un petit test smoke sur un cas réel. Même avec des tests unitaires, il vaut la peine de vérifier qu'un flux principal fonctionne toujours (auth, action principale de création/sauvegarde, et une page qui appelle l'API et rend des données réelles).
Exemple : scinder un fichier utils mixte dans une vraie app
Une startup a un utils.ts unique qui a commencé petit, puis a grossi jusqu'à 900 lignes. Il contient des helpers d'auth (stockage de token, vérifs de session), des appels API (fetchWithAuth), et du formatage UI (dates, monnaie, noms d'affichage). Des bugs apparaissent à des endroits étranges parce que tout importe tout.
Ils gardent l'avancement des features en extrayant de petits modules dans un ordre sûr :
- Déplacer d'abord les helpers de formatage purs.
- Extraire ensuite le wrapper du client API et garder les anciennes exportations comme wrappers fins.
- Séparer l'auth en modules token et session distincts, isolant tout ce qui touche au stockage.
- Terminer par une passe de nettoyage : supprimer les réexportations, renommer les fonctions peu claires, et supprimer les helpers morts dont vous pouvez prouver l'absence d'utilisation.
Ce qui s'améliore rapidement est pratique : moins de régressions, propriété claire (les changements d'auth n'affectent pas le formatage), revue de sécurité plus facile, et onboarding plus rapide car les fichiers correspondent aux concepts réels de l'app.
Prochaines étapes : transformer le plan en progrès régulier
Le moyen le plus rapide de scinder un fichier utils est de le traiter comme une série de petits mouvements sûrs.
Un plan simple sur une semaine :
- Jour 1 : Choisir une frontière (date/temps, strings, monnaie, validation) et déplacer 3 à 5 fonctions pures. Ajouter des tests rapides.
- Jour 2 : Déplacer les 3 à 5 suivantes dans la même frontière.
- Jour 3 : Extraire un module à effets de bord (storage, cookies, wrappers fetch). Garder des wrappers fins.
- Jour 4 : Migrer les imports dans quelques fichiers. Ajouter une règle pour décourager les nouveaux imports depuis l'ancien fourre-tout.
- Jour 5 : Nettoyage : renommer les modules, ajouter des commentaires courts, et documenter ce qui reste dans l'ancien fichier.
Arrêtez quand le fichier utils restant est majoritairement des wrappers de compatibilité et de la colle vraiment partagée, pas l'endroit où tout finit par atterrir.
Si vous avez hérité d'une app générée par IA et que utils semble posséder tout le produit, FixMyMess (fixmymess.ai) peut aider en diagnostiquant la base de code, en isolant les helpers risqués (auth, secrets, builders de requêtes sujets à injection), et en transformant la séparation en une séquence sûre et livrable au lieu d'une réécriture.
Questions Fréquentes
How do I know my utils file is actually a problem and not just “big”?
Si le fichier mélange des tâches sans rapport et qu'une petite modification casse beaucoup de fonctionnalités, c'est déjà un fourre-tout. Le vrai signal, c'est le couplage : des helpers qui importent des modules spécifiques à l'app, des effets de bord cachés, ou des fonctions utilisées partout.
Le nombre de lignes importe moins que le nombre de parties de l'app qui en dépendent.
What module boundaries should I pick before I start moving code?
Visez des bacs « une seule responsabilité » simples qui correspondent à la façon dont les gens pensent l'app, comme strings, dates, validation, api et auth. Séparez les helpers purs (pas d'I/O, pas d'état global) des helpers à effets de bord (stockage, env, réseau, base de données).
Si vous ne pouvez pas décrire un module en une phrase, la frontière est encore trop floue.
What should I move out of utils first?
Commencez par les helpers purs et à faible risque, faciles à tester et qui ne lisent pas l'état global. Priorisez aussi les helpers avec peu de points d'appel, car vous pouvez les migrer rapidement.
Laissez les pièces à haut risque comme l'auth, la gestion des tokens, l'accès aux variables d'environnement et la construction de requêtes pour plus tard, une fois que vous avez un schéma d'extraction sûr.
How do I refactor without accidentally changing behavior?
Ne changez pas le comportement en déplaçant le code. Écrivez un petit test (ou un contrôle runtime rapide) qui verrouille la sortie actuelle, même si elle est un peu étrange.
Traitez le déplacement comme un déménagement : déplacez d'abord, améliorez ensuite dans un changement séparé et explicite.
What’s the safest step-by-step way to split a utils file?
Créez le nouveau module, déplacez 1–3 fonctions liées, et gardez les anciennes exportations fonctionnelles en réexportant temporairement depuis le fichier utils original. Ensuite, migrez les imports dans une petite partie de l'app et validez.
Quand la plupart des sites d'appel sont mis à jour, retirez les réexportations temporaires et répétez avec le prochain lot.
How do I avoid circular imports when I extract modules?
Les imports circulaires signifient souvent que le fichier « utils » n'est pas vraiment utilitaire ; il fait du travail métier réel et dépend des internals de l'app. Séparez par responsabilité et gardez les dépendances à sens unique.
Si nécessaire, créez un petit module bas niveau (helpers purs) que les modules de plus haut niveau peuvent importer, plutôt que tout le monde s'importe mutuellement.
Which utils functions are the most security-sensitive?
Tout ce qui touche aux tokens, secrets, mots de passe, variables d'environnement, stockage ou construction de requêtes doit être traité comme hautement risqué. Déplacez-les dans des modules clairement nommés (comme auth, env, storage, db) au lieu de les laisser dispersés parmi des helpers inoffensifs.
Assurez-vous aussi que le code qui lit des secrets ne se retrouve pas par erreur dans des bundles côté client.
Should I wrap things like localStorage, cookies, and env access?
Oui, mais faites-le intentionnellement. Créez un petit wrapper comme tokenStore.getToken() et tokenStore.setToken() pour que le reste de l'app n'appelle pas localStorage ou les cookies directement.
Cela rendra les changements futurs plus sûrs et réduira les risques d'un traitement incohérent des tokens dans l'app.
How do I migrate imports without breaking everything?
Privilégiez une période de transition courte : gardez les anciens imports fonctionnels via des réexportations ou des wrappers fins pendant que vous migrez progressivement les sites d'appel. Gardez chaque extraction assez petite pour qu'un réviseur puisse la comprendre rapidement.
Évitez les mouvements massifs ou les commits mixtes qui renommant, déplacent et changent la logique en même temps.
When should I get help breaking up a messy utils file in an AI-generated app?
Quand vous héritez d'un code généré par IA, un gros fichier utils cache souvent des flows d'auth cassés, des secrets exposés et des effets secondaires imprévisibles qui rendent chaque changement risqué. Si vous devez séparer rapidement sans en faire une réécriture, FixMyMess (fixmymess.ai) peut aider en réalisant un audit gratuit du code, en isolant les helpers risqués (auth, client API, stockage, builders de requêtes) et en transformant la séparation en une séquence sûre et livrable, validée par des humains.
La plupart des projets peuvent être stabilisés en 48–72 heures une fois que les helpers les plus risqués sont identifiés et séparés proprement.