Accueil / Articles / Dix habitudes architecturales qui permettent aux bases de code frontend de rester maintenables pendant des années

Dix habitudes architecturales qui permettent aux bases de code frontend de rester maintenables pendant des années

Explique les habitudes structurelles telles que l’optimisation pour la supprimabilité, le flux de données explicite et l’isolation de la logique métier, qui aident les bases de code à rester maintenables au fil des années de modifications.

3870 mots

Toute base de code frontend qui survit suffisamment longtemps finit par se diviser en deux régions distinctes.

La première peut être appelée la Zone de danger.

C’est un mélange chaotique de gestionnaires d’état, d’hooks de cycle de vie corrigés à la hâte, d’abstractions globales astucieuses mais opaques, d’expériences inachevées, ainsi que de fonctions d’aide écrites il y a des années dont personne ne peut plus expliquer complètement le fonctionnement.

Nul ne veut s’en approcher.

Lorsqu’une nouvelle demande de fonctionnalité arrive dans cette partie de l’application, l’équipe n’évalue pas le travail en fonction de la difficulté réelle de sa réalisation.

Elle l’évalue plutôt en fonction du risque perçu lié à la modification de ce code.

Une modification qui devrait durer deux jours se transforme en un travail de deux semaines, car tout le monde sait que la majeure partie du temps sera consacrée aux tests de régression plutôt qu’à la mise en œuvre.

Puis il y a l’autre région.

Appelons-la la Fondation stable.

Ces modules, conçus il y a des années, ont résisté silencieusement à plusieurs migrations de framework, réajustements de conception, changements de direction du produit et substitutions à la tête des équipes techniques.

Ils provoquent presque jamais de pannes.

Leur manière d’être écrits n’a rien de particulièrement ingénieux.

Lorsqu’un nouveau membre rejoint l’équipe, il peut ouvrir l’un de ces fichiers, comprendre son fonctionnement sans avoir besoin d’explications détaillées, et soumettre sa première demande de fusion en un ou deux jours.

C’est là l’aspect qui mérite d’être pris en compte.

Le code qui perdure n’est généralement pas le plus avancé du système.

C’est souvent celui qui est le plus simple.

Les ingénieurs expérimentés savent que le logiciel ne reste jamais dans les mêmes conditions qu’à son moment de création.

Les exigences changent.

Les équipes se réorganisent.

Les dépendances sont remplacées.

Les frameworks évoluent.

Les entreprises changent de stratégie.

Les gens quittent l’entreprise.

De nouveaux ingénieurs arrivent sans aucune information sur les raisons pour lesquelles certaines choses ont été conçues de cette manière.

Ainsi, la question à se poser n’est pas :

"À quel point ce design est-il propre pour l’instant ?"

C’est plutôt :

"Quel sera le coût pour modifier cela dans cinq ans ?"

Voici les habitudes structurelles qui permettent une telle durabilité.

1. Optimiser pour la supprimabilité, pas pour la réutilisabilité

Beaucoup de directives en matière d’architecture se concentrent sur la réutilisabilité.

Rendez vos composants réutilisables.

Créez des services génériques.

Ajoutez des points d’extension.

Concevez des architectures de plugins.

Rédigez des abstractions pour des implémentations que vous n’avez pas encore développées.

La réutilisabilité a sa place.

Mais il existe une autre qualité qui compte souvent davantage dans un produit en perpétuelle évolution :

La facilité avec laquelle on peut supprimer quelque chose.

Les fonctionnalités ne sont pas permanentes.

Elles sont remplacées.

Elles sont reconstruites.

Elles sont intégrées à d’autres fonctionnalités.

Parfois, l’entreprise perd simplement tout intérêt pour elles.

Imaginez un projet React organisé strictement par type de fichier :

src/
  components/
    BillingTable.tsx
    UserModal.tsx
    SubscriptionCard.tsx
  hooks/
    useBillingData.ts
    useUserData.ts
    useSubscription.ts  services/
    billingApi.ts
    userApi.ts
    subscriptionApi.ts

À première vue, cela semble ordonné.

Chaque type de fichier a son dossier dédié.

