Accueil / Articles / Comment de petites décisions s’accumulent dans des bases de code React à long terme

Comment de petites décisions s’accumulent dans des bases de code React à long terme

Quinze habitudes de maintenance pour les applications React qui durent des années : code lisible, composants ciblés, état limité aux besoins, dépendances réduites, tests et surveillance.

2206 mots

Démarrer un projet React est la partie facile. La véritable épreuve survient des mois ou des années plus tard, lorsque l’application compte davantage d’utilisateurs, de fonctionnalités, de contributeurs et de dépendances, et que quelques solutions de fortune « temporaires » sont devenues silencieusement essentielles. À ce stade, React lui-même est rarement le problème ; le vrai défi réside à maintenir la base de code compréhensible alors que tout autour continue de changer. Ce guide présente quinze habitudes, classées selon les domaines où coûtent réellement les efforts de maintenance, afin que vous puissiez identifier à temps les décisions qui s’accumulent avant de transformer la base de code en quelque chose que personne ne souhaite modifier.

Code écrit pour le lecteur suivant

Préférer un code évident à un code astucieux

Des lignes de code compactes, des abstractions profondes ainsi que des outils conçus pour des dizaines de cas hypothétiques semblent productifs au moment où on les écrit. Six mois plus tard, ils deviennent un casse-tête, souvent pour la personne même qui les a écrits et qui ne se souvient plus pourquoi.

L’alternative consiste à utiliser un code délibérément simple. Pensez à afficher une interface qui peut être en chargement, avoir échoué ou être prête. Les retours précoces rendent chaque état explicite, une condition à la fois :

if (isLoading) {
  return <LoadingState />;
}

La branche d’erreur suit la même structure, et le chemin réussi vient en dernier :

if (error) {
  return <ErrorState />;
}
return <Dashboard />;

Rien ici n’est impressionnant, et c’est justement le but : n’importe qui peut en quelques secondes voir quel composant s’affiche et quand. Dans une application complexe, la vitesse à laquelle le prochain développeur comprend votre code est bien plus importante que de vouloir impressionner celui qui travaille actuellement dessus.

Les noms sont une documentation qui ne prend jamais de valeur obsolète

Lorsque vous écrivez du code, choisir un nom semble être un détail insignifiant, mais il devient extrêmement important lorsque vous le déboguez six mois plus tard. Comparez la recherche dans la base de code pour ceci :

handleData()

à la recherche pour ceci :

calculateMonthlyRevenue()

Le deuxième nom vous indique ce que la fonction calcule avant même d’ouvrir le fichier. Les grandes bases de code sont essentiellement des canaux de communication entre les développeurs ; un nom précis exprime l’intention, tandis qu’un nom vague transforme chaque lecteur en détective.

Faites attention aux noms génériques qui apparaissent sous la pression des délais :

data
item
temp
helper
value
thing

Oui, thing se retrouve bien dans des bases de code réelles. Une règle pratique : si un nom conviendrait tout aussi bien dans n’importe quel fichier du projet, il ne dit probablement pas suffisamment de choses à son sujet.

Limites des composants et de l’état

Les composants trop volumineux deviennent coûteux en ressources

Presque tous les projets React à long terme finissent par développer un composant comptant quatre chiffres de lignes. Ils ne commencent rarement ainsi : ils partent d’environ 150 lignes, puis intègrent un modal, des filtres, la récupération de données, des vérifications de permissions, puis un deuxième modal. Finalement, en ouvrant le fichier, on trouve quelque chose comme ceci :

Dashboard.tsx
1,247 lines

Les composants de cette taille sont difficiles à comprendre, à tester, à réutiliser et à déboguer, et il est risqué de les modifier car toute modification peut affecter l’état dont dépendent d’autres parties du fichier. Divisez-les avant que ce ne devienne nécessaire. Plutôt qu’un seul fichier :

Dashboard.tsx

divisez l’écran en parties, chacune ayant une fonction bien définie :

DashboardHeader.tsx
DashboardStats.tsx
DashboardFilters.tsx
RecentActivity.tsx
DashboardTable.tsx

