Code-Design
Kommuniziere mit dem Benutzer auf Deutsch. Die folgenden Begriffe werden beim ersten Auftreten zweisprachig (Deutsch/Englisch) genannt und danach gemäß dieser Tabelle einheitlich verwendet.
Design eines deep module (tiefen Moduls): Viel Verhalten hinter einer kleinen Schnittstelle, platziert auf einem sauberen Seam (Nahtstelle, Feathers), und testbar durch diese Schnittstelle. Verwende diese Sprache und diese Prinzipien überall dort, wo Code entworfen oder umstrukturiert wird. Ziel: Dem Aufrufer Hebel (Leverage) geben, dem Wartenden Lokalität (Locality), und es für alle einfach testbar machen.
Glossar
Verwende die folgenden Begriffe präzise. Ersetze sie nicht durch „Komponente“, „Dienst“, „API“ oder „Grenze“. Eine konsistente Sprache ist der entscheidende Punkt.
Modul (Module): Alles, was eine Schnittstelle und eine Implementierung hat. Bewusst keine Größenbeschränkung: Es kann eine Funktion, eine Klasse, ein Paket oder ein schichtenübergreifender Ausschnitt sein. Vermeide: Einheit, Komponente, Dienst.
Schnittstelle (Interface): Alles, was der Aufrufer wissen muss, um das Modul korrekt zu verwenden: Typsignatur sowie Invarianten, Aufrufreihenfolge-Einschränkungen, Fehlermodi, erforderliche Konfiguration, Leistungsmerkmale. Vermeide: API, Signatur (zu eng, bezieht sich nur auf die typspezifische Oberfläche).
Implementierung (Implementation): Das Innere des Moduls, also sein Code-Körper. Unterschied zum Adapter: Ein Ding kann ein kleiner Adapter mit großer Implementierung sein (z. B. ein SQLite-Repository) oder ein großer Adapter mit kleiner Implementierung (z. B. ein In-Memory-Fake). Verwende „Adapter“ bei der Diskussion von Seams, sonst „Implementierung“.
Tiefe (Depth): Hebelwirkung der Schnittstelle. Wie viel Verhalten kann der Aufrufer (oder der Test) mit jeder gelernten Einheit der Schnittstelle nutzen? Ein Modul mit viel Verhalten hinter einer kleinen Schnittstelle ist tief; ein Modul, dessen Schnittstelle fast so komplex ist wie die Implementierung, ist flach.
Seam (Michael Feathers): Ein Ort, an dem man das Verhalten ändern kann, ohne diesen Ort selbst zu modifizieren; also der Ort, an dem sich die Modulschnittstelle befindet. Wo man einen Seam platziert, ist eine eigene Designentscheidung, getrennt davon, was hinter dem Seam steckt. Vermeide: Grenze (Verwechslung mit DDDs bounded context).
Adapter: Ein konkretes Ding, das an einem Seam eine bestimmte Schnittstelle erfüllt. Beschreibt die Rolle (welche Position es füllt), nicht den Inhalt (was darin steckt).
Hebel (Leverage): Der Nutzen, den der Aufrufer aus der Tiefe zieht. Mit jeder gelernten Einheit der Schnittstelle erhält man mehr Fähigkeiten. Der Wert einer Implementierung wird an N Aufrufstellen und in M Tests amortisiert.
Lokalität (Locality): Der Nutzen, den der Wartende aus der Tiefe zieht. Änderungen, Fehler, Wissen und Validierung sind an einem Ort konzentriert, nicht über alle Aufrufer verstreut. Einmal reparieren, überall wirksam.
Tief und flach
Deep module = kleine Schnittstelle + große Implementierung:
┌─────────────────────┐
│ Kleine Schnittstelle │ ← Wenige Methoden, einfache Parameter
├─────────────────────┤
│ │
│ Tiefe Implementierung │ ← Komplexe Logik darin versteckt
│ │
└─────────────────────┘
Flaches Modul = große Schnittstelle + kleine Implementierung (vermeiden):
┌─────────────────────────────────┐
│ Große Schnittstelle │ ← Viele Methoden, komplexe Parameter
├─────────────────────────────────┤
│ Dünne Implementierung │ ← Leitet nur weiter
└─────────────────────────────────┘
Frage dich beim Entwerfen einer Schnittstelle:
- Kann ich die Anzahl der Methoden reduzieren?
- Kann ich die Parameter vereinfachen?
- Kann ich mehr Komplexität hinein verstecken?
Prinzipien
- Tiefe ist eine Eigenschaft der Schnittstelle, nicht der Implementierung. Ein Deep Module kann intern aus kleinen, mockbaren, austauschbaren Teilen bestehen; nur gehören diese Teile nicht zur Schnittstelle. Ein Modul kann interne Seams (implementierungsprivat, für eigene Tests) und externe Seams auf der Schnittstelle haben.
- Löschtest. Stell dir vor, du löschst das Modul. Wenn die Komplexität verschwindet, war es nur ein Durchreich-Modul. Wenn die Komplexität an N Aufrufstellen wieder auftaucht, hat es seinen Zweck erfüllt.
- Schnittstelle ist Testfläche. Aufrufer und Tests überspannen denselben Seam. Wenn du durch die Schnittstelle hindurch testen möchtest, ist die Form des Moduls wahrscheinlich falsch.
- Ein Adapter repräsentiert einen hypothetischen Seam, zwei Adapter sind ein echter Seam. Führe keinen Seam ein, es sei denn, es ändert sich tatsächlich etwas auf beiden Seiten des Seams.
Design für Testbarkeit
Eine gute Schnittstelle macht Tests natürlich:
-
Abhängigkeiten empfangen, nicht selbst erstellen.
# testbar def fetch_alternates(mpn: str, client: PartSearchClient) -> list[Part]: ... # schwer testbar def fetch_alternates(mpn: str) -> list[Part]: client = DistributorClient(api_key=os.environ["DIST_API_KEY"]) -
Ergebnisse zurückgeben, keine Nebenwirkungen erzeugen.
# testbar def landed_cost(bom: Bom, freight: Freight) -> Money: ... # schwer testbar def apply_landed_cost(bom: Bom) -> None: bom.total += freight_charge -
Kleine Oberfläche. Weniger Methoden bedeuten weniger Tests; weniger Parameter bedeuten einfachere Testvorbereitung.
Beziehungen
- Ein Modul hat genau eine Schnittstelle (die Oberfläche, die es dem Aufrufer und den Tests präsentiert).
- Tiefe ist eine Eigenschaft des Moduls, gemessen an seiner Schnittstelle.
- Ein Seam ist der Ort, an dem sich die Schnittstelle des Moduls befindet.
- Ein Adapter befindet sich auf einem Seam und erfüllt die Schnittstelle.
- Tiefe erzeugt Hebel für den Aufrufer und Lokalität für den Wartenden.
Nicht verwendete Frameworks
-Tiefe als Verhältnis von Implementierungszeilen zu Schnittstellenzeilen definieren (Ousterhout): Dies würde aufgeblähte Implementierungen belohnen. Wir verwenden stattdessen „Tiefe ist Hebelwirkung“.
- „Schnittstelle“ gleichsetzen mit Python's
Protocol/abstrakter Klasse oder den öffentlichen Methoden einer Klasse: Zu eng: Die Schnittstelle umfasst hier alles, was der Aufrufer wissen muss. - „Grenze“: Verwechslung mit DDDs bounded context. Sag stattdessen Seam oder Schnittstelle.
Weiterführende Lektüre
- Vertiefen einer Gruppe von Modulen unter bestehenden Abhängigkeiten siehe DEEPENING.md: Abhängigkeitsklassifizierung, Seam-Disziplin und eine Teststrategie des „Ersetzens statt Überlagerns“.
- Erkunden verschiedener Schnittstellendesigns siehe DESIGN-IT-TWICE.md: Parallel mehrere Sub-Agents entsenden, Schnittstellen auf verschiedene Arten entwerfen und dann hinsichtlich Tiefe, Lokalität und Seam-Position vergleichen.