Mais supposons que l’entreprise décide, dix-huit mois plus tard, d’éliminer complètement le processus de facturation.

Où se trouvent réellement tous les éléments liés à la facturation ?

Il faudrait chercher dans plusieurs dossiers différents.

On trouve et on supprime BillingTable.tsx.

Puis on découvre useBillingData.ts.

Puis quelques définitions de types écrites spécifiquement pour la facturation.

Puis une fonction d’aide que seule la facturation appelle jamais.

Puis un fichier de style.

Puis une appel API.

Puis un fichier de configuration pour les tests.

Puis un crochet qui a commencé comme un crochet de facturation mais a été renommé au fil du temps.

Cette fonctionnalité a disparu du produit, mais des vestiges subsistent encore un peu partout dans le code.

C’est exactement ainsi que le code inutilisé s’accumule avec le temps.

L’organisation par fonctionnalité rend les frontières bien plus évidentes :

src/
  features/
    billing/
      components/
        BillingTable.tsx
      hooks/
        useBillingData.ts
      services/
        billingApi.ts
      types.ts
      index.ts

Désormais, la facturation a une place bien définie.

Si l’entreprise décide de supprimer cette fonctionnalité, la première étape est simple :

src/features/billing/

Supprimer le dossier.

TypeScript mettra alors en évidence tout ce qui dépend encore de lui.

C’est un type de dépendance bien plus gérable à gérer.

La supprimabilité est une forme de maintenabilité

C’est en partie pour cette raison que les structures de dossiers basées sur des fonctionnalités reviennent constamment dans les discussions sur l’extension de grands projets React. Les équipes travaillant sur des applications importantes mentionnent souvent la colocalisation, car elle réduit l’impact d’un changement donné.

L’objectif n’est pas de viser un arbre de dossiers parfaitement organisé.

L’objectif est de pouvoir répondre rapidement à cette question :

« Si cette fonctionnalité disparaissait demain, qu’aurais-je besoin de supprimer ? »

Si cette question est difficile à répondre, les limites de vos fonctionnalités sont probablement trop floues.

2. Ne transformez pas shared/ en poubelle

Il existe un deuxième piège qui apparaît souvent lorsque les équipes passent à une architecture basée sur des fonctionnalités.

Tout ce qui ne correspond évidemment à aucune fonctionnalité particulière est placé dans shared/.

Quelques mois plus tard, on se retrouve avec quelque chose comme ceci :

shared/
  utils/
  helpers/
  common/
  services/
  components/
  hooks/
  types/

À ce stade, shared/ représente silencieusement la moitié du codebase.

Cela crée son propre type de problème de couplage.

Un bon principe à suivre :

Le code doit être déplacé dans un dossier partagé parce que plusieurs fonctionnalités dépendent réellement du même concept, et non parce qu’on ne sait pas où l’installer ailleurs.

Un composant de bouton générique s’intègre naturellement dans un système de conception.

Un client d’authentification peut raisonnablement se trouver dans une couche d’infrastructure partagée.

Un outil de formatage de dates peut également être partagé.

Mais une fonction comme :

calculateEnterpriseRenewalDiscount()

fait presque certainement partie de la fonctionnalité qui gère cette règle métier spécifique.

Résistez à l’envie de déplacer des éléments dans des dossiers globaux uniquement pour que l’arborescence des fichiers paraisse plus ordonnée.

Le code partagé n’est pas gratuit, car chaque fonctionnalité qui s’y rapporte devient un dépendant potentiel.

Plus le dossier shared/ s’agrandit, plus il devient difficile de savoir qui est réellement responsable d’un comportement donné.

3. Créez des adaptateurs de défense autour des dépendances externes

Il est fort probable que votre application continue de fonctionner longtemps après la disparition de certaines de ses dépendances.

Vous pouvez compter sur Axios aujourd’hui et passer à la fonction native fetch demain.

Vous pouvez utiliser un fournisseur d’analytique maintenant, pour découvrir que votre entreprise migre vers un autre dans quelques années.

Vous pouvez intégrer une bibliothèque d’authentification aujourd’hui, seulement pour que de nouvelles exigences en matière de sécurité vous poussent à en choisir une autre par la suite.

