My App

Seuils de fichiers

Max lignes par fichier, quand splitter, exceptions pour les tests et les schemas

Règle absolue : aucun fichier source ne dépasse 500 lignes. Passé 400, on planifie le split. Passé 500, c'est un blocker de PR.

Pourquoi

Un fichier qui dépasse 500 lignes signale presque toujours :

  • Deux responsabilités fusionnées (service + repository, ou routes + logique)
  • Un composant React qui gère trop d'états
  • Une classe qui a trop de collaborators
  • Un manque d'extraction de helpers privés

Cas extrême connu dans l'ancien projet : cardex/page.tsx à 900 lignes qui mélangeait filtres + grid + modales + mutations. Imaintenable par un humain, pire par l'IA.

Les seuils par type de fichier

TypeCibleAlerteBlocker
Service métier200300500
Route Elysia150250400
Composant React150250400
Page Next.js100200300
Repository200300500
Schema Drizzle300500800
Schema TypeBox200400600
Fichier de test400600800
SKILL.md5000 mots7000 mots10000 mots

Les schemas et les tests ont un plafond plus généreux car ils sont souvent constitués de blocs répétitifs (constantes, cas de test, definitions de colonnes) dont la lecture linéaire est acceptable.

Quand et comment splitter

Service métier

Symptôme : un *.service.ts dépasse 300 lignes et mélange plusieurs sous-domaines.

Exemple concret : cardex.service.ts qui gère à la fois linkGuestToUser, completeCheckIn, confirmGuestArrival, checkoutGuest, getAllGuests, createGuest, toggleVip, sendCheckInEmail.

# Avant (trop gros)
packages/api/src/modules/cardex/
└── cardex.service.ts             # 420 lignes

# Après (split par sous-domaine)
packages/api/src/modules/cardex/
├── cardex.service.ts             # re-export façade (~20 lignes)
├── cardex-linking.service.ts     # link, complete, confirm (~150 lignes)
├── cardex-management.service.ts  # CRUD, VIP, checkout (~180 lignes)
└── cardex-invitation.service.ts  # send-check-in-email + Reacher (~80 lignes)

Attention : le split doit suivre le domaine, pas l'arbitraire. Si les 3 fichiers finissent par s'appeler mutuellement, c'est qu'on a mal cassé.

Composant React

Symptôme : component.tsx > 250 lignes, avec plusieurs états useState, plusieurs sous-composants inline.

# Avant
apps/dashboard/src/features/cardex/cardex-page.tsx  # 900 lignes

# Après
apps/dashboard/src/features/cardex/
├── cardex-page.tsx                 # orchestration (~100 lignes)
├── guest-card-grid.tsx             # grid + card rendering (~200 lignes)
├── cardex-filters.tsx              # filtres + search (~150 lignes)
├── cardex-toolbar.tsx              # bulk actions (~100 lignes)
├── new-guest-modal.tsx             # modal création (~120 lignes)
├── edit-guest-modal.tsx            # modal édition (~130 lignes)
└── use-cardex-filters.ts           # state des filtres (~50 lignes)

Le cardex-page.tsx restant est composition pure : il importe les sous-composants et les assemble, sans logique métier.

Fichiers de test

Les tests ont un plafond plus permissif (800 lignes) mais on split quand même par scénario au-delà de 600 :

# Avant
cardex.service.test.ts                   # 720 lignes

# Après
cardex.service.link-guest.test.ts        # ~200 lignes
cardex.service.confirm-arrival.test.ts   # ~180 lignes
cardex.service.send-email.test.ts        # ~150 lignes
cardex.service.crud.test.ts              # ~200 lignes

Schemas Drizzle / TypeBox

Les schemas sont souvent longs par nature (liste de colonnes, relations). Plafond 800 lignes. Au-delà, on split par sous-domaine :

# Au lieu d'un packages/db/src/schema/everything.ts
packages/db/src/schema/
├── auth.ts                 # Better Auth tables
├── organizations.ts        # organization, member, invitation
├── guests.ts               # guest, guest_group
├── rooms.ts                # room
├── bookings.ts             # restaurant, room_service, laundry, spa
├── conversations.ts        # guest_conversation, guest_message
├── tickets.ts              # ticket, ticket_comment
├── integrations.ts         # integration, integration_sync_log
├── analytics.ts            # activity_log, interaction_log
├── enums.ts                # tous les pgEnum centralisés
└── index.ts                # re-exports

Les excuses qu'on n'accepte pas

  • « Mais c'est plus simple à lire d'un coup » — non, 500 lignes n'est jamais "simple à lire". On scroll, on perd le contexte, l'IA hallucine.
  • « Je vais le splitter plus tard » — plus tard = jamais. On split au moment où on ajoute la feature qui pousse le fichier au-delà du seuil.
  • « Le framework me force » — si vraiment un framework force 400+ lignes dans un fichier (rare), on documente l'exception en commentaire en haut du fichier.

Comment vérifier

Pas de linter automatique pour la limite (Biome ne le fait pas nativement). On utilise un script CI qui liste les fichiers > 400 lignes et warn, > 500 fail :

# scripts/check-file-sizes.sh (à créer)
find apps packages -name "*.ts" -o -name "*.tsx" \
  | grep -v node_modules \
  | grep -v "\.test\." \
  | xargs wc -l \
  | awk '$1 > 500 { print $2 ": " $1 " lines — BLOCKER"; fail=1 }
         $1 > 400 && $1 <= 500 { print $2 ": " $1 " lines — warning" }
         END { exit fail }'

Ajouté au CI comme step obligatoire, on ne merge pas si un fichier dépasse 500.

Exceptions documentées

Si un fichier doit vraiment dépasser le seuil, on le documente en tête de fichier :

// packages/db/src/schema/bookings.ts
//
// NOTE: ce fichier fait 680 lignes car il contient les 4 schemas de booking
// (restaurant, room-service, laundry, spa) qui partagent les mêmes types de
// base et qu'on veut garder colocalisés. Split possible mais introduit des
// imports circulaires avec les relations — voir ADR-XX si besoin.

Sans note : le reviewer bloque la PR.

Lien avec les autres conventions

On this page