Chaque fichier a désormais une seule responsabilité et un rayon d’impact beaucoup plus restreint. Un critère pratique : si vous ne pouvez pas décrire un composant en une phrase sans utiliser plusieurs « et », divisez-le.

Reutilisez des modèles que vous avez déjà vus, pas ceux que vous imaginez

Les composants réutilisables sont une bonne intuition, mais il est facile d’en abuser. Un mode d’échec courant consiste à utiliser un seul bouton qui tente de remplacer tous les boutons du produit :

<UniversalButton
  type="primary"
  variant="rounded"
  size="medium"
  iconPosition="left"
  loadingStyle="spinner"
/>

Chaque nouvelle propriété semble inoffensive, mais ensemble elles créent un espace combinatoire de variantes que personne ne teste pleinement, et le composant « réutilisable » devient finalement plus difficile à utiliser que trois petits boutons spécifiques. La réutilisation a de la valeur ; une généralisation prématurée, non.

Extraitez une abstraction commune uniquement lorsque vous voyez un modèle se répéter réellement, et non parce qu’une page future en aurait besoin. Si ce besoin se concrétise, vous conçurez l’abstraction à partir d’exemples réels plutôt que de suppositions.

Rangez l’état selon son emplacement

La gestion de l’état a tendance à se dégrader silencieusement. Une petite application commence avec l’état des composants :

useState()

puis ajoute un état partagé via le contexte :

useContext()

et finalement, le projet contient à la fois Redux, plusieurs contextes, un état local, un état URL et un état serveur, sans réponse claire quant à savoir lequel contrôle la barre latérale. Chaque outil était raisonnable lorsqu’il a été ajouté ; ce qui manque, c’est une règle indiquant ce qui doit aller où.

Une façon simple de rétablir l’ordre est de classer les états en fonction de leur nature. L’état UI local couvre des éléments tels que ceux-ci :

modal open
dropdown selected
input value

Il doit rester à l’intérieur du composant qui l’utilise. L’état serveur est des données qui se trouvent sur le backend et ne sont que mises en cache dans le navigateur :

users
products
analytics

Pour cette catégorie, utilisez une bibliothèque conçue pour récupérer, mettre en cache et révalider des données distantes plutôt que de copier manuellement les réponses dans un stockage global. Si votre équipe envisage ce changement, les avantages et inconvénients y sont abordés dans notre comparaison de React Query et Redux pour l’état serveur. Enfin, les états véritablement globaux sont peu nombreux :

theme
authenticated user
app-wide preferences

Rendez ce dernier groupe minimal : n’importe quel composant peut lire ou modifier l’état global, donc moins d’états signifie moins de bugs qui semblent apparaître sans raison. Les filtres, les onglets et la pagination devraient souvent se trouver dans l’URL, où ils survivent aux rechargements et peuvent être partagés.

Structure et dépendances

Faites en sorte que la structure des dossiers soit prévisible

Imaginez rejoindre un projet et trouver ceci en racine :

src/
components/
shared/
common/
helpers/
utils/
services/
core/
misc/
new/
new2/

Où placer un nouveau composant de profil utilisateur ? Personne ne peut le dire, si bien que chaque développeur choisit différemment et la structure continue de changer. Des catégories superposées telles que shared, common, helpers et utils montrent que personne n’a défini ce que chacune signifie.

Un agencement basé sur les fonctionnalités élimine la plupart de ces ambiguïtés en regroupant le code selon la partie du produit qu’il dessert :

features/
  auth/
  dashboard/
  users/
  billing/

Dans chaque fonctionnalité, un petit ensemble de sous-dossiers répétitifs permet de maintenir une structure familière :

components/
hooks/
services/
types/

Désormais, toute personne travaillant sur la facturation sait où se trouve le code correspondant, et la réécriture d’une fonctionnalité ne concerne qu’un seul dossier au lieu de dix. D’autres structures sont également possibles (voir notre comparaison des structures de dossiers React) ; ce qui compte, c’est que la règle soit prévisible et écrite noir sur blanc.

Considérez chaque dépendance comme une maintenance future

Ajouter un paquet ne nécessite qu’une seule commande :

npm install something-cool