La méthode fragile pour construire des applications consiste à importer directement ces paquets externes dans des dizaines de composants.

import axios from 'axios';
import { trackMixpanelEvent } from 'mixpanel-browser';
export function CheckoutCard() {
  const handlePurchase = async () => {
    await axios.post('/api/checkout', payload);    trackMixpanelEvent('checkout_completed');
  };
}

À ce stade, la couche d’interface utilisateur connaît précisément quel client HTTP et quel fournisseur d’analytiques vous utilisez. Si ce schéma est appliqué à quarante-cinq composants, remplacer un fournisseur n’est plus une tâche isolée — cela devient un changement qui affecte l’ensemble du répertoire.

Une couche de délimitation permet de rendre les dépendances interchangeables. Par exemple :

// src/shared/lib/analytics.ts
import mixpanel from 'mixpanel-browser';export const analytics = {
  trackCheckoutCompleted(
    orderId: string,
    amount: number
  ) {
    mixpanel.track('checkout_completed', {
      orderId,
      amount,
    });
  },
};

Le composant communique désormais avec un concept défini par votre propre application :

analytics.trackCheckoutCompleted(orderId, amount);

Il n’a aucune idée de savoir si Mixpanel, PostHog, Segment ou un autre outil se trouve en dessous. Si le fournisseur change, le contrat sur lequel repose votre application peut rester exactement identique.

Mais ne抽象isez pas tout

Cet aspect est tout aussi important que le précédent.

Encadrer une dépendance dans un adaptateur n’est pas en soi une bonne pratique de conception. Si vous créez une interface personnalisée pour chaque petite bibliothèque que vous utilisez, vous risquez d’écrire plus de code que la bibliothèque elle-même ne contient.

La vraie question à se poser est :

« Remplacer cette dépendance plus tard serait-il coûteux, ou laisser courir ce risque sans contrôle dans le code serait-il dangereux ? »

Si la réponse est oui, un adaptateur vaut bien cet investissement. Sinon, appeler directement la dépendance est probablement plus simple et suffisant.

Être ingénieur senior ne signifie pas ajouter des couches d’abstraction à tout ce que l’on touche. Cela signifie définir des limites précisément là où les ignorer pourrait vous coûter cher plus tard.

4. Un flux de données explicite vaut mieux que la magie

L’un des moyens les plus rapides de rendre une base de code confuse est de masquer l’origine réelle des valeurs.

Les émetteurs d’événements globaux en sont un exemple typique.

eventBus.emit('USER_UPDATED', {
  id: user.id,
});

Ce ligne indique qu’un événement a été déclenché. Elle ne dit rien sur ceux qui l’écoutent.

On pourrait rechercher dans toute la base de code pour finalement trouver quelque chose comme :

eventBus.on('USER_UPDATED', handler);

Mais il pourrait y avoir trois écouteurs distincts. L’un a peut-être été ajouté il y a deux ans. Un autre pourrait modifier l’état global. Un troisième pourrait envoyer une requête d’analyse. Soudain, le véritable comportement déclenché par cette fonction initiale est dispersé dans toute l’application au lieu d’exister en un seul endroit.

Comparez maintenant cela à un contrat qui est défini explicitement :

interface UserCardProps {
  user: User;
  onUserRoleChange: (
    userId: string,
    newRole: Role
  ) => Promise<void>;
}

Ici, le composant déclare ouvertement quelle action il prend en charge, et le composant parent indique clairement ce qui se produit lorsque cette action est déclenchée. Le flux des données est visible sur la page.

Oui, c’est plus verbeux que de déclencher un événement anonyme. Mais cette surcharge verbale est justifiée, car elle laisse un parcours traçable à travers le code.

Lorsqu’une personne non familière avec le composant ouvre le fichier, elle doit pouvoir répondre à trois questions sans avoir à examiner le reste du répertoire :

D’où proviennent ces données ?

Généralement, il s’agit de props, de paramètres de route, d’un hook ou d’une couche d’accès aux données clairement définie.

Qu’est-ce qui peut les modifier ?

Une appel de fonction visible, une mutation, une action ou une mise à jour explicite de l’état.

