Die Commit-Historie als Lernpfad

Dieses Repository ist so committet, dass git log selbst ein lesbares TDD-Transkript ist. Diese Seite erklärt die Konvention und wie du sie zum Nacherleben eines Use-Case nutzt.

Commit-Konvention

Präfix Bedeutung TDD-Phase
test(<scope>): [RED] ... neuer, absichtlich fehlschlagender Test — sonst keine Produktionsänderung Red
feat(<scope>): [GREEN] ... gerade genug Produktionscode, um den zuletzt hinzugefügten Test grün zu machen Green
refactor(<scope>): ... Strukturverbesserung ohne Verhaltensänderung, Tests bleiben grün Refactor
docs(<scope>): ... Dokumentationsänderung (ADR, Doku-Seite, README)
build(<scope>): ... Build-/Abhängigkeitsänderung (POM, Plugin-Konfiguration)
chore(<scope>): ... Modul-/Verzeichnis-Gerüst, ohne eigenes Verhalten
fix(<scope>): ... Korrektur eines eigenen vorherigen Fehlers (z. B. in der POM-Konfiguration)

<scope> ist meist der Modulname (common-domain, accounts-service, ...), bei modulübergreifenden Änderungen der grobe Bereich.

Zwei Granularitätsstufen in diesem Projekt

Die ersten Use-Cases (common-domain, common-events, customer-service bis einschließlich "Kunde registrieren") committen pro Klasse: ein RED-Commit pro neuem Testfall, ein GREEN-Commit pro dafür nötiger Produktionsklasse. Das zeigt die Methode im Detail, erzeugt aber sehr viele Commits.

Ab "Identität verifizieren" (siehe TDD-Katalog) gilt eine gröbere Regel, die sich in der Praxis besser bewährt hat: ein Commit pro Phasen-Übergang (Rot → Gelb/Refactor → Grün), auch wenn dabei mehrere Dateien gleichzeitig entstehen (z. B. ein Domain-Test, ein Application-Test und ein Web-Slice-Test für denselben Anwendungsfall zusammen als ein RED-Commit, die zugehörigen drei Produktionsklassen zusammen als ein GREEN-Commit). Der didaktische Kern - "erst ein fehlschlagender Test, dann die minimale Implementierung" - bleibt erhalten; nur die Commit-Anzahl sinkt. Das ist selbst eine reale TDD-Praxis-Lektion: Commit-Granularität ist eine bewusste Abwägung zwischen maximaler Nachvollziehbarkeit und praktikabler Historie, keine feste Regel - und darf sich, wie hier, sogar innerhalb eines Projekts ändern, wenn sich zeigt, dass die ursprüngliche Granularität zu fein war.

Wie man einen Use-Case Schritt für Schritt nachvollzieht

Jede Zeile im TDD-Katalog verweist auf den ersten und letzten Commit-Hash eines Use-Case. Um die komplette Entwicklung nachzuvollziehen:

# Alle Commits zwischen Start- und End-Hash eines Use-Case, aeltester zuerst
git log --oneline --reverse <start-hash>^..<end-hash>

# Nur die Aenderungen an einer bestimmten Testdatei ueber die Zeit verfolgen
git log --oneline --follow -- accounts-service/src/test/java/.../OpenAccountUseCaseTest.java

# Eine einzelne Commit-Aenderung ansehen (z.B. den GREEN-Schritt, der einen Test zum
# Laufen brachte)
git show <hash>

# Den Diff zwischen einem RED- und dem zugehoerigen GREEN-Commit ansehen -
# zeigt exakt, wie viel (bzw. wie wenig) Produktionscode fuer ein "Gruen" noetig war
git diff <red-hash> <green-hash>

