Coding style applicatif — non-négociables Mathieu
Périmètre : règles de code transverses à respecter par toute instance Claude qui édite du code applicatif (PHP/Symfony, Twig, Doctrine, Python/FastAPI) dans l'écosystème
telaria/tlr-*. Pas un coding standard exhaustif : juste les non-négociables, validés en pratique surtelariav0.1 → v0.4.Réf
pilotage/ecosystem.md(rôles & autorité). Compléments transverses :quality.md,accessibility.md,guides/git-conventions.md,markdown.md.Dernière mise à jour : 2026-05-29 — création (suite inbox codexia #18).
Comment utiliser cette fiche
- Toute nouvelle instance Claude sur un dépôt code lit ce fichier avant ses premiers commits.
- Chaque règle = une phrase + un why d'une ligne + un exemple court quand l'écart est tentant.
- En cas de doute sur l'interprétation : Lead dev (
codexia) tranche. - Les écarts justifiés se documentent dans le code (commentaire
WHY) ou la PR — pas de zèle silencieux.
1. Sobriété > élégance
Règle : trois lignes répétées valent mieux qu'une abstraction prématurée.
Why : les abstractions créées « au cas où » durcissent le code avant qu'on sache de quoi il a besoin. Coût de revert > coût de duplication tant qu'il n'y a pas 3 cas réels.
Interdits par défaut : feature flags pour code non livré, shims de rétrocompatibilité pour API internes, wrappers « génériques » sans usage prouvé, hiérarchies d'interfaces à une seule implémentation.
2. Tests d'intégration > unitaires mockés
Règle : quand l'intégration est faisable (DB de test, conteneur, fixture), elle bat le mock.
Why : leçon retenue — des tests mockés verts ont masqué une migration cassée en prod. Le mock ment sur le contrat réel.
Mocks acceptables : dépendances coûteuses (modèle ML chargé, API tierce payante, e-mail sortant). Tout le reste passe par une vraie instance.
3. Sécurité par défaut
Règle : Voters Symfony, rate limiter sur surfaces sensibles, headers HSTS/CSP/X-Frame, durcissement systemd en prod.
Why : la sécu ajoutée après coup laisse toujours un trou. Ajoutée d'office, elle coûte ~10 lignes.
Cibles minimales :
is_granted('VOTER_NAME', $resource)plutĂ´t queif ($user->getId() === ...)ad-hoc.- Rate limiter sur
login, reset password, endpoints API publics. - Headers via
nelmio/security-bundleou équivalent, CSP stricte par défaut. - En WSL local : pas de zèle sécu (HTTPS strict, durcissement systemd) — gardé pour le VPS.
4. Accessibilité RGAA AA non négociable
Règle : toute surface UI livrée respecte RGAA AA.
Why : norme légale + engagement Opquast du projet. Rattraper l'accessibilité après coup = re-design.
Détails : voir accessibility.md (couleurs, contrastes, navigation clavier, ARIA, RGAA AA déjà cadré).
5. i18n par défaut
Règle : aucune chaîne UI en dur ; fr par défaut ; structure prête pour en et autres.
Why : ajouter une langue à un projet hardcodé fr coûte plus cher que d'écrire trans('...') dès le début.
Avant / après (Twig) :
{# ❌ #} <button>Valider</button> {# ✅ #} <button>{{ 'action.submit'|trans }}</button>
6. Doctrine — entités fines, repos pour queries
Règle : entités = état + invariants. Queries non triviales dans le Repository. Migrations checkées via doctrine:schema:validate. Jamais de SQL dans un contrôleur.
Why : SQL inline = duplication garantie + sécurité fragile. schema:validate rouge = drift entre code et DB → bugs silencieux en prod.
Avant / après :
// ❌ contrôleur $rows = $conn->executeQuery('SELECT * FROM user WHERE ...')->fetchAllAssociative(); // ✅ $users = $userRepository->findActiveByOrganization($org);
7. Twig — pas de logique métier
Règle : Twig affiche. La logique vit dans des extensions Twig ou des Value Objects passés depuis le contrôleur.
Why : logique en Twig = intestable + invisible aux outils statiques.
Avant / après :
{# ❌ #} {% if user.subscriptionEndsAt|date('U') < 'now'|date('U') and user.role != 'admin' %} {# ✅ #} {% if user.needsRenewalReminder %}
8. Python (FastAPI) — lazy imports, Pydantic strict, lifespan
Règle :
- Imports lourds (ML, torch, sentence-transformers) lazy dans une fonction d'init, pas au top-level.
- Validation contrat via Pydantic strict (
Field(min_length=1), types précis aux frontières). - Préchargement via
lifespan, pas@app.on_event(déprécié).
Why : top-level imports lourds → boot lent + tests qui chargent tout pour rien. Pydantic strict aux frontières = un seul point de validation, le reste du code peut faire confiance aux types.
Avant / après :
# ❌ top du module from sentence_transformers import SentenceTransformer model = SentenceTransformer(...) # ✅ async def lifespan(app): from sentence_transformers import SentenceTransformer app.state.model = SentenceTransformer(...) yield
9. Bundles vs app-code — critère = réutilisation prouvée
Règle : on extrait en bundle (Symfony) ou en package (Python) uniquement quand la réutilisation est avérée — pas « parce que ce serait propre ».
Why : un bundle prématuré gèle une API qu'on aurait voulu changer. Voir [[dette-architecture-ia-surfaces]] côté codexia pour le retour d'expérience.
Seuil : ≥ 2 dépôts consommateurs réels, ou besoin de versioning indépendant clair.
10. Commentaires — WHY uniquement
Règle : un commentaire explique le pourquoi non-évident (contrainte cachée, invariant subtil, workaround pour un bug précis). Pas le quoi (les identifiants le disent).
Why : un commentaire WHAT ment dès le prochain rename. Un commentaire WHY reste vrai.
Interdits :
- Refs au ticket / PR / incident (
// fix #123,// pour le flow X) → ça vit dans le message de commit et la PR, pas dans le code. - Docstrings multi-paragraphes décoratives.
// updated,// removed,// old code—git logest là pour ça.
11. Outillage avant push
Règle : avant chaque push (idéalement via hook pre-push) :
- PHP : PHPStan niveau max raisonnable (currently atteint sur
telaria),composer audit= 0 advisory, tests verts. - Python :
ruff check,mypy --strictsur les modules de contrat, tests verts. - Tous :
git diff --check(pas de trailing whitespace ni de marqueurs de conflit).
Why : un advisory ou un test rouge en CI coûte 5× le temps qu'il aurait coûté en local. La discipline pre-push tient la barre.
12. Zéro dette, zéro poussière sous le tapis
Règle : on fait toujours tout ce qu'on peut faire au fur et à mesure. Pas de « on verra plus tard », pas de TODO reporté à la session suivante, pas de petite MAJ doc qu'on garde pour « quand on aura le temps ».
Why : un report devient un trou qui se découvre au pire moment. Coût immédiat < coût différé. Mathieu refuse la dette technique et les non-dits.
Conséquence assumée : la doc peut changer contradictoirement plusieurs fois dans la même journée au fil des devs — la cohérence à l'instant T prime sur la stabilité éditoriale.
Exemples concrets :
- Release voisine qui impacte
ecosystem.md(version courante) → MAJ immédiate, même pour 1 ligne. - Commit qui rend une page de spec obsolète → patch dans la foulée, pas une ligne de wishlist.
- Ack rituel dû à une instance qui vient de livrer → posté, même si elle dit « rien à patcher ».
- Trou doc identifié pendant un dev → corrigé maintenant ou créé en ticket explicite, jamais ignoré.
Voir aussi
AGENTS.md— règles éditoriales (français, UTF-8/LF, accessibilité,inputs/legacy/lecture seule).guides/git-conventions.md— commits, signature, branches.pilotage/ecosystem.md— rôles & arbitrage entre instances.quality.md— qualité produit transverse (Opquast, VPTCS).