Que se passe-t-il lorsque l’utilisateur effectue cette action ?

Un appel direct à une fonction dont l’implémentation peut être suivie pas à pas.

Plus vous cachez de comportements, plus il devient facile de comprendre l’ensemble du système.

5. Conserver la logique métier en dehors des cycles de vie du framework

Les frameworks ne sont pas des éléments permanents — c’est l’une des hypothèses les plus fiables que l’on puisse faire en travaillant sur le frontend.

Réact seul a connu plusieurs changements majeurs. Les composants de type classe ont perdu en popularité. Les hooks ont réorganisé la manière dont la logique à état est structurée. Create React App a été remplacé dans de nombreux projets par des outils tels que Vite ou des configurations natives aux frameworks. Le rendu côté serveur et de nouvelles approches de routage ont redéfini la façon dont les équipes envisagent le chargement des données et l’emplacement des limites de l’application.

Le framework que vous utilisez actuellement pourrait ne plus ressembler du tout à ce qui sera standard dans cinq ans. Vos règles métier, quant à elles, doivent continuer de fonctionner quel que soit le contexte.

Prenons le calcul des impôts comme exemple. Une version fragile cache la logique réelle à l’intérieur d’un hook React :

export function useTaxCalculator(
  cartItems: CartItem[]
) {
  const [tax, setTax] = useState(0);
  useEffect(() => {
    let calculated = 0;    // 60 lines of tax calculation,
    // rounding rules,
    // country logic,
    // exemptions...    setTax(calculated);
  }, [cartItems]);  return tax;
}

Désormais, le calcul des impôts dépend de React. Le tester nécessite de lancer un environnement React. L’appeler depuis une action serveur devient complexe. Le faire fonctionner dans un Web Worker est également compliqué. Le transférer vers un autre framework UI représente une tâche coûteuse.

Une approche plus efficace consiste à séparer ces deux aspects :

export function calculateTax(
  cartItems: CartItem[],
  countryCode: string
): number {
  // Pure business logic
  return totalTax;
}

La couche React appelle alors simplement cette fonction :

const tax = calculateTax(cartItems, countryCode);

Avec cette structure, la logique qui compte réellement n’a aucune connaissance de React. Elle peut s’exécuter dans n’importe quel environnement. Elle est testable avec des tests unitaires classiques. Un processus serveur peut la réutiliser directement. De plus, elle survit à une migration vers un framework UI sans avoir besoin d’être réécrite.

Les frameworks doivent être placés en périphérie

Une façon utile de visualiser cela est à l’aide d’un diagramme en couches :

┌──────────────────────────────┐
│          UI Layer            │
│      React / Next.js         │
├──────────────────────────────┤
│       Application Logic      │
├──────────────────────────────┤
│        Domain Logic          │
│     Pure TypeScript          │
├──────────────────────────────┤
│       Infrastructure        │
│ APIs / DB / Vendors / SDKs   │
└──────────────────────────────┘

Plus un morceau de code se trouve près du centre, moins il devrait dépendre d’un framework ou d’une bibliothèque de fournisseur particulier.

Cela ne signifie pas que chaque projet React nécessite une mise en place complète de l’« Architecture Propre ».

Cela signifie que vous devez être clair sur quels éléments de votre code sont véritablement spécifiques à React et quels éléments représentent vos règles métier réelles.

Ce sont deux catégories distinctes, et c’est en les traitant comme une seule que commencent les problèmes.

6. Éviter de transformer les hooks en mini-applications

Cette approche apparaît fréquemment dans les bases de code React.

Généralement, tout commence innocemment :

function useUser() {
  // fetch user
}

Puis, avec le temps, les exigences s’accumulent.

function useUser() {
  // fetch user
  // loading state  // error handling  // permissions  // analytics  // transformations  // caching  // retry logic  // business rules  // notifications  // feature flags
}

Très vite, ce qui était un simple hook est devenu discrètement une application de 500 lignes cachée derrière un nom de fonction à l’air inoffensif.

Les hooks sont vraiment utiles.

Mais un hook ne devrait pas devenir un dépotoir pour tous les problèmes, simplement parce qu’il a un accès pratique à l’état et aux effets de React.

