Solutions — série 10-P (le projet final)
Rappel de la règle d'or : on n'ouvre ce fichier qu'après avoir travaillé la phase correspondante. Particularité absolue de cette série : il n'existe pas de correction d'un projet personnel — ces pages sont des exemples de RÉFÉRENCE (fil rouge :
spendlog, le suivi de dépenses cadré en 10-1) pour comparer la STRUCTURE de ton travail : rythme des merges, taille des issues, honnêteté du bilan. Jamais le contenu — ton projet ne ressemble à aucun autre, c'est le but. Tes juges réels : la checklist « projet fini » deprojects/mini-projects/12-projet-final-cli.md, le test du dossier vierge, et ton bilan.
Exercice facile a — installer le cadrage
Raisonnement — Copie des documents 10-1 dans le dossier projet, dernière relecture avec la grille, et gel de la définition de « fini ». Le seul piège est de « profiter » de la copie pour regonfler le périmètre.
Solution — l'état attendu après la session :
projects/final-projects/spendlog/
SPECS.md ← relu, exigences numérotées, hors-périmètre daté
ROADMAP.md ← v0.1 / v0.2 / v1.0
ISSUES.md ← v0.1 découpée en 4-6 issues cochables
git add projects/final-projects/spendlog/
git commit -m "project: add spendlog specs, roadmap and issues"
(Ce commit-là peut se faire sur main : c'est du cadrage, pas une feature — la discipline de branche commence avec le code.)
Pourquoi ça marche — Le cadrage versionné AVANT le code est une exigence de processus de la fiche 12 : l'historique prouvera que les specs précèdent l'implémentation, et chaque mise à jour du cadrage laissera un diff lisible (« qu'est-ce qui a changé entre mes intentions et la réalité ? » devient une question à laquelle git log SPECS.md répond).
Erreur classique sur cet exercice — Retoucher « deux-trois petites choses » du périmètre pendant la copie, sans repasser par la grille 10-1. Toute retouche du cadrage suit la même règle que pendant le projet : ligne datée, motif. Sinon le gonflement commence avant même le premier commit.
Variante plus difficile — Ouvre de vraies GitHub Issues au lieu du fichier ISSUES.md (une issue par ligne, labels v0.1/v0.2). Tu fermeras chaque issue depuis le message de commit (closes #3) — le niveau au-dessus en traçabilité, et le geste exact du travail en équipe.
Exercice facile b — le squelette qui tourne
Raisonnement — Objectif unique : passer de « rien » à « un programme qui répond + un test vert + un merge », en UNE session. Tout ce qui n'est pas nécessaire à ça attend.
Solution — le squelette de référence, commenté :
spendlog/
__init__.py # vide — marque le paquet
cli.py # argparse : LA porte d'entrée
storage.py # vide pour l'instant (il EXISTE : la structure est posée)
tests/
test_smoke.py
SPECS.md ROADMAP.md ISSUES.md README.md
# cli.py — le minimum qui répond
import argparse
def build_parser():
parser = argparse.ArgumentParser(prog="spendlog",
description="Track daily expenses.")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("version", help="show version")
return parser
def main(argv=None):
args = build_parser().parse_args(argv)
if args.command == "version":
print("spendlog 0.0.1")
if __name__ == "__main__":
main()
# tests/test_smoke.py — trivial ET structurant
from spendlog.cli import build_parser
def test_parser_accepts_version_command():
args = build_parser().parse_args(["version"])
assert args.command == "version"
git switch -c feat/project-skeleton
git add projects/final-projects/spendlog/
git commit -m "project: add spendlog skeleton with argparse and first test"
git switch main
git merge feat/project-skeleton
git branch -d feat/project-skeleton
Pourquoi ça marche — Deux choix invisibles mais décisifs : main(argv=None) + build_parser() séparé rendent le CLI testable sans lancer de processus (on passe argv en liste — la leçon du niveau 08 appliquée à argparse) ; et le storage.py vide pose la frontière logique/interface dès le jour 1, celle que json/tool.py t'a montrée en 10-3. Le test est trivial EXPRÈS : sa valeur n'est pas ce qu'il vérifie, c'est que pytest tourne vert dès le premier jour — l'infrastructure de test existe AVANT d'en avoir besoin.
Erreur classique sur cet exercice — Le squelette-cathédrale : cinq modules, trois classes, zéro fonctionnalité — la session y passe et rien ne répond. Le squelette se juge à UN critère : python -m spendlog version répond et pytest est vert. Tout raffinement de structure viendra quand une issue l'exigera.
Variante plus difficile — Ajoute --help au test : vérifie que chaque sous-commande enregistrée a un help= non vide (en itérant sur les sous-parseurs). Ce test-là attrapera automatiquement chaque future sous-commande ajoutée sans documentation — un garde-fou qui travaille pour toi jusqu'au tag.
Exercice facile c — la première vraie issue
Raisonnement — Répéter la session type de la leçon (exemple 2) sur l'issue #1, en soignant le rituel : c'est lui qu'on rejouera vingt fois.
Solution — la trace de référence d'une session réussie (issue #1 : « add 12.50 courses écrit une ligne dans expenses.csv ») :
git switch -c feat/add-command
# 1. le test d'abord (rouge) :
# tests/test_add.py::test_add_appends_expense_row → FAILED
# 2. code : storage.append_expense() + branchement argparse → PASSED
# 3. le vrai lancement :
python -m spendlog add 12.50 courses
# → added: 2026-07-08 12.50 courses
git add projects/final-projects/spendlog/
git commit -m "project: add expense recording via add command"
git switch main
git merge feat/add-command # Fast-forward
git branch -d feat/add-command
# 4. ISSUES.md : [x] #1 — commit "project: mark issue 1 done"
Pourquoi ça marche — La session contient les cinq temps dans l'ordre : branche → test rouge → code jusqu'à vert → usage réel → merge + issue cochée. Le temps 3 (lancer l'outil pour de vrai) est celui qu'on saute le plus volontiers et celui qui attrape ce que pytest ne voit pas : message moche, sortie illisible, oubli du cas « fichier absent au premier lancement ».
Erreur classique sur cet exercice — Finir la session « à 90 % » : le code marche mais le test n'est pas écrit, ou le merge attendra demain. Une issue à 90 % est une issue à 0 % mergée : elle rouvre la session suivante sur du rangement au lieu d'une victoire. Petit, FINI, mergé.
Variante plus difficile — Chronomètre la session. Si elle dépasse nettement ta durée type, note POURQUOI au journal (issue trop grosse ? test difficile à écrire ? — ce deuxième cas est souvent un signe de code mal découpé, pas de test difficile).
Exercice moyen a — livrer v0.1
Raisonnement — Enchaîner les issues au rythme une session = une issue. La « solution » est le point de contrôle objectif de fin de jalon.
Solution — les trois vérifications de fin de v0.1 :
# 1. l'historique a la bonne forme (que des bosses, zéro commit de
# feature directement sur main) :
git log --oneline --graph
* merge feat/list-command
|\
| * project: add list command with newest-first order
|/
* merge feat/add-validation
|\
| * fix: reject invalid amounts in add command
|/
* merge feat/add-command
|\
| * project: add expense recording via add command
|/
* project: add spendlog skeleton with argparse and first test
* project: add spendlog specs, roadmap and issues
# 2. la machine est verte :
pytest # tous les tests passent, sur main
# 3. le juge de paix du jalon :
# « ce soir, est-ce que je m'en sers pour de vrai ? » — OUI attendu.
(Ton graphe peut montrer des Fast-forward sans bosse si main n'a pas bougé entre deux features — c'est sain aussi ; l'important est l'absence de commits de feature directs sur main. Pour garantir une bosse par feature, certains utilisent merge --no-ff — vu en solutions 10-2.)
Pourquoi ça marche — Les trois vérifications couvrent les trois axes du jalon : discipline (le graphe), qualité (pytest sur main), et VALEUR (l'usage réel). Les deux premières sont mécaniques ; la troisième est celle qui décide si la revue de mi-parcours aura de la matière.
Erreur classique sur cet exercice — S'apercevoir au milieu du jalon qu'on a commité deux features directement sur main (le vieux réflexe) et tenter de réécrire l'historique pour « faire propre ». Non : la discipline se rattrape à la PROCHAINE session (cas A/B de la leçon 10-2 si c'est en cours ; sinon on note au journal et on continue en branche). Un historique imparfait et honnête vaut mieux qu'une chirurgie hasardeuse.
Variante plus difficile — Avant de passer en v0.2, mesure ton calibrage : nombre de sessions RÉELLES du jalon vs l'estimation du cadrage. Le ratio (souvent proche de 2…) est TA constante personnelle — utilise-la pour recalibrer v0.2 et v1.0 dans ROADMAP.md, ligne datée.
Exercice moyen b — la revue de mi-parcours
Raisonnement — Utiliser l'outil sans coder, noter ce qui agace, et laisser l'usage réel mettre à jour le cadrage — y compris en coupant.
Solution — l'exemple de référence d'une coupe bien tracée, les deux artefacts :
Dans SPECS.md :
## Hors périmètre v1
- Interface graphique (V2 ? probablement jamais)
- Modification d'une dépense (V2 — noté le 2026-07-05)
- [COUPÉ le 2026-07-19] budgets par catégorie avec alertes : prévu en
v1.0 (exigence 7), déplacé en V2 — trois sessions estimées, et deux
semaines d'usage montrent que `report` couvre le besoin réel.
- [AJOUTÉ le 2026-07-19 en v0.2] `add` accepte "hier" comme date : en
usage réel, je saisis souvent la veille au matin. Une session.
Dans le journal :
# Journal — 2026-07-19 — revue de mi-parcours spendlog
3 jours d'usage réel. Ce qui agace : devoir saisir la date d'hier en
entier (corrigé : nouvelle issue v0.2), l'ordre de `list` (déjà bien).
Ce qui ne sert pas : je n'ai JAMAIS eu envie des alertes budget →
coupé de v1.0, tracé dans SPECS. Le projet reste livrable pour le 26.
Pourquoi ça marche — La revue a produit les trois effets attendus : une coupe (motivée par l'usage, pas par la fatigue), un ajout CALIBRÉ (une session, en v0.2 — pas une dérive : c'est l'usage réel qui l'exige), et une date de livraison reconfirmée. Remarque la symétrie : couper ET ajouter passent par le même circuit tracé — le cadrage vivant fonctionne dans les deux sens, sous contrôle.
Erreur classique sur cet exercice — Sauter la revue « parce que je suis lancé » : c'est précisément quand on est lancé qu'on fonce dans des features que l'usage aurait invalidées. Trois jours d'usage coûtent zéro session de code et en économisent trois. L'autre erreur : une revue qui n'ose rien couper — si RIEN ne sort du périmètre à mi-parcours, ou bien le cadrage était parfait (rare), ou bien la règle ×2 attend en embuscade.
Variante plus difficile — Donne l'outil à quelqu'un d'autre pendant la revue (même 10 minutes, même un proche non-technique) et regarde-le faire SANS l'aider. Chaque hésitation de sa part est une issue potentielle de v0.2 — et un avant-goût du test du dossier vierge.
Exercice moyen c — la passe « aucun traceback »
Raisonnement — Bombarder l'outil d'entrées absurdes ; chaque traceback devient un test + un correctif, dans cet ordre, sur une branche fix/.
Solution — la liste d'essais de référence (à adapter à ton outil) et le circuit d'un traceback trouvé :
LISTE D'ESSAIS spendlog (extrait — la tienne vit dans tests/abuse-list.md) :
spendlog → aide, pas traceback (argparse gère)
spendlog add → erreur propre « montant manquant »
spendlog add abc courses → « montant invalide : abc »
spendlog add -- -5 courses → « montant négatif refusé »
spendlog add 12,50 courses → piège FR : virgule décimale !
spendlog report 2026-13 → « mois invalide »
spendlog list (fichier absent) → liste vide, fichier créé, pas de crash
expenses.csv rempli de texte au hasard → message clair, fichier préservé
Le circuit, quand spendlog add 12,50 courses crache un traceback :
git switch -c fix/comma-decimal-amount
# 1. D'ABORD le test qui reproduit :
# def test_add_accepts_comma_decimal(): ... → FAILED (rouge)
# 2. PUIS le correctif (normaliser "12,50" → "12.50" avant float())
# → PASSED
git commit -am "fix: accept comma as decimal separator in amounts"
git switch main && git merge fix/comma-decimal-amount
Pourquoi ça marche — Le test AVANT le correctif garantit deux choses : que le bug est bien compris (le test rouge le prouve), et qu'il ne reviendra jamais silencieusement (le test reste). La liste d'essais versionnée devient l'exigence « aucun traceback » sous forme vérifiable — et un document qu'on enrichit à chaque bug de la vraie vie.
Erreur classique sur cet exercice — Corriger d'abord et « tester après » (donc jamais), ou corriger au symptôme : attraper Exception autour de tout le main() pour faire taire les tracebacks. Ce dernier est le pire : les VRAIS bugs deviennent invisibles aussi. On attrape précis (ValueError sur la conversion, FileNotFoundError sur l'ouverture — niveau 05), jamais en couverture générale.
Variante plus difficile — Ajoute à ta liste les cas hostiles de l'ENVIRONNEMENT, pas seulement des arguments : fichier de données en lecture seule, dossier supprimé pendant l'exécution, CSV de 100 000 lignes (performance). Décide consciemment lesquels tu traites et lesquels tu documentes comme limites connues dans le README — les deux sont des réponses de pro.
Exercice difficile a — la traversée jusqu'au tag
Raisonnement — Pas de solution : c'est ton projet, ses semaines, ses creux. Voici les points de contrôle de la fin de traversée, et l'exemple de la toute fin.
Solution — la séquence finale de référence :
# la checklist de la fiche 12 est cochée, SANS indulgence ; alors :
git switch -c chore/release-v1
# dernière passe README (test du dossier vierge FAIT, pas imaginé)
git commit -am "docs: finalize README for v1.0"
git switch main && git merge chore/release-v1
git tag v1.0
git push
git push --tags
Et le test du dossier vierge, littéralement :
cd /tmp # n'importe où, HORS du projet
git clone https://github.com/<toi>/spendlog.git fresh-spendlog
cd fresh-spendlog
# suivre le README ligne par ligne, RIEN d'autre :
# installation, premier lancement, un exemple de chaque commande.
# Toute commande qui échoue = un bug du README à corriger avant le tag.
Pourquoi ça marche — Le tag est volontairement bureaucratique : c'est un geste FROID, déclenché par une checklist et non par un sentiment. C'est la protection contre le piège n°5 (polir sans fin) : le jour où la checklist est verte, le tag part — le perfectionnisme n'a plus voix au chapitre. Et le test du dossier vierge est le seul juge honnête du README : toi, tu connais trop le projet pour le lire vraiment.
Erreur classique sur cet exercice — Faire le test du dossier vierge « mentalement » (« oui oui, ça marcherait ») : le clone réel révèle TOUJOURS quelque chose — une dépendance non listée, un chemin en dur, une commande d'exemple périmée. L'autre classique : tout relire une dernière fois « avant de publier » et repartir pour trois jours de retouches — la checklist a déjà statué ; publie.
Variante plus difficile — Les extensions de la fiche 12, dans l'ordre de rentabilité : GitHub Actions qui lance pytest à chaque push (une heure, et ton repo affiche un badge de CI — c'est l'extension 3), puis pyproject.toml + pip install -e . (l'extension 2). APRÈS le bilan, jamais à la place.
Exercice difficile b — le bilan rétrospectif
Raisonnement — La dernière page du parcours. Pas de bonne réponse, mais un exemple de référence COMPLET, pour situer le niveau d'honnêteté attendu.
Solution — un bilan de référence :
# Journal — 2026-07-26 — bilan spendlog v1.0 (fin du parcours)
## Les chiffres
19 sessions sur 21 jours. 14 issues fermées, 2 coupées (tracées).
31 commits, 11 branches mergées, 1 conflit résolu (sans drame — la
leçon 10-2 avait raison sur ce point). Tag v1.0 poussé ce matin.
## Ce qui a pris plus de temps que prévu, et pourquoi
- Le parsing des montants : 3 sessions au lieu de 1. La virgule décimale
française, les espaces, le « 12.5€ » collé — je n'avais pas imaginé
la moitié des formes que MOI-MÊME je tape. Leçon : les entrées
utilisateur sont toujours plus sales que prévu, même avec un seul
utilisateur.
- Le README : 2 sessions. Écrire pour un inconnu est un vrai travail.
## Ce que je referais autrement
- Tester storage.py isolément dès le jour 2 : j'ai déboggé à travers
argparse des bugs qui étaient dans le stockage (2 sessions perdues).
- La revue de mi-parcours au jour 8 plutôt qu'au jour 12 : les alertes
budget auraient été coupées 4 jours plus tôt.
## Ce que le projet m'a appris sur MA façon de travailler
- Sessions du matin : 2× plus productives que le soir (mesuré au
journal, pas ressenti). Les sessions du soir produisent les bugs du
lendemain matin.
- Le flottement (« par quoi je continue ? ») m'a frappé 3 fois ; les
3 fois, la liste d'issues a suffi. La méthode tient.
- Je sous-estime d'un facteur 1,8 (mesuré) — proche du ×2 annoncé.
## Et maintenant
Plan post-méthode (10-3) mis à jour : spendlog v2 = migration SQLite,
en gardant les tests comme filet. Première session le 2026-08-03.
Le parcours est fini. L'outil tourne. Je m'en sers tous les soirs.
Pourquoi ça marche — Ce bilan a trois qualités à copier : il MESURE (sessions, commits, facteur de sous-estimation — le journal rend ça possible), il distingue le processus du produit (les sections « autrement » et « ma façon de travailler » parlent méthode, pas features), et il se termine par une date — le pont vers l'après. La dernière ligne est le vrai critère de réussite du parcours entier : l'outil existe et sert.
Erreur classique sur cet exercice — Le bilan-satisfecit (« super projet, très content ») comme le bilan-flagellation (« trop lent, code moyen ») : aucun des deux n'est utilisable dans trois mois. Le bilan n'est ni un bulletin de notes ni une thérapie — c'est un document TECHNIQUE sur ta façon de travailler, écrit pour le seul lecteur qui compte : toi, au prochain projet.
Variante plus difficile — Relis ton bilan dans un mois (mets un rappel) avec une seule question : « le plan post-méthode a-t-il démarré ? ». Si non, applique la section « signaux d'abandon » de la variante 10-3 : réduire le plan, pas se flageller. La méthode t'a appris à finir un projet ; la vie d'après consiste à recommencer, en plus petit que tu ne crois, aussi souvent que tu veux.