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 Verwendungvoid transfer(BigDecimal amount) {}// Reifiziert – Money kapselt Validierung & Semantikrecord 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 Regelnvoid calculateTax(BigDecimal amount) {}// WET – bewusst getrennt, bis das Muster klar istvoid 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 wirdinterface 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 AbsichtList<Order> valid = new ArrayList<>();for (Order o : orders) { if (o.isValid()) valid.add(o);}// Deklarativ – Absicht auf einen Blickorders.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 Codepublic Order load(String id) { return repository.find(id).orElse(null); // NPE-Zeitbombe}// Do or Die – Fehler direkt an der Quellepublic 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 → CityString city = customer.address().city();// LoD-konform – Customer delegiertpublic 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 ungehindertUser findUser(String id) { return db.get(id); // kann null sein}// Null-safe – Abwesenheit ist Teil des TypsOptional<User> findUser(String id) { return Optional.ofNullable(db.get(id));}// Aufrufer muss sich entscheidenfindUser(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