Une meilleure approche consiste à faire en sorte que le hook délègue des tâches à des composants plus petits et mieux ciblés :

function useUser() {
  const user = useUserQuery();
  const permissions =
    calculatePermissions(user.data);  return {
    user: user.data,
    permissions,
    isLoading: user.isLoading,
  };
}

Avec cette structure, le hook devient une couche d’orchestration qui relie les différents éléments entre eux.

Il n’est plus l’ensemble de l’architecture entassée dans une seule fonction.

Cette frontière est bien plus facile à maintenir au fil du temps.

7. Rédiger des documents de décision architecturale, pas des wikis interminables

L’une des causes les plus fréquentes de dégradation architecturale n’est pas du tout du code désordonné.

C’est le contexte perdu.

Voici comment cela se produit généralement : un développeur prend une décision peu évidente. La décision est judicieuse, et tout le monde dans l’équipe à ce moment-là comprend les raisons qui la sous-tendent. Puis cette personne part.

Des mois plus tard, un nouvel ingénieur tombe sur cette implémentation inhabituelle et se demande :

"Pourquoi le faisons-nous de cette manière ? Il doit y avoir une solution plus propre."

Ainsi, il la réécrit, réintroduisant à son insu exactement le même problème que la décision initiale visait à éviter.

Prenons par exemple un tableau de bord construit sur des Server-Sent Events plutôt que sur des WebSockets.

En l’absence de ce contexte, un nouveau développeur pourrait raisonnablement en conclure :

"WebSockets constituent la norme la plus récente. Changeons de technologie."

Cependant, l’équipe initiale a pu choisir SSE précisément parce que de nombreux clients entreprises utilisent des proxies d’entreprise restrictifs qui gèrent mal les connexions WebSocket.

Cette raison reste invisible si l’on ne se concentre que sur le code lui-même.

C’est précisément cette lacune que les documents de décision architecturale sont conçus pour combler.

Par exemple :

# ADR 003: Use Server-Sent Events for Dashboard Feeds
## ContextOur dashboard requires real-time metric updates.We evaluated WebSockets and Server-Sent Events.## DecisionWe chose Server-Sent Events because:1. Communication is strictly server-to-client.
2. SSE uses standard HTTP infrastructure.
3. Browser reconnection is supported natively.
4. The solution works reliably within our enterprise network environment.## ConsequencesIf we later require client-to-server
bi-directional streaming, we should
re-evaluate this decision.

Avec un tel document en place, le développeur suivant n’a pas besoin de reconstituer la logique à partir de zéro.

Il peut immédiatement comprendre le pourquoi.

C’est simple

/docs/adr/

Un dossier dans votre répertoire peut conserver des années de connaissances institutionnelles qui, sinon, disparaîtraient avec le départ de quiconque quitte l’équipe.

Documentez les décisions, pas tout

Vous n’avez pas besoin de maintenir une wiki volumineuse de cent pages.

Dans la plupart des cas, le code lui-même doit être suffisamment clair pour expliquer ce qu’il fait.

La documentation doit plutôt retenir les raisonnements que le code ne peut pas exprimer par lui-même :

  • pourquoi une technologie particulière a été choisie
  • pourquoi une alternative plus évidente a été écartée
  • pourquoi une contrainte inhabituelle existe
  • pourquoi une solution de contournement qui semble inutile est en réalité encore nécessaire

La documentation justifie son existence précisément lorsqu’elle capture des contextes qui, sinon, disparaîtraient avec les personnes qui les possédaient.

8. Conception destinée à l’ingénieur qui rejoindra après vous

Ce pourrait être le test le plus simple pour évaluer si une architecture est conçue pour durer.

Imaginez un scénario où, dès demain, toutes les personnes qui comprennent actuellement le fonctionnement interne du système quittent l’entreprise en même temps.

Une équipe nouvelle serait-elle capable de continuer à le faire fonctionner ?

Si votre réponse honnête est non, cela ne signifie pas nécessairement que vos développeurs manquent de compétences. Cela veut dire que le système dépend de manière cachée des connaissances de personnes spécifiques.

