Codebase-Design
Entwerfen Sie tiefe Module: viel Verhalten hinter einer kleinen Schnittstelle, platziert an einer sauberen Naht, testbar durch diese Schnittstelle. Verwenden Sie diese Sprache und diese Prinzipien, wo immer Code entworfen oder umstrukturiert wird. Das Ziel ist Hebelwirkung für Aufrufer, Lokalität für Wartende und Testbarkeit für alle.
Glossar
Verwenden Sie diese Begriffe genau: Ersetzen Sie nicht durch „Komponente“, „Service“, „API“ oder „Grenze“. Konsistente Sprache ist der entscheidende Punkt.
Modul: alles mit einer Schnittstelle und einer Implementierung. Bewusst skalierungsneutral: eine Funktion, Klasse, ein Paket oder ein bereichsübergreifender Schnitt. Vermeiden Sie: Einheit, Komponente, Service.
Schnittstelle (Interface): alles, was ein Aufrufer wissen muss, um das Modul korrekt zu nutzen: die Typsignatur, aber auch Invarianten, Reihenfolge-Einschränkungen, Fehlermodi, erforderliche Konfiguration und Leistungsmerkmale. Vermeiden Sie: API, Signatur (zu eng; sie beziehen sich nur auf die Typebene).
Implementierung: was sich innerhalb eines Moduls befindet, sein Codekörper. Abzugrenzen von Adapter: ein Ding kann ein kleiner Adapter mit einer großen Implementierung (ein Postgres-Repository) oder ein großer Adapter mit einer kleinen Implementierung (ein In-Memory-Fake) sein. Greifen Sie zu „Adapter“, wenn die Naht das Thema ist; andernfalls „Implementierung“.
Tiefe (Depth): Hebelwirkung an der Schnittstelle. Die Menge an Verhalten, die ein Aufrufer (oder ein Test) pro Einheit der Schnittstelle, die er lernen muss, ausüben kann. Ein Modul ist tief, wenn eine große Menge an Verhalten hinter einer kleinen Schnittstelle steckt; flach, wenn die Schnittstelle fast so komplex ist wie die Implementierung.
Naht (Seam) (Michael Feathers): ein Ort, an dem Sie das Verhalten ändern können, ohne an dieser Stelle zu editieren; der Ort, an dem die Schnittstelle eines Moduls lebt. Wo die Naht platziert wird, ist eine eigene Designentscheidung, getrennt von dem, was dahinter liegt. Vermeiden Sie: Grenze (überladen mit DDDs Bounded Context).
Adapter: ein konkretes Ding, das eine Schnittstelle an einer Naht erfüllt. Beschreibt die Rolle (welchen Slot es füllt), nicht die Substanz (was darin steckt).
Hebelwirkung (Leverage): was Aufrufer aus der Tiefe gewinnen. Mehr Fähigkeit pro Einheit der Schnittstelle, die sie lernen. Eine Implementierung zahlt sich über N Aufrufstellen und M Tests aus.
Lokalität (Locality): was Wartende aus der Tiefe gewinnen. Änderungen, Fehler, Wissen und Verifikation konzentrieren sich an einem Ort, anstatt sich über Aufrufer zu verteilen. Einmal repariert, überall repariert.
Tief vs. flach
Tiefes Modul = kleine Schnittstelle + viel Implementierung:
┌─────────────────────┐
│ Kleine Schnittst. │ ← Wenige Methoden, einfache Parameter
├─────────────────────┤
│ │
│ Tiefe Implementierung│ ← Komplexe Logik verborgen
│ │
└─────────────────────┘
Flaches Modul = große Schnittstelle + wenig Implementierung (vermeiden):
┌─────────────────────────────────┐
│ Große Schnittstelle │ ← Viele Methoden, komplexe Parameter
├─────────────────────────────────┤
│ Dünne Implementierung │ ← Leitet nur durch
└─────────────────────────────────┘
Wenn Sie eine Schnittstelle entwerfen, fragen Sie:
- Kann ich die Anzahl der Methoden reduzieren?
- Kann ich die Parameter vereinfachen?
- Kann ich mehr Komplexität im Inneren verbergen?
Prinzipien
- Tiefe ist eine Eigenschaft der Schnittstelle, nicht der Implementierung. Ein tiefes Modul kann intern aus kleinen, mockbaren, austauschbaren Teilen bestehen; sie sind nur nicht Teil der Schnittstelle. Ein Modul kann interne Nähte (privat für seine Implementierung, von seinen eigenen Tests verwendet) sowie die externe Naht an seiner Schnittstelle haben.
- Der Löschtest. Stellen Sie sich vor, Sie löschen das Modul. Wenn die Komplexität verschwindet, war es ein Durchgangsstück. Wenn die Komplexität an N Aufrufern wieder auftaucht, hat es sich bezahlt gemacht.
- Die Schnittstelle ist die Testoberfläche. Aufrufer und Tests überqueren dieselbe Naht. Wenn Sie hinter die Schnittstelle testen wollen, hat das Modul wahrscheinlich die falsche Form.
- Ein Adapter bedeutet eine hypothetische Naht. Zwei Adapter bedeuten eine echte. Führen Sie keine Naht ein, es sei denn, etwas variiert tatsächlich über sie hinweg.
Entwerfen für Testbarkeit
Gute Schnittstellen machen das Testen natürlich:
-
Abhängigkeiten akzeptieren, nicht erzeugen.
// Testbar function processOrder(order, paymentGateway) {} // Schwer zu testen function processOrder(order) { const gateway = new StripeGateway(); } -
Ergebnisse zurückgeben, keine Seiteneffekte erzeugen.
// Testbar function calculateDiscount(cart): Discount {} // Schwer zu testen function applyDiscount(cart): void { cart.total -= discount; } -
Kleine Oberfläche. Weniger Methoden = weniger benötigte Tests. Weniger Parameter = einfachere Testeinrichtung.
Beziehungen
- Ein Modul hat genau eine Schnittstelle (die Oberfläche, die es Aufrufern und Tests präsentiert).
- Tiefe ist eine Eigenschaft eines Moduls, gemessen an seiner Schnittstelle.
- Eine Naht ist der Ort, an dem die Schnittstelle eines Moduls lebt.
- Ein Adapter sitzt an einer Naht und erfüllt die Schnittstelle.
- Tiefe erzeugt Hebelwirkung für Aufrufer und Lokalität für Wartende.
Abgelehnte Rahmen
- Tiefe als Verhältnis von Implementierungszeilen zu Schnittstellenzeilen (Ousterhout): belohnt das Aufblähen der Implementierung. Wir verwenden stattdessen Tiefe-als-Hebelwirkung.
- „Schnittstelle“ als das TypeScript-
interface-Schlüsselwort oder die öffentlichen Methoden einer Klasse: zu eng: Schnittstelle umfasst hier jede Tatsache, die ein Aufrufer wissen muss. - „Grenze“: überladen mit DDDs Bounded Context. Sagen Sie Naht oder Schnittstelle.
Tiefer gehen
- Vertiefen eines Clusters bei gegebenen Abhängigkeiten, siehe DEEPENING.md: Abhängigkeitskategorien, Nahtdisziplin und Testen durch Ersetzen statt Schichten.
- Alternative Schnittstellen erkunden, siehe DESIGN-IT-TWICE.md: parallele Sub-Agenten starten, um die Schnittstelle auf mehrere radikal unterschiedliche Weisen zu entwerfen, dann anhand von Tiefe, Lokalität und Nahtplatzierung vergleichen.