Übungsvorschlag für Einsteiger:innen: Nimm den allerersten Use-Case dieses Projekts — Money-Value-Object in common-domain — und lies dir die Commits von chore: Reactor-Skeleton ... bis refactor(common-domain): Waehrungspruefung ... extrahiert in chronologischer Reihenfolge durch (git log --oneline --reverse). Du wirst sehen, wie aus vier kleinen Red-Green-Paaren plus einem Refactor-Schritt ein vollständiges, invariantengeschütztes Value Object entsteht — ganz ohne dass an irgendeiner Stelle mehr Code geschrieben wurde, als der jeweils aktuelle Test verlangte.

Exkurs: feingranulare Commits nachträglich zu Phasen-Commits verdichten

Dieses Projekt hat die feingranularen Commits der ersten Use-Cases bewusst stehen lassen (siehe oben) - aber die Frage "wie würde man das nachträglich verdichten?" ist selbst lehrreich genug für eine eigene Notiz, auch ohne es hier tatsächlich auszuführen.

Zwei Standardwerkzeuge dafür, beide git-nativ:

1. git reset --soft + neu committen (einfachste Variante für "die letzten N Commits zu einem verschmelzen"):

# Beispiel: die neun Money-Commits dieses Projekts (RED/GREEN x4 + 1 Refactor,
# e63d924..82d72f9) zu drei Phasen-Commits verdichten.

# 1. Zurueck auf den Stand VOR dem ersten Money-Commit, Arbeitsverzeichnis bleibt unveraendert
git reset --soft e63d924~1

# 2. Jetzt liegen alle Aenderungen der neun Commits gemeinsam in der Staging Area.
#    Selektiv wieder committen, z.B. nur die Testdateien fuer den RED-Commit:
git reset HEAD .                                   # erstmal alles unstagen
git add **/MoneyTest.java
git commit -m "test(common-domain): [RED] Money - Erzeugung, Validierung, Addition"

# 3. Dann die Produktionsklassen fuer GREEN:
git add **/Money.java **/DomainException.java **/InvariantViolationException.java
git commit -m "feat(common-domain): [GREEN] Money-Value-Object mit Validierung und Addition"

# 4. Der Refactor-Commit war inhaltlich bereits sauber getrennt - so uebernehmen
#    (Diff pruefen, dann committen)

2. git rebase -i (interaktives Rebase mit squash/fixup) - praktischer, wenn die Commits nicht direkt hintereinander sauber nach RED/GREEN/REFACTOR sortiert sind:

git rebase -i e63d924~1
# Im Editor: erster Commit jeder Phase bleibt "pick", alle folgenden Commits derselben Phase
# werden auf "squash" (verschmilzt Commit-Historie UND fragt nach neuer Nachricht) oder
# "fixup" (verschmilzt still, behaelt die Nachricht des "pick"-Commits) gesetzt, z.B.:
#   pick   e63d924 test(common-domain): [RED] Money.of erzeugt Geldbetrag ...
#   fixup  3e4a8c8 feat(common-domain): [GREEN] Money als Record ...
#   pick   fbceaaa test(common-domain): [RED] Money.of lehnt negativen Betrag ab
#   fixup  e89c607 feat(common-domain): [GREEN] Money.of validiert Betrag ...
#   ...

Wichtige Randbedingungen, bevor man das tatsächlich tut:

In diesem Projekt bleibt die bestehende Historie unverändert (siehe Baufortschritt) - dieser Exkurs dokumentiert das Vorgehen als Lektion, nicht als durchgeführte Aktion.

Warum diese Disziplin (und ihre Grenzen)

Der Nutzen dieser feinkörnigen Historie ist didaktisch: Sie macht den TDD-Prozess selbst zu einem prüfbaren Artefakt, nicht nur zu einer Behauptung im Projekt-README. Für ein reales Produktionsprojekt außerhalb eines Lernkontexts wäre diese Granularität an Commits meist zu fein für den täglichen Arbeitsfluss (typischerweise squasht man vor dem Merge in einen Feature-Branch) — hier bleibt sie bewusst erhalten, weil das Nachvollziehen der Methode selbst das Lernziel ist, nicht nur das fertige Produkt.

⌂ Cockpit