Plan de l'article
- Introduction
- Product Goal
- Exprimer un besoin (User Story)
- Conserver les spécifications (BDD)
- Faire émerger le code (TDD)
- TDD Strict (Red-Green-Refactor)
- Refactoring sécurisé
- Tests unitaires
- Tests End-to-End
- Definition of Done
- Clean Code
- Gestion des erreurs et exception handling
- SOLID
- YAGNI — Refuser la complexité anticipée
- Domain-Driven Design (DDD)
- Langage ubiquitaire (Ubiquitous Language)
- Architecture Hexagonale
- Cas d'usage (Use Cases)
- CQRS (Command Query Responsibility Segregation)
- Event Sourcing (ES)
- Documentation des API
- Documentation du code
- Front-End
- Séparation contenu/présentation
- CI/CD
- Versionning et gestion des dépendances
- Git workflow et conventions de commits
- Documentation technique détaillée
- Sprint en cours et principes Kanban
- Configuration IA et pair programming avec l'IA
- Métriques : management visuel des délivrables
- Maintenabilité locale (setup et onboarding)
- Observabilité
- Logging et débogage pendant le développement
- Annexes
---
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 :
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 :
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) :
// 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) :
// É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) :
// 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) :
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) :
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 :
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) :
// ❌ 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) :
// ❌ 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) :
// ❌ 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) :
// ❌ 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) :
// ❌ 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é :
// ❌ 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) :
// 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) :
// 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) :
// 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) :
// É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) :
// ❌ 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) :
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) :
<!-- 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>/* 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) :
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) :
{
"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) :
// ❌ 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) :
// 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