Un système conçu pour durer doit permettre de découvrir automatiquement son fonctionnement essentiel.

Quelqu’un de nouveau devrait pouvoir ouvrir le répertoire de code et rassembler progressivement des réponses aux questions suivantes :

  • Où se trouve cette fonctionnalité précise dans le code ?
  • Quel module est responsable de ce comportement ?
  • D’où proviennent réellement ces données ?
  • Quels systèmes externes ce code dépend-il ?
  • Quelles hypothèses ce code établit-il implicitement ?
  • Pourquoi a-t-on choisi cette architecture en particulier ?
  • Qu’est-ce qui peut être modifié sans endommager autre chose ?
  • C’est précisément pour cette raison que des limites claires ont une telle importance.

    Un nouveau venu ne devrait pas avoir à apprendre toute l’histoire de l’entreprise juste pour comprendre ce que fait le code.

    La base de code elle-même doit contenir suffisamment d’informations sur cette histoire.

    9. Limiter la portée des changements

    Un bon moyen d’évaluer une architecture consiste à compter le nombre de fichiers qu’un changement mineur oblige à modifier.

    Imaginons une demande de fonctionnalité simple : quelqu’un souhaite un bouton permettant aux utilisateurs d’exporter le rapport de facturation en fichier CSV.

    Dans un système fortement couplé, répondre à cette demande pourrait nécessiter de modifier des fichiers dispersés dans :

    components/
    hooks/
    services/
    utils/
    types/
    global state/
    shared helpers/
    

    L’ingénieur peut se retrouver à modifier une douzaine de fichiers rien que pour déployer un bouton.

    Comparez cela à une structure de fonctionnalités bien délimitée :

    features/
      billing/
        components/
        hooks/
        services/
        utils/
    

    Ici, la même modification peut rester presque entièrement dans le dossier propre à la fonctionnalité de facturation.

    C’est ce que l’on entend lorsqu’on parle de réduire le rayon d’action d’une modification.

    • Moins de régressions
    • Des revues de code plus simples
    • Un déploiement plus rapide
    • Moins de conflits de fusion
    • Un test plus facile
    • Des refacturations plus sûres

    Atteindre cet objectif ne nécessite pas une architecture complexe. Il faut plutôt des limites qui reflètent réellement l’évolution du produit dans la pratique.

    10. Cessez d’optimiser pour le diagramme d’architecture

    Même un diagramme d’architecture magnifique peut cacher une base de code difficile à utiliser.

    Vous pourriez cocher chacune de ces cases :

    • suivre une architecture propre
    • appliquer les principes de conception SOLID
    • Inverser correctement les dépendances
    • encapsuler l’accès aux données dans des patterns de repository
    • générer des objets à l’aide de patterns de factory
    • lier les éléments entre eux via des événements
    • superposer plusieurs couches d’abstraction les unes sur les autres

    et pourtant transformer une fonctionnalité simple en un travail fastidieux de plusieurs jours.

    L’architecture doit réduire la complexité, et non l’augmenter. Si votre couche architecturale introduit plus de concepts que le produit lui-même, quelque chose ne va pas.

    Souvent, l’architecture la plus solide est celle dont personne ne prend la peine de parler, car les ingénieurs peuvent simplement lire le code et le suivre. En pratique, cela peut ressembler à ceci :

    features/
      billing/
      checkout/
      accounts/
    

    accompagné d’un code modeste :

    shared/
      ui/
      lib/
    

    ainsi que quelques fonctions purement liées à la logique métier.

    Rien de tout cela ne semble impressionnant. Mais s’il reste fonctionnel et logique quatre ans plus tard, c’est exactement ce que l’architecture est censée faire.

    Ce pour quoi les ingénieurs seniors optimisent réellement

    Les ingénieurs seniors ne produisent pas nécessairement du code plus complexe. Ce qui diffère, c’est le ensemble de questions qu’ils se posent avant de l’écrire.

    "Comment puis-je rendre cela réutilisable ?"

    Un ingénieur senior, quant à lui, demande :

    "Est-ce vraiment nécessaire de le rendre réutilisable ?"

    "Comment devrais-je abstraire cela ?"

    Un ingénieur senior, quant à lui, demande :

    « Quel problème spécifique cette abstraction est-elle censée résoudre ? »

    « Où cette fonction utilitaire doit-elle être placée ? »

    « Qui est réellement responsable de ce comportement ? »

    « Comment nous préparons-nous aux exigences futures ? »

    « Quel changement futur est suffisamment probable pour justifier l’ajout de cette complexité maintenant ? »

    « À quoi ressemblera ce code une fois que la personne qui l’a écrit sera partie ? »

    C’est justement à cette question que commence la réflexion à long terme en ingénierie.

    Conclusion : Un code durable a tendance à paraître ordinaire

    Un code qui continue de fonctionner correctement après des années de modifications n’est pas nécessairement celui construit avec le framework le plus récent, le pattern de conception le plus ingénieux ou l’abstraction la plus élégante. Il s’agit généralement simplement d’un code aux frontières claires, avec des décisions sensées mais peu spectaculaires — du genre où un autre ingénieur peut ouvrir le répertoire et comprendre comment ça marche sans avoir besoin de retrouver celui qui l’a écrit à l’origine.

    Les principes fondamentaux sont simples :

    1. Organiser par fonctionnalité et par responsable. Les fonctionnalités doivent être faciles à localiser et, le moment venu, faciles à supprimer.
    2. Préférer la possibilité de suppression à la maximisation du réutilisement. Tous les fragments de logique ne méritent pas d’être transformés en abstractions partagées.
  • Protégez votre application des dépendances de tiers. Recourez à des adaptateurs chaque fois qu’un changement apporté par un fournisseur pourrait avoir des répercussions sur une grande partie du code.
  • Préférez un flux de données explicite. Un peu plus de code visible coûte généralement moins cher qu’un comportement caché et implicite.
  • Séparez la logique métier des cycles de vie du framework. Le rôle de React est de rendre l’interface et de coordonner les éléments — pas de gérer toutes les règles sur lesquelles repose votre activité.
  • Réduisez la portée des hooks. Évitez que un hook personnalisé ne devienne une petite application à part entière.
  • Enregistrez les décisions architecturales. Conservez les raisons derrière les choix inhabituels, et non seulement une description de l’état actuel.
  • Limitez l’impact des changements. Idéalement, une fonctionnalité peut être modifiée sans que vous ayez besoin de toucher à la moitié du répertoire.
  • Ne confondez pas la complexité avec la qualité. Ajouter davantage de couches ne rend pas automatiquement une architecture meilleure.
  • Le plus grand éloge qu’un codebase puisse recevoir n’est pas :

    "Cette architecture est remarquablement ingénieuse."

    C’est plutôt :

    "Je comprends cela."

    Pourquoi ? Parce que cinq ans plus tard, les ingénieurs initiaux auront probablement quitté l’entreprise. Le framework sera sans doute différent, la conception aura évolué, le produit se sera amélioré. L’entreprise elle-même pourrait ne plus ressembler du tout à ce qu’elle est aujourd’hui.

    Mais tant que les limites restent claires, que la logique reste simple et que les raisons des décisions clés sont consignées quelque part, le code peut continuer à évoluer en même temps que tout le reste.

    C’est ainsi qu’apparaît réellement un logiciel durable.

    Lectures associées

  • Erreurs courantes du contract API qui compromettent la fiabilité du frontend — Découvrez dix défauts récurrents dans la conception des API backend, allant de formes de réponse incohérentes à une pagination fragile, qui érodent la confiance du frontend, ainsi que les moyens de les corriger.
  • Dangers de l’architecture backend qui compliquent la tâche des équipes React axées sur le frontend — Explication de cinq défauts fréquents dans la conception backend dans les projets basés sur React, allant d’une mauvaise utilisation du paradigme API à des déploiements fragiles, ainsi que les solutions architecturales pour une fiabilité de niveau production.
  • Pourquoi les abstractions frontend se transforment subtilement en dette technique — Découvrez pourquoi les abstractions frontend prématurées ajoutent une complexité cachée et comment déterminer quand des composants partagés, des hooks ou des utilitaires valent vraiment la peine d’être créés.