Les conséquences apparaissent plus tard : les paquets sont abandonnés, ils introduisent des changements disruptifs, exposent des problèmes de sécurité et alourdissez le bundle, obligeant votre équipe à continuer d’actualiser du code que personne n’a lu.

Ainsi, avant d’installer un logiciel, demandez-vous s’il est vraiment nécessaire. Un paquet qui permet d’économiser beaucoup de temps ou qui résout un problème réellement difficile, comme la gestion des dates tenant compte des fuseaux horaires, mérite sa place. Un paquet qui se contente de capitaliser une chaîne de caractères, en revanche, ne le mérite pas.

Systèmes de sécurité qui évoluent avec l’application

Testez les flux qui subiraient le plus de dommages en cas de panne

Dans une petite application, parcourir les écrans avant la publication semble suffisant. Dans une grande application, modifier une seule fonction peut perturber le système de facturation pour des raisons inexplicables, et c’est là que les tests automatisés se révèlent utiles : ils vous permettent de modifier du code que vous n’avez pas écrit avec confiance.

Vous n’avez pas besoin de couvrir chaque détail d’implémentation. Concentrez-vous sur les flux utilisateurs où une panne a des conséquences coûteuses :

  • la connexion
  • le processus de paiement
  • l’envoi de formulaires importants
  • les vérifications de permissions
  • les calculs sur lesquels l’entreprise compte

Les tests ne prouveront pas que l’application est sans défaut ; ils détectent les dégradations évidentes avant que les utilisateurs ne les remarquent. Les tests de comportement, plutôt que ceux des aspects internes, résistent également bien mieux au refactoring.

Faites attention à la dégradation progressive et lente des performances

Les grandes applications React deviennent rarement lentes dès une seule mise à jour. Les performances se détériorent petit à petit à cause de décisions discutables : des dépendances lourdes, d’immenses images, des re-render inutiles, dix requêtes par écran. Ensemble, elles peuvent faire passer le temps de chargement d’un tableau de bord à cinq secondes ; surveillez donc en permanence :

  • les composants qui se re-render alors que rien dans ce qu’ils affichent n’a changé
  • la taille du bundle qui augmente à chaque mise à jour
  • la même requête envoyée plus d’une fois par écran
  • des composants individuels qui mettent beaucoup de temps à se render
  • de longues listes affichées sans virtualisation
  • des images de taille excessive

De petites victoires s’accumulent, et détecter une régression dès son apparition coûte bien moins cher que de devoir la retrouver plus tard.

Concevez délibérément le chemin vers l’échec

La plupart des efforts sont consacrés au chemin réussi, où chaque requête aboutit. En production, les connexions se coupent, les APIs échouent, les permissions changent et le backend renvoie des résultats inattendus. L’utilisateur devrait voir un message clair et permettant de se remettre en situation, du type « Nous n’avons pas pu charger vos données. Essayez à nouveau », plutôt qu’une erreur de fonctionnement brute telle que :

TypeError: Cannot read properties of undefined

En termes React, cela signifie des états d’erreur explicites pour la récupération de données, des limites d’erreur afin qu’un composant défaillant ne vide pas toute la page, ainsi que des actions de tentative répétée là où c’est pertinent.

Considérez la surveillance en production comme faisant partie du développement

Le déploiement n’est pas la fin du travail. La production révèle des problèmes que le développement local ne mettra jamais en évidence, et vous avez besoin d’une visibilité sur :

  • erreurs en temps de exécution et endroits où elles se produisent
  • demandes plus lentes que prévu
  • appels API qui échouent
  • performance globale de la page et des interactions
  • manière dont les utilisateurs utilisent réellement le produit

Sans cela, un rapport de bug n’est rien d’autre que « un utilisateur a dit qu’il y avait un problème hier ». Le suivi des erreurs et les journaux contextuels transforment cela en un traceur d’appel et en une date et heure permettant de prendre des mesures.

Habitudes qui maintiennent une équipe alignée

Rédiger de la documentation pour les membres déjà présents dans l’équipe

La documentation est souvent considérée comme une tâche de transmission, or elle aide tous les membres de l’équipe aujourd’hui, en particulier pour ce qui concerne les permissions complexes, les architectures inhabituelles, les intégrations avec des tiers et les règles commerciales importantes.

Aucun manuel long n’est nécessaire. De brèves notes expliquant le fonctionnement de l’authentification, le flux des données, l’emplacement des fonctionnalités principales et les raisons des décisions clés permettent d’économiser des heures. Demander de l’aide au développeur initial ne fonctionne plus une fois qu’il part, et c’est toujours le cas dans les projets de longue durée.

Le refactoring est une maintenance de routine, pas une reconnaissance d’échec

Le refactoring ne prouve pas que le code initial était mauvais. Les exigences, les équipes et les produits changent, et un code qui convenait il y a un an peut ne plus correspondre au problème qu’il devait résoudre.

Le danger réside dans l’argument selon lequel quelque chose « fonctionne déjà ». Une chaise à trois pieds fonctionne aussi, jusqu’à ce que quelqu’un s’y assoie. De petits refacteurs réguliers, soutenus par des tests, permettent de garder la dette technique gérable.

La cohérence compte plus que les préférences personnelles

camelCase

Un autre préfère ceci :

snake_case

Et un troisième invente un nouveau modèle de composant toutes les quelques semaines. Une équipe importante ne peut pas fonctionner lorsque chaque fichier suit les préférences de son auteur ; il faut donc assurer la cohérence automatiquement grâce à :

  • un linter avec des règles convenues
  • un formatteur automatique
  • des conventions de nommage documentées
  • des modèles partagés et examinés pour les tâches courantes

La base de code doit ressembler à un seul projet, et non à une douzaine de développeurs qui se disputent sur les structures des fichiers.

Choisissez l’architecture que votre équipe peut réellement mettre en œuvre

Les débats sur l’architecture sont un passe-temps préféré : microservices, monorepos, architecture propre, conception pilotée par le domaine… et quelqu’un a toujours un diagramme prêt. Mais l’option la plus sophistiquée n’est pas forcément la meilleure. Un bon choix répond à trois critères :

  • il résout les problèmes que vous rencontrez réellement
  • toute l’équipe peut l’expliquer
  • L’équipe peut le faire fonctionner et l’améliorer continuellement
  • Un design simple appliqué de manière cohérente l’emporte à chaque fois sur un design brillant que seul son créateur comprend.

    Pourquoi rien de tout cela n’est excitant, et pourquoi ça marche

    La maintenance à long terme change la définition de ce qu’est un « bon développement » : la vitesse d’écriture importe moins que la lisibilité, la facilité de modification future, les besoins des autres développeurs et le comportement en production. Voici une liste de contrôle pratique :

    • Les composants restent petits et à vocation unique
    • L’état global est une liste courte et bien définie
    • Les noms décrivent l’intention
    • Chaque nouveau package a une justification
    • La structure des dossiers suit une règle documentée
    • Le refactoring a lieu en permanence
    • Les flux d’utilisateurs critiques disposent de tests
    • Les erreurs en production sont visibles
    • L’option la plus simple l’emporte en cas d’égalité

    Ces règles ne sont pas glamour, ce qui explique probablement pourquoi elles perdurent. Pour des pratiques structurelles au niveau de l’architecture, consultez dix habitudes architecturales qui permettent de maintenir des bases de code frontend fonctionnelles sur le long terme.

    Points clés

    • Les grandes applications React souffrent rarement à cause de React lui-même ; elles souffrent plutôt à cause de l’accumulation de raccourcis : un composant géant, une utilité mystérieuse, un paquet inutile, une astuce conçue pour être temporaire.
    • La maintenabilité ne peut pas être ajoutée en dernier recours. C’est la somme de nombreuses petites décisions prises une par une.
    • Le moment le plus opportun pour adopter ces habitudes est avant que les problèmes n’apparaissent, lorsque diviser un composant ou refuser une dépendance ne coûte encore que quelques minutes et non des semaines.
  • Lorsque vous êtes tenté par une solution ingénieuse, rappelez-vous que la personne qui en assurera la maintenance dans six mois, ce sera peut-être vous, devra comprendre pourquoi elle fonctionne sans le contexte actuel.
  • Lectures complémentaires