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
| Type | Cible | Alerte | Blocker |
|---|---|---|---|
| Service métier | 200 | 300 | 500 |
| Route Elysia | 150 | 250 | 400 |
| Composant React | 150 | 250 | 400 |
| Page Next.js | 100 | 200 | 300 |
| Repository | 200 | 300 | 500 |
| Schema Drizzle | 300 | 500 | 800 |
| Schema TypeBox | 200 | 400 | 600 |
| Fichier de test | 400 | 600 | 800 |
| SKILL.md | 5000 mots | 7000 mots | 10000 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 lignesSchemas 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-exportsLes 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
- Architecture modulaire (/docs/conventions/modular-architecture) — le split suit le domaine, pas l'arbitraire
- Factorisation (/docs/conventions/factorization) — splitter n'est pas factoriser ; on split pour la lisibilité, pas pour DRY
- Tests (/docs/conventions/testing) — le split des tests suit la même logique que le split du code