Photo Alain Meunier

1. Introduction

Développer un logiciel robuste, maintenable et évolutif nécessite une approche structurée. Ces pratiques visent à clarifier les besoins, réduire les erreurs et faciliter la collaboration entre tous les acteurs, qu'ils soient techniques ou non.

Même pour un projet personnel, même pour apprendre, je considère que le minimum est :

  • CI/CD : le code est versionné, testé et déployable
  • TDD : les tests guident le code
  • un historique clair sur GitHub
  • un déploiement rapide dans un environnement accessible à des utilisateurs réels (testeurs, amis, tiers)
  • des itérations courtes et du feedback rapide

Le code n'est pas écrit « pour moi seul », mais pour être :

  • lu
  • compris
  • critiqué
  • amélioré

Je suis particulièrement attaché à la capacité du code à exprimer l'intention métier. je trouve puissant que les intentions soient "dans le code source". C'est pourquoi j'aime utiliser des scénarios BDD en Gherkin un langage compréhensible par des non-développeurs Ce principe est mal connu donc mal aimé.

L'exemple du login d'un utilisateur sert ici de fil conducteur pédagogique pour illustrer chaque concept — ce n'est pas une fonctionnalité de ce site vitrine.

---

2. Product Goal

En tant que manager technico/fonctionnel, je souhaite améliorer ma capacité à interagir efficacement avec les développeurs en construisant un site web personnel qui démontre ma compréhension des pratiques du software craftsmanship moderne (TDD, BDD, Clean Code, DDD). L'objectif n'est pas de devenir développeur, mais de développer une compréhension concrète des enjeux techniques, de la qualité du code, et de l'expression du métier dans le code source, afin de mieux dialoguer, challenger et collaborer avec les équipes de développement. Le site sert de laboratoire d'apprentissage où l'IA est utilisée comme partenaire pédagogique pour accélérer cette remise à niveau, tout en garantissant que je maîtrise chaque ligne de code produite.

---

3. Exprimer un besoin (User Story)

Théorie : Une User Story (US) est une description simple et compréhensible d'une fonctionnalité, rédigée du point de vue de l'utilisateur. Elle permet de formaliser un besoin et sert de base aux échanges entre les parties prenantes. Les critères d'acceptation précisent les conditions à remplir pour que la fonctionnalité soit considérée comme validée. Ils sont rédigés de manière à être testables et compréhensibles par tous, y compris les non-techniciens. Une US est considérée terminée lorsqu'elle respecte la Definition of Done du projet (tests, revue, pas de régression).

Valider le besoin avant de coder : reformuler la demande, valider la User Story et les scénarios BDD avec les parties prenantes avant toute écriture de code.

Exemple User Storie :

User Storie
En tant qu'utilisateur,
Je souhaite me connecter à mon compte avec mon email et mon mot de passe,
Afin de accéder à mes informations personnelles.

Critères d'acceptation :
- L'email doit être au format valide (ex. : `monemail@chezmoi.fr`).
- Si l'email est vide afficher le message "L'email est obligatoire".
- Le mot de passe doit contenir au moins 8 caractères, 1 majuscule, et 1 chiffre (ex. : `motMotDePasse1`).
- Si le mot de passe est vide, afficher le message "Le mot de passe est obligatoire".
- Si les identifiants sont valides, rediriger vers le tableau de bord.
- Sinon, afficher le message "Email ou mot de passe incorrect".

---

4. Conserver les spécifications (BDD)

Théorie : Les BDD (Behavior-Driven Development) transforment les User Stories en scénarios concrets, écrits dans un langage accessible à tous. Ces scénarios deviennent des tests automatisés, intégrés au code source pour garantir la conformité aux besoins. Dans ce projet, les scénarios Gherkin sont rédigés en français pour rester accessibles à tous les acteurs.

Scénario Gherkin :

Gherkin
Fonctionnalité : Connexion utilisateur

  Contexte :
    L'utilisateur est sur la page de connexion.

  Scénario : Email et mot de passe obligatoires
    Étant donné que l'utilisateur ne saisit ni email ni mot de passe,
    Quand il clique sur "Se connecter",
    Alors le système affiche "Email et mot de passe obligatoires."

  Scénario : Email ou mot de passe incorrect
    Étant donné que l'utilisateur saisit "monemail@chezmoi.fr" et "CestUneErreur",
    Quand il clique sur "Se connecter",
    Alors le système affiche "Email ou mot de passe incorrect."

  Scénario : Connexion réussie
    Étant donné que l'utilisateur saisit "monemail@chezmoi.fr" et "motMotDePasse1",
    Quand il clique sur "Se connecter",
    Alors il est redirigé vers son tableau de bord.

---

5. Faire émerger le code (TDD)

Théorie : Le TDD (Test-Driven Development) consiste à écrire un test avant d'écrire le code. Cela permet de rester concentré sur l'objectif fonctionnel et de valider immédiatement que le code répond au besoin. Dans ce projet, le TDD s'applique à tout nouveau code ou modification, y compris les corrections techniques ; une couverture de tests suffisante est visée pour maintenir la qualité.

Exemple (TypeScript) :

TypeScript
// Test pour vérifier que la connexion échoue si le mot de passe est incorrect test("Connexion échoue avec un mot de passe incorrect", () => { const resultat = verifierConnexion("monemail@chezmoi.fr", "CestUneErreur"); expect(resultat).toBe("Email ou mot de passe incorrect"); });

---

6. TDD Strict (Red-Green-Refactor)

Théorie : Le cycle Red-Green-Refactor est au cœur du TDD strict. Il garantit une implémentation progressive et validée :

  • Red : Écrire un test qui échoue (car le code n'existe pas encore).
  • Green : Écrire le code minimal pour faire passer le test.
  • Refactor : Améliorer la structure du code sans en altérer le comportement.

Cette approche permet d'obtenir un code minimal, fonctionnel et maintenable.

Exemple (TypeScript) :

TypeScript
// Étape 1 : Test échoue (Red) test("Connexion réussie avec bon email et mot de passe", () => { const resultat = verifierConnexion("monemail@chezmoi.fr", "motMotDePasse1"); expect(resultat).toBe("Connexion acceptée"); }); // Étape 2 : Code minimal pour passer le test (Green) function verifierConnexion(email: string, motDePasse: string): string { if (email === "monemail@chezmoi.fr" && motDePasse === "motMotDePasse1") { return "Connexion acceptée"; } return "Email ou mot de passe incorrect"; }

---

7. Refactoring sécurisé

Théorie : Le refactoring est la 3e phase du cycle Red-Green-Refactor. Les tests existants sont le filet de sécurité — ils valident que vous n'avez rien cassé. Refactorisez petit et souvent : chaque étape doit laisser les tests passer. Les patterns de refactoring (extract method, rename, consolidate) sont des microétapes qui préservent le comportement.

Exemple (Login) :

TypeScript
// Avant : fonction chargée function verifierConnexion(email: string, pwd: string): boolean { const user = db.prepare("SELECT * FROM users WHERE email = ?").get(email); if (!user) return false; return user.pwd === pwd; } // Après : refactorisation progressive (tests passent à chaque étape) // Étape 1 : extraire la recherche utilisateur const rechercherUtilisateur = (email: string) => db.prepare("SELECT * FROM users WHERE email = ?").get(email); // Étape 2 : extraire la vérification du mot de passe const motDePasseCorrespond = (user: Utilisateur, pwd: string) => user.verifierMotDePasse(pwd); // Étape 3 : composer function verifierConnexion(email: string, pwd: string): boolean { const user = rechercherUtilisateur(email); return user && motDePasseCorrespond(user, pwd); }

---

8. Tests unitaires

Théorie : Les tests unitaires valident chaque règle métier de façon isolée, sans base de données ni interface. Ils constituent le filet de sécurité le plus rapide : chaque modification du code est immédiatement confrontée à l'ensemble des comportements attendus. Un test unitaire qui échoue localise le problème à la source, avant qu'il ne se propage dans les couches supérieures. Bien nommés, ils servent aussi de documentation vivante du domaine.

Exemple (Jest / TypeScript) :

TypeScript
test("Connexion refusée si le mot de passe ne contient pas de majuscule", () => { const resultat = verifierConnexion("monemail@chezmoi.fr", "sansmajuscule"); expect(resultat).toBe("Mot de passe invalide"); }); test("Connexion refusée si l'email est vide", () => { const resultat = verifierConnexion("", "motMotDePasse1"); expect(resultat).toBe("L'email est obligatoire"); }); test("Connexion acceptée avec des identifiants valides", () => { const resultat = verifierConnexion("monemail@chezmoi.fr", "motMotDePasse1"); expect(resultat).toBe("Connexion acceptée"); });

---

9. Tests End-to-End

Théorie : Les tests end-to-end (E2E) simulent un vrai utilisateur naviguant dans l'interface. Ils valident que toutes les couches — front-end, back-end, base de données — fonctionnent correctement ensemble. Là où les tests unitaires vérifient chaque règle isolément, les tests E2E vérifient que l'assemblage produit l'expérience attendue. Dans ce projet, les tests E2E reprennent les scénarios BDD écrits en Gherkin et les exécutent dans un vrai navigateur via Playwright.

Exemple (Playwright BDD) :

TypeScript
test("Connexion réussie — accès au tableau de bord", async ({ page }) => { await page.goto("/login"); await page.getByLabel(/email/i).fill("monemail@chezmoi.fr"); await page.getByLabel(/mot de passe/i).fill("motMotDePasse1"); await page.getByRole("button", { name: /se connecter/i }).click(); await expect(page.getByRole("heading", { name: /bienvenue/i })).toBeVisible(); });

---

10. Definition of Done

Théorie : La Definition of Done (DoD) est une liste de critères non négociables que toute User Story doit satisfaire pour être déclarée terminée. Sans elle, « c'est fini » peut signifier « ça compile », « ça marche sur ma machine » ou « j'ai écrit le code mais pas les tests ». La DoD est un contrat partagé entre le développeur, le testeur et le métier : chaque critère est vérifiable, chacun sait ce qu'il couvre. Elle clôt le cycle US → BDD → TDD et garantit qu'aucune étape n'a été escamotée.

Exemple :

text
Definition of Done — à valider avant de fermer la User Story :

✅ Tous les critères d'acceptation de l'US couverts par des scénarios BDD
✅ Couverture de tests ≥ 100 % sur le domaine métier modifié
✅ ESLint passe sans erreur ni warning
✅ Build TypeScript (tsc --noEmit) passe sans erreur
✅ Pas de régression sur la suite complète de tests
✅ Fonctionnalité testée manuellement en staging
✅ Documentation à jour si une API a changé
✅ Code relu par un pair ou par l'IA avec des critères explicites

---

11. Clean Code

Théorie : Le Clean Code est un ensemble de pratiques visant à écrire un code lisible, simple et maintenable. Cela inclut l'utilisation de noms explicites, des fonctions courtes, et une structure claire. Un code propre est plus facile à comprendre, à modifier et à faire évoluer, ce qui réduit les coûts de maintenance et les risques d'erreurs. Dans ce projet, les noms métier sont en français dans le code pour faciliter la lecture et l'échange avec le métier.

Exemple (Login) :

TypeScript
// ❌ Peu clair function verifierConnexion(e: string, p: string): boolean { return e && p.length >= 8 && /[A-Z]/.test(p); } // ✅ Explicite (français métier, noms explicites) function verifierFormatConnexion(email: string, motDePasse: string): boolean { const emailValide = email.includes('@'); const motDePasseRobuste = motDePasse.length >= 8 && /[A-Z]/.test(motDePasse); return emailValide && motDePasseRobuste; }

---

12. Gestion des erreurs et exception handling

Théorie : Erreurs métier (email invalide) ≠ erreurs techniques (BDD down). Les erreurs métier sont attendues, claires pour l'utilisateur. Les erreurs techniques sont loggées pour diagnostic. Une fonction doit expliciter ce qu'elle peut échouer — via une exception typée.

Exemple (Login) :

TypeScript
// ❌ Erreur cachée function connecter(email: string, pwd: string) { try { return depot.trouver(email); } catch (e) { return null; // Quoi ? Pas trouvé ? Erreur réseau ? } } // ✅ Erreurs métier explicites class UtilisateurNonTrouveError extends Error {} class MotDePasseInvalideError extends Error {} function connecter(email: string, pwd: string): Utilisateur { const user = depot.trouver(email); if (!user) throw new UtilisateurNonTrouveError(email); if (!user.verifierMotDePasse(pwd)) throw new MotDePasseInvalideError(); return user; } // Handler HTTP app.post("/login", (req, res) => { try { const user = connecter(req.body.email, req.body.motDePasse); res.json({ user }); } catch (e) { if (e instanceof UtilisateurNonTrouveError || e instanceof MotDePasseInvalideError) { res.status(401).json({ erreur: "Email ou mot de passe incorrect" }); // Métier } else { logger.error("Erreur connexion", e); // Technique res.status(500).json({ erreur: "Erreur serveur" }); } } });

---

13. SOLID

Théorie : Les principes SOLID sont cinq règles de conception orientée objet qui guident l'écriture d'un code modulaire, testable et évolutif. Ils préviennent l'accumulation de dette technique : un code qui « fonctionne » aujourd'hui mais qui devient impossible à modifier ou à tester demain. Ces principes ne sont pas des règles abstraites — ils fondent en pratique l'architecture hexagonale (qui applique le D), les cas d'usage (qui appliquent le S) et les interfaces de port (qui appliquent le I).

| Lettre | Principe | En pratique | |---|---|---| | S | Single Responsibility | Une classe / fonction = une seule raison de changer | | O | Open / Closed | Étendre le comportement sans modifier le code existant | | L | Liskov Substitution | Un sous-type peut remplacer son parent sans comportement inattendu | | I | Interface Segregation | Interfaces petites et ciblées plutôt qu'une interface fourre-tout | | D | Dependency Inversion | Dépendre des abstractions (interfaces), jamais des implémentations concrètes |

Exemple (D — Inversion de dépendance) :

TypeScript
// ❌ Le cas d'usage dépend de SQLite : impossible à tester sans base de données réelle class ConnecterUtilisateur { private db = new SQLiteDatabase(); connecter(email: string, motDePasse: string): Utilisateur { /* ... */ } } // ✅ Le cas d'usage dépend d'une abstraction : testable, remplaçable class ConnecterUtilisateur { constructor(private readonly depot: DepotUtilisateurs) {} connecter(email: string, motDePasse: string): Utilisateur { /* ... */ } }

---

14. YAGNI — Refuser la complexité anticipée

Théorie : YAGNI = "You Ain't Gonna Need It". Refusez d'ajouter des patterns, config, ou features pour des besoins imaginaires. 80% ne verront jamais le jour. La complexité coûte : bugs, maintenance, compréhension. Construisez itérativement : le besoin réel guide la structure. Demain, quand ce besoin existe, vous le couvrirez.

Exemple (Login) :

TypeScript
// ❌ YAGNI violation : config imaginaire class ServiceConnexion { private oauthStrategy: OAuthStrategy; private mfaStrategy: MFAStrategy; private biometricStrategy: BiometricStrategy; // 300 lignes pour "si un jour on en a besoin" } // ✅ YAGNI respecté : juste ce qui existe class ServiceConnexion { connecterUtilisateur(email: string, motDePasse: string): Utilisateur { const user = this.depot.rechercherParEmail(email); if (!user || !user.verifierMotDePasse(motDePasse)) { throw new ConnexionEchoueeError(); } return user; } } // OAuth demain ? On l'ajoute. Pas avant.

---

15. Domain-Driven Design (DDD)

Théorie : Le Domain-Driven Design (DDD) place le domaine métier au centre de la conception logicielle. Il utilise un langage ubiquitaire (partagé par tous les acteurs) pour clarifier les concepts et les processus. Le DDD permet de modéliser le logiciel en fonction des besoins métiers, ce qui facilite la communication et réduit les ambiguïtés. Utiliser un langage métier en français dans le code (variables, fonctions, types) renforce cette clarté.

Exemple (Login) :

TypeScript
// ❌ Ambigu const e = "test@test.fr"; const u = null; const p = "secret"; // ✅ Clair (langage métier explicite) const emailUtilisateur = "test@test.fr"; const utilisateurTrouve = null; const motDePasseUtilisateur = "secret123";

---

16. Langage ubiquitaire (Ubiquitous Language)

Théorie : Le DDD insiste sur un langage partagé entre tous (métier, dev, design). Ce langage vit dans le code : variables, fonctions, événements, tout parle du métier. En français ici. Pas de jargon technique qui cache le domaine. Un utilisateur n'est pas "user" ou "person", c'est un Utilisateur. Une connexion n'est pas un "login" ou "auth", c'est une Connexion. Quand métier et dev parlent le même langage, les bugs disparaissent (malentendu = zéro).

Exemple (Login) — L'ambiguïté :

TypeScript
// ❌ Mauvais : jargon technique mélangé, ambigu class UserService { authenticate(credentials: any) { const person = this.db.findPerson(credentials.email); if (!person) return null; // person.password ? person.pwd ? person.hash ? return { token: jwt.sign(person) }; } } // Qui est "person" ? C'est un User en base ? Un enregistrement ? Une personne humaine ? // ✅ Bon : langage métier explicite (ubiquitaire) class ServiceConnexion { connecterUtilisateur(identifiants: Identifiants): UtilisateurConnecte { const utilisateur = this.depot.rechercherParEmail(identifiants.email); if (!utilisateur) throw new UtilisateurNonTrouveError(identifiants.email); if (!utilisateur.motDePasseCorrespond(identifiants.motDePasse)) throw new MotDePasseInvalideError(); return new UtilisateurConnecte(utilisateur); } } // Événement domaine : langage du métier type EvenementConnexion = | { type: 'UtilisateurConnecte'; uid: string; email: string; dateConnexion: Date } | { type: 'TentativeConnexionEchouee'; email: string; raison: 'email_absent' | 'pwd_invalide' };

---

17. Architecture Hexagonale

Théorie : L'architecture hexagonale (ou *Ports & Adapters*) pose une règle structurelle simple : les dépendances vont toujours de l'extérieur vers le centre, jamais l'inverse. Le cœur métier ne sait pas qu'il tourne sur le web, dans une CLI ou via une API — c'est aux adaptateurs (les couches extérieures) de s'adapter à lui. Cette règle garantit que le domaine est testable sans démarrer de serveur ni de base de données, et que l'on peut changer de canal (web → mobile) ou de technologie (Postgres → SQLite) sans toucher à la logique métier.

Exemple (TypeScript) :

TypeScript
// Port : contrat que le domaine expose (interface) interface DepotUtilisateurs { trouverParEmail(email: string): Utilisateur | undefined; } // Adaptateur web : connaît HTTP, appelle le port, ignore le domaine app.post("/login", (req: Request, res: Response) => { const { email, motDePasse } = req.body; const resultat = serviceConnexion.connecter(email, motDePasse); res.status(resultat.succes ? 200 : 401).json({ message: resultat.message }); }); // L'adaptateur SQLite implémente le port — le domaine ne le connaît pas class DepotUtilisateursSQLite implements DepotUtilisateurs { trouverParEmail(email: string): Utilisateur | undefined { return db.prepare("SELECT * FROM users WHERE email = ?").get(email); } }

---

18. Cas d'usage (Use Cases)

Théorie : Un cas d'usage est l'unité de logique qui vit à l'intérieur de l'hexagone. Il orchestre les règles métier pour répondre à une intention précise de l'utilisateur. Il ne sait rien d'HTTP, de SQL ni de l'interface graphique — il reçoit des données métier, applique les règles, et retourne un résultat métier. Il dépend uniquement de ports (interfaces), jamais d'implémentations concrètes. C'est ce code que les tests unitaires couvrent en priorité.

Exemple (TypeScript) :

TypeScript
// Le cas d'usage n'importe ni Express ni SQLite class ConnecterUtilisateur { constructor(private readonly depot: DepotUtilisateurs) {} connecter(email: string, motDePasse: string): Utilisateur { if (!email) throw new EmailObligatoireError(); const utilisateur = this.depot.trouverParEmail(email); if (!utilisateur) throw new EmailNonTrouveError(); if (!utilisateur.motDePasseCorrespond(motDePasse)) { throw new MotDePasseInvalideError(); } return utilisateur; } }

---

19. CQRS (Command Query Responsibility Segregation)

Théorie : Le CQRS sépare les commandes (écriture, modification d'état) des requêtes (lecture). Dans une architecture classique, un seul modèle gère les deux — ce qui force des compromis permanents entre performance de lecture et rigueur métier en écriture. CQRS définit deux chemins distincts :

  • Commandes : intention de changer l'état (`ConnecterUtilisateur`, `ChangerMotDePasse`) — gérées par un `CommandHandler`, passent par le domaine et ses règles métier.
  • Requêtes : demande d'information sans modification (`ObtenirProfilUtilisateur`) — gérées par un `QueryHandler`, peuvent court-circuiter le domaine pour aller directement vers une vue optimisée.

CQRS s'intègre naturellement dans l'architecture hexagonale : les commandes sont des cas d'usage (côté écriture), les requêtes sont des adaptateurs de lecture qui ne traversent pas les règles métier.

Exemple (TypeScript) :

TypeScript
// Commande : intention métier explicite class ConnecterUtilisateurCommande { constructor( public readonly email: string, public readonly motDePasse: string ) {} } class ConnecterUtilisateurHandler { constructor(private readonly depot: DepotUtilisateurs) {} executer(commande: ConnecterUtilisateurCommande): Utilisateur { const utilisateur = this.depot.trouverParEmail(commande.email); if (!utilisateur?.motDePasseCorrespond(commande.motDePasse)) { throw new MotDePasseInvalideError(); } return utilisateur; } } // Requête : lecture directe, sans passer par le domaine class ObtenirProfilUtilisateurQuery { constructor(public readonly utilisateurId: string) {} } class ObtenirProfilUtilisateurHandler { constructor(private readonly vue: VueUtilisateurs) {} executer(query: ObtenirProfilUtilisateurQuery): ProfilUtilisateurDTO { return this.vue.trouverParId(query.utilisateurId); } }

---

20. Event Sourcing (ES)

Théorie : Avec l'Event Sourcing, l'état d'un agrégat n'est pas stocké comme une ligne en base de données, mais comme une séquence d'événements immuables. Pour connaître l'état actuel d'un utilisateur, on rejoue tous les événements qui l'ont concerné : `UtilisateurCree`, `MotDePasseModifie`, `ConnexionEffectuee`. Chaque événement est un fait passé, immuable, nommé au passé dans le langage du domaine.

Avantages :

  • Audit trail complet : tout l'historique est conservé, sans perte
  • Time travel : reconstruire l'état à n'importe quel instant passé
  • Découplage : les systèmes réagissent aux événements sans couplage fort
  • Lisibilité métier : les événements parlent le langage du domaine

Event Sourcing se combine naturellement avec CQRS : les commandes produisent des événements, les projections (vues de lecture) sont construites en rejouant ces événements.

Exemple (TypeScript) :

TypeScript
// Événements domaine : faits immuables, nommés au passé interface UtilisateurCree { type: 'UtilisateurCree'; email: string; dateCreation: Date; } interface ConnexionEffectuee { type: 'ConnexionEffectuee'; dateConnexion: Date; } type EvenementUtilisateur = UtilisateurCree | ConnexionEffectuee; // L'agrégat se reconstruit en rejouant ses événements class Utilisateur { private email: string = ''; private dateConnexion?: Date; appliquer(evenement: EvenementUtilisateur): void { switch (evenement.type) { case 'UtilisateurCree': this.email = evenement.email; break; case 'ConnexionEffectuee': this.dateConnexion = evenement.dateConnexion; break; } } static reconstruireDepuis(evenements: EvenementUtilisateur[]): Utilisateur { const utilisateur = new Utilisateur(); evenements.forEach((e) => utilisateur.appliquer(e)); return utilisateur; } }

---

21. Documentation des API

Théorie : La documentation des API explique comment interagir avec le back-end. Elle utilise des standards comme OpenAPI (ex-Swagger) pour décrire les endpoints, les paramètres et les réponses. Une documentation claire permet aux développeurs front-end ou aux partenaires externes de consommer les API sans ambiguïté, ce qui facilite l'intégration et réduit les erreurs.

Exemple (OpenAPI / YAML) : voir la spécification exposée sur ce site via le module API OpenAPI.

---

22. Documentation du code

Théorie : Code clair réduit le besoin de commentaires — mais ne l'élimine pas. Règle : code parle du "quoi", commentaires du "pourquoi". Pas de redondance. Docstrings pour contrats publics. Expliciter : pourquoi ce seuil ? Pourquoi cette exception ?

Exemple (Login) :

TypeScript
// ❌ Commentaire redondant function verifierMotDePasse(pwd: string, hash: string): boolean { // vérifier si le mot de passe correspond au hash return bcrypt.compare(pwd, hash); } // ✅ Code clair + "pourquoi" function verifierMotDePasse(pwd: string, hash: string): boolean { // Comparaison avec bcrypt pour éviter timing attacks. return bcrypt.compareSync(pwd, hash); } // ✅ Docstring : contrat public /** * Connecter un utilisateur avec email et mot de passe. * @throws {UtilisateurNonTrouveError} Si email n'existe pas en base * @throws {MotDePasseInvalideError} Si mot de passe incorrect */ export function connecterUtilisateur(email: string, motDePasse: string): Utilisateur { }

---

23. Front-End

Théorie : Le front-end adapte l'interface utilisateur au canal de diffusion (web, mobile, etc.). Il s'appuie sur les données fournies par le back-end pour offrir une expérience utilisateur optimisée. Chaque canal a ses propres contraintes techniques et nécessite un code spécifique pour répondre aux attentes des utilisateurs.

Exemple (React / TypeScript) :

TSX
function LoginForm() { const [email, setEmail] = useState(""); const [motDePasse, setMotDePasse] = useState(""); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // Appel à l'API de connexion }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={motDePasse} onChange={(e) => setMotDePasse(e.target.value)} /> <button type="submit">Se connecter</button> </form> ); }

---

24. Séparation contenu/présentation

Théorie : La séparation entre contenu et présentation consiste à distinguer la structure sémantique (DOM HTML) de son style visuel (CSS). Cela permet de modifier l'apparence sans altérer la logique ou le contenu, et facilite la collaboration entre développeurs et designers. Le contenu (DOM) décrit ce que c'est, tandis que le CSS décrit comment ça s'affiche. Sur ce site, la Charte graphique formalise ces choix de présentation.

Exemple (HTML/CSS) :

HTML
<!-- Contenu sémantique (DOM) --> <div class="login-form"> <h1>Connexion</h1> <form> <input type="email" placeholder="Email" /> <input type="password" placeholder="Mot de passe" /> <button>Se connecter</button> </form> </div>
CSS
/* Présentation (CSS) */ .login-form { background: #f5f5f5; padding: 20px; } .login-form button { background: blue; color: white; }

---

25. CI/CD

Théorie : Le CI/CD (Intégration et Livraison Continues) automatise les tests et les déploiements. Cela permet de livrer rapidement les nouvelles fonctionnalités aux utilisateurs, tout en garantissant leur qualité. Le code est toujours prêt à être utilisé, ce qui facilite la collecte de feedback pour identifier sur des ajustements sont nécessaires.

Exemple (Login — GitHub Actions) :

YAML
name: CI/CD Pipeline — Service Connexion on: [push] env: NODE_VERSION: 18 jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npm install - run: npm run test # Tests login: ServiceConnexion, erreurs - run: npm run test:bdd # BDD login: scénarios Connexion deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npm run build - run: npm run deploy

---

26. Versionning et gestion des dépendances

Théorie : Semver (MAJOR.MINOR.PATCH) exprime clairement : MAJOR = breaking change, MINOR = feature rétro-compatible, PATCH = bugfix. Les dépendances doivent être mises à jour et testées régulièrement. Les breaking changes doivent être documentés (changelog). Auditer les vulnérabilités (`npm audit`) fait partie de la maintenance.

Exemple (Login) :

json
{
  "version": "1.2.3",
  "dependencies": {
    "bcrypt": "^5.1.0",
    "jsonwebtoken": "^9.0.0"
  }
}

CHANGELOG

[1.2.3] - PATCH

  • Fix : timing attack sur vérification mot de passe

[1.2.0] - MINOR

  • Feature : ajouter JWT pour sessions (rétro-compatible)

[1.0.0] - MAJOR

  • Breaking : colonne SQL `users.password_hash` → `users.motDePasseHash`

(migration CLI requise)

27. Git workflow et conventions de commits

Théorie : Un historique Git clair racontne l'histoire du code. Conventional Commits : préfixe (`feat:`, `fix:`, `refactor:`, `docs:`) + raison (le pourquoi, pas le quoi). Commits atomiques (une idée = un commit). Branches courtes. Cela facilite les revues et les diagnostics.

Exemple (Login) :

✅ Bon

git commit -m "fix: corriger timing attack sur vérification mot de passe

bcrypt.compare est async, on utilisait compareSync (bloquant). Passer à version async du handler pour éviter.

Fixes #42"

❌ Mauvais

git commit -m "fix: login stuff"

28. Documentation technique détaillée

Théorie : La documentation technique détaillée décrit comment les couches communiquent entre elles. Elle est essentielle pour que les développeurs et designers puissent travailler efficacement dans leur domaine. Elle inclut des détails sur les endpoints API, la structure du DOM, et les classes CSS disponibles.

Exemple (Markdown) : sur ce site, la documentation technique détaillée est accessible via le module Documentation technique.

---

29. Sprint en cours et principes Kanban

Théorie : Le tableau Sprint en cours de cette page applique les principes du Kanban : visualisation du flux de travail, colonnes correspondant aux étapes (agents du tunnel US → BDD → TDD-back-end → TDD-front-end), et cartes représentant les User Stories. Le workflow est explicite — chaque colonne correspond à une étape du processus — et une limite de WIP (Work In Progress) peut être appliquée pour éviter la surcharge et favoriser la finition des tâches avant d'en entamer de nouvelles. Sur ce site, le module Sprint en cours affiche le board du sprint courant et permet d'ouvrir le détail d'une US en cliquant sur un post-it.

---

30. Configuration IA et pair programming avec l'IA

Théorie : Un projet en pair programming avec l'IA a des spécificités : les règles de qualité, de nommage et de workflow doivent être explicites et stables pour que l'assistant produise un code cohérent. La configuration des règles (fichiers de consignes, Definition of Done) et la spécialisation des agents (US, BDD, TDD-back-end, TDD-front-end, Designer) permettent de limiter le périmètre de chaque tâche et d'éviter la dilution du contexte. La contrainte de taille du contexte des modèles impose de donner à l'IA un cadre clair et ciblé plutôt qu'un mélange de sujets ; des agents dédiés avec des prompts spécialisés y répondent. Sur ce site, le module Configuration IA décrit les agents et les règles utilisées.

---

31. Métriques : management visuel des délivrables

Théorie : Les métriques offrent un management visuel de l'avancement et de la qualité des délivrables : couverture de tests (unitaires, intégration, E2E), qualité du code (linter), taille du code, dépendances, performance. Les afficher de façon centralisée et lisible permet de prendre des décisions basées sur des faits et de détecter rapidement les dérives. Sur ce site, le module Métriques regroupe ces indicateurs (tests, couverture, qualité, version) dans un tableau de bord compact.

---

32. Maintenabilité locale (setup et onboarding)

Théorie : Setup doit être rapide : clone → npm install → npm run dev = ça roule. Prérequis explicites (Node 18+, etc.). Doc troubleshooting courte (port occupé ? dépendances ?). Sans ça, chaque nouveau dev perd 2h. Une bonne doc locale = zéro friction.

Exemple (Login) :

Setup

\`\`\`bash git clone ... && cd job-joy && npm install && npm run dev

→ http://localhost:3001/login

\`\`\`

Prérequis

  • Node 18+, npm 9+
  • (Optionnel) Base SQLite créée par migration au premier démarrage

Tests login

  • \`npm run test\` : units (ServiceConnexion, vérification mot de passe)
  • \`npm run test:bdd\` : E2E (scénario login échoué/réussi)

Troubleshooting

  • Login échoue ? Vérifier que SQLite \`users.db\` existe
  • JWT invalid ? Vérifier secret dans \`.env.local\`

33. Observabilité

Théorie : En production, on ne peut pas attacher un débogueur ni rejouer une exécution pas à pas. Sans observabilité, un problème peut persister des heures sans être détecté, et le diagnostic repose sur des suppositions plutôt que sur des faits. L'observabilité s'appuie sur trois piliers : les logs (que s'est-il passé ?), les métriques (à quelle fréquence, avec quelle latence ?) et les alertes (quand un seuil critique est-il dépassé ?). Un log non structuré est illisible par un outil automatique — et donc inutile en production.

Exemple (TypeScript) :

TypeScript
// ❌ Log non structuré : illisible par un outil d'agrégation console.log("Erreur connexion !"); // ✅ Log structuré : niveau, contexte, timestamp — exploitable par Sentry, Datadog… logger.error("Échec de connexion", { email: masquer(email), tentative: 3, timestamp: new Date().toISOString(), requestId: req.id, });

---

34. Logging et débogage pendant le développement

Théorie : Logging en dev ≠ prod. En dev, `console.log` pour comprendre le flux. En prod, logs structurés pour Sentry/Datadog. Debuggers (VSCode, DevTools) plus puissants qu'un log — apprenez à les utiliser (breakpoints, watch expressions).

Exemple (Login) :

TypeScript
// Dev : console.log pour déboguer console.log("🔐 Tentative connexion", { email: req.body.email }); const user = depot.rechercherParEmail(email); console.log("👤 Utilisateur trouvé", { uid: user?.uid }); // Prod : logs structurés logger.info("Connexion utilisateur", { uid: user.uid, email: masquer(email), timestamp: now(), requestId: req.id, }); // Déboguer dans VSCode : breakpoint à "if (!user)" function connecter(email: string, pwd: string) { const user = depot.rechercherParEmail(email); // Breakpoint ici if (!user) throw new UtilisateurNonTrouveError(); }

---

35. Annexes

Mon parcours : développeur, puis décideur

Je suis un ancien développeur, avec une expérience professionnelle allant de 1995 à 2020, principalement sur l'environnement 4D. Ce contexte m'a permis de construire de très solides bases en :

  • modélisation de données
  • conception de bases de données relationnelles
  • SQL (un peu rouillé)
  • algorithmie et raisonnement logique

En revanche, mon parcours ne m'a pas exposé directement à :

  • la programmation orientée objet « pure »
  • les design patterns
  • les frameworks modernes
  • la culture du code issue du software craft

Dès 2010, mon entreprise a financé et j'ai dirigé une équipe de développeurs C#. Nous avons connu un échec marquant : en trois ans, le projet est devenu un monolithe spaghetti… que nous avons fini par jeter. Cette expérience a été déterminante. Elle a conduit l'équipe à une remise en question profonde, puis à l'adoption progressive de pratiques solides :

  • BDD & TDD
  • Clean Code
  • DDD
  • architecture hexagonale
  • CQRS et Event Sourcing

Aujourd'hui, en tant qu'ancien éditeur de logiciel, j'ai expérimenté la différence entre du code qui fonctionne et du code de qualité.

Mes arbitrages

  • IDE : Cursor
  • Langage : TypeScript
  • Frontend : Next.js
  • Gestion du code : GitHub
  • TDD : Jest
  • BDD : Cucumber.js
  • Hébergement : Vercel

Ce que j'ai fait

  • Installer Cursor
  • Installer Git
  • Installer NodeJS
  • Créer un compte GitHub (que j'ai associé à Cursor)
  • Créer un compte Vercel (où j'ai créé un projet associé à mon projet sous GitHub)
  • Mon premier promt dans Cursor a été de lui demander d'afficher un "Hello World"
  • Après, je n'ai pas arrêté d'enchainer les prompts :

. pour faire émerger petit à petit de nouveaux contenus sur le site . pour lui demander de refactoriser le code . pour lui imposer des BDD et TDD . pour recevoir une formation sur la syntaxe en TypeScript et le comportement de Next.JS