Clean Code in Java, Teil 2

Reification:

Konzepte explizit im Code modellieren

Primitive Typen transportieren keine Domänen-Semantik. Ein record Money macht Währung, Betrag und Validierungslogik zu einem eigenständigen Konzept.

// Primitiv – kein Schutz vor falscher Verwendung
void transfer(BigDecimal amount) {}
// Reifiziert – Money kapselt Validierung & Semantik
record Money(BigDecimal amount, Currency currency) {
Money {
if (amount.compareTo(BigDecimal.ZERO) < 0)
throw new IllegalArgumentException("Negative Beträge sind ungültig");
}
}
void transfer(Money amount) {}

Lesbarkeit:

Money kommuniziert sofort die Domäne. Methodensignaturen erzählen die Geschäftssprache.

Wartbarkeit:

Validierung lebt im Objekt, nicht verstreut im Aufruf-Code. Eine Änderung wirkt überall.

Erweiterbarkeit:

Methoden wie add(), subtract(), convertTo() wachsen natürlich ins Objek

AHA und WET:

Abstrahieren erst nach erkannten Mustern

AHA (Avoid Hasty Abstractions) und WET (Write Everything Twice) raten: Erst beim dritten Mal duplizieren abstrahieren. Zwei ähnliche Methoden sind oft unterschiedlicher als sie aussehen.

// Voreilig – ähnliche Namen, aber verschiedene Regeln
void calculateTax(BigDecimal amount) {}
// WET – bewusst getrennt, bis das Muster klar ist
void calculateVat(BigDecimal amount) {
return amount.multiply(new BigDecimal("0.19"));
}
void calculateIncomeTax(BigDecimal amount) {
return amount.multiply(progressiveRate(amount));
}
// Abstraktion erst wenn echtes Muster sichtbar wird
interface TaxStrategy { BigDecimal calculate(BigDecimal base); }

Lesbarkeit:

Explizite Methoden sind klarer als generische Abstraktion, die man immer erst entschlüsseln muss.

Wartbarkeit:

Falsche Abstraktionen erzwingen unnatürliche Anpassungen. Duplizierter Code kann unabhängig evolvieren.

Erweiterbarkeit:

Ist das Muster klar, lohnt die Abstraktion. Vorher entstehen teure und starre Strukturen.

OOP und funktionale Programmierung:

Objekte und Funktionen kombinieren

Java Streams verbinden OOP (Methoden-Referenzen auf Objekten) mit funktionalen Konzepten (filter, map, reduce). Deklarativer Code kommuniziert die Absicht, nicht die Mechanik.

// Imperativ – Mechanik überlagert Absicht
List<Order> valid = new ArrayList<>();
for (Order o : orders) {
if (o.isValid()) valid.add(o);
}
// Deklarativ – Absicht auf einen Blick
orders.stream()
.filter(Order::isValid)
.toList();

Lesbarkeit:

Stream-Pipeline liest sich wie eine Anforderung: „Alle gültigen Bestellungen“. Kein Loop-Overhead im Kopf.

Wartbarkeit:

Schritte können direkt ergänzt (.map(), .sorted()) oder entfernt werden, ohne Struktur zu verändern.

Erweiterbarkeit:

Methodenreferenzen (Order::isValid) zeigen direkt, wo Erweiterungslogik lebt.

Do or Die Principle:

Methoden liefern Ergebnisse oder scheitern klar

Eine Methode soll keinen „stillen“ Misserfolg haben. orElseThrow() macht explizit: Entweder gibt es ein Ergebnis, oder der Aufrufer muss mit einer Ausnahme rechnen.

// Still scheitert – null wandert durch den Code
public Order load(String id) {
return repository.find(id).orElse(null); // NPE-Zeitbombe
}
// Do or Die – Fehler direkt an der Quelle
public Order load(String id) {
return repository.find(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}

Lesbarkeit:

Verhalten ist unmissverständlich. Kein stiller null-Rückgabewert, der erst Zeilen später explodiert.

Wartbarkeit:

Stack Traces zeigen direkt auf die Quelle des Problems – nicht auf den Ort der NPE.

Erweiterbarkeit:

Exception-Typ kann später verfeinert werden. Aufrufer können gezielt fangen oder weiterwerfen.

LoD:

Grundlegende Designprinzipien im Überblick

Das Law of Demeter (LoD) besagt: Sprich nur mit deinen direkten Nachbarn. customer.address().city() verletzt dies – es navigiert durch fremde Objekte.

// Verletzung – navigiert durch Customer → Address → City
String city = customer.address().city();
// LoD-konform – Customer delegiert
public class Customer {
public String city() { return address.city(); }
}
String city = customer.city(); // nur ein Hop

Lesbarkeit:

customer.city() liest sich natürlich. Die interne Objektstruktur bleibt verborgen.

Wartbarkeit:

Ändert sich Address, muss nur Customer.city() angepasst werden – nicht alle Aufrufer.

Erweiterbarkeit:

Lose Kopplung erlaubt es, Address zu ersetzen (z. B. internationale Adressen) ohne Kettenreaktion.

NullPointerExceptions strukturell vermeiden

Optional<T> macht Abwesenheit explizit. Es zwingt den Aufrufer, mit dem „kein Wert“-Fall umzugehen – statt null einfach weiterzureichen.

// Null-anfällig – null wandert ungehindert
User findUser(String id) {
return db.get(id); // kann null sein
}
// Null-safe – Abwesenheit ist Teil des Typs
Optional<User> findUser(String id) {
return Optional.ofNullable(db.get(id));
}
// Aufrufer muss sich entscheiden
findUser(id).ifPresent(user -> greet(user));

Lesbarkeit:

Der Rückgabetyp kommuniziert: „Vielleicht kein User“. Keine Dokumentation, kein Kommentar nötig.

Wartbarkeit:

Compiler erzwingt Behandlung des leeren Falls. NPEs werden zur Compilezeit sichtbar.

Erweiterbarkeit:

Optional lässt sich mit map(), flatMap(), orElse() elegant verketten.

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