Stepdown Rule:
Code von oben nach unten lesbar strukturieren
Die Stepdown Rule (Robert C. Martin): Methoden sollen in der Reihenfolge stehen, wie sie aufgerufen werden – oben die übergeordnete Logik, darunter die Details.
public void checkout() { // höchste Ebene zuerst validate(); pay(); ship();}private void validate() { ... } // dann Detailsprivate void pay() { ... }private void ship() { ... }
Lesbarkeit:
Code liest sich wie ein Artikel – Überschrift, dann Details. Man muss nicht scrollen, um den Überblick zu behalten.
Wartbarkeit:
Neue Entwickler finden sich sofort zurecht. Einstiegspunkt und Ablauf sind sofort klar.
Erweiterbarkeit:
Neue Schritte können in checkout() eingefügt und darunter implementiert werden – konsistent.
Replace Getters und Setters:
Domainlogik in Objekten kapseln
Getter und Setter externalisieren Logik. account.deposit(100) kapselt Validierung, Ereignisse und Invarianten direkt im Objekt – statt sie in Service-Klassen zu verteilen.
// Anämisch – Logik lebt im Service, nicht im Objektaccount.setBalance(account.getBalance() + 100);// Verhaltenreich – Objekt kennt seine Regelnaccount.deposit(100);public class Account { public void deposit(int amount) { if (amount <= 0) throw new IllegalArgumentException(); this.balance += amount; events.add(new MoneyDeposited(amount)); }}
Lesbarkeit:
account.deposit(100) beschreibt die Domänen-Absicht. Kein Rechnen im Aufruf-Code.
Wartbarkeit:
Invarianten (z. B. positive Beträge) sind zentralisiert. Nie wieder vergessene Validierung im Service.
Erweiterbarkeit:
Events, Logging, Limits können in deposit() ergänzt werden – Aufrufer bleiben unverändert.
Flatten Conditionals with Monads:
Optionals reduzieren Verschachtelung
flatMap() auf Optional vermeidet das Verschachteln von Null-Checks. Verschachtelte if-Ketten werden zur linearen Pipeline.
// Verschachtelt – Pyramid of Doomif (user != null) { Address addr = user.getAddress(); if (addr != null) { String city = addr.getCity(); }}// Flach – Optional-PipelineOptional<String> city = user .flatMap(User::address) .map(Address::city);
Lesbarkeit:
Lineare Pipeline zeigt den Happy Path klar. Null-Handling ist implizit und unsichtbar.
Wartbarkeit:
Neue Navigationsschritte werden per .flatMap() angehängt – keine neue Einrückungsebene nötig.
Erweiterbarkeit:
Mit Vavr oder Reactor lässt sich dasselbe Muster auf Fehlerbehandlung (Either) und Async (Mono) ausdehnen
Elegant Objects & Primitive Obsession:
Value Objects statt primitiver Typen
Eine rohe String email kennt keine Regeln. Ein record Email validiert beim Erstellen – eine ungültige E-Mail kann gar nicht existieren.
// Primitiv – jeder Aufrufer muss selbst validierenvoid sendEmail(String email) { ... }// Value Object – Invariante im Typ verankertrecord Email(String value) { Email { if (!value.contains("@")) throw new InvalidEmailException(value); }}void sendEmail(Email email) { ... }
Lesbarkeit:
Die Methodensignatur kommuniziert: „Nur gültige E-Mails erlaubt.“ Keine Kommentare nötig.
Wartbarkeit:
Validierung zentral und nicht duplizierbar. Records sind immutable – keine unerwarteten Mutationen.
Erweiterbarkeit:
Methoden wie domain() oder isBusinessEmail() wachsen natürlich ins Email-Objekt.
Exception Handling & Datenklassen:
Konsistente, ausdrucksstarke Fehlerbehandlung
Ausnahmen sollen die Domäne widerspiegeln. OrderNotFoundException ist informativer als ein generisches RuntimeException und ermöglicht gezieltes Fehler-Handling.
// Generisch – kein Kontextthrow new RuntimeException("Not found");// Domänenspezifisch – selbstdokumentierendthrow new OrderNotFoundException(id);public class OrderNotFoundException extends RuntimeException { public OrderNotFoundException(String id) { super("Bestellung nicht gefunden: " + id); }}// Aufrufer kann gezielt reagierencatch (OrderNotFoundException e) { return404(); }
Lesbarkeit:
Exception-Typ und -Nachricht beschreiben Ursache und Kontext. Logs sind sofort verständlich.
Wartbarkeit:
Gezieltes Catching möglich ohne Nachrichtenvergleiche (e.getMessage().contains("not found")).
Erweiterbarkeit:
Exception-Hierarchien können erweitert werden (z. B. ExpiredOrderException extends OrderNotFoundException).
Dependency Injection:
Moderne Bibliotheken gezielt einsetzen
Bibliotheken wie Spring DI (Dependency Injection) reduzieren Boilerplate und erhöhen Ausdrucksstärke – wenn sie zielgerichtet eingesetzt werden.
// Spring DI – Abhängigkeiten deklarativ, nicht imperativ@Serviceclass OrderService { private final OrderRepository repo; OrderService(OrderRepository repo) { // Constructor Injection this.repo = repo; }}
Lesbarkeit:
@Service und Constructor Injection machen Abhängigkeiten explizit und testbar – ohne manuelle Factories.
Wartbarkeit:
Standardisierte Muster aus bekannten Bibliotheken sind leichter zu verstehen als Custom-Boilerplate.
Erweiterbarkeit:
DI erlaubt einfaches Austauschen von Implementierungen (z. B. Mock im Test, echte Impl. in Prod).

Hinterlasse einen Kommentar