Clean Code in Java, Teil 3

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 Details
private 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 Objekt
account.setBalance(account.getBalance() + 100);
// Verhaltenreich – Objekt kennt seine Regeln
account.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 Doom
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
String city = addr.getCity();
}
}
// Flach – Optional-Pipeline
Optional<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 validieren
void sendEmail(String email) { ... }
// Value Object – Invariante im Typ verankert
record 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 Kontext
throw new RuntimeException("Not found");
// Domänenspezifisch – selbstdokumentierend
throw new OrderNotFoundException(id);
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(String id) {
super("Bestellung nicht gefunden: " + id);
}
}
// Aufrufer kann gezielt reagieren
catch (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
@Service
class 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

I’m Iman

Mein Name ist Iman Dabbaghi. Ich arbeite als Senior Software Engineer in der Schweiz. Außerdem interessiere ich mich sehr für gewaltfreie Kommunikation, Bachata-Tanz und Musik sowie fürs die Persönlichkeitsentwicklung.

Ich habe einen Masterabschluss in Informatik von der Universität Freiburg in Deutschland, bin Spring/Java Certified Professional (OCP), Certified Professional for Software Architecture (CPSA-F) und ein lebenslanger Lernender 🎓.

EN:

My name is Iman Dabbaghi. I work as a Senior Software Engineer in Switzerland. I am also very interessted in nonviolent communication, Bachata dance and music and also for personal development.

I hold a masters degree in computer science from the university of Freiburg in Germany, am a Spring / Java Certified Professional (OCP), Certified Software Architecture (CPSA-F) and Life Long Learner🎓

Let’s connect