W3docs

Java Clean-Code-Prinzipien

Clean-Code-Prinzipien in Java: kleine Methoden, aussagekräftige Namen, Einzelverantwortung und minimale Mutation.

Clean Code ist Code, den die nächste Person — oft man selbst, sechs Monate später — lesen, verstehen und gefahrlos ändern kann. Der Compiler akzeptiert fast alles; Clean Code richtet sich an das andere Publikum: die Menschen, die ihn pflegen. Keines der folgenden Prinzipien ist eine Java-spezifische Erfindung, aber Java bietet konkrete Werkzeuge — kleine Methoden, final-Felder, Records, Exceptions, aussagekräftige Typen —, um sie anzuwenden. Dieses Kapitel behandelt diejenigen, die sich täglich auszahlen.

Namen, die Absichten offenbaren

Ein guter Name beantwortet warum der Wert existiert, nicht nur welchen Typ er hat. Braucht ein Name einen Kommentar zur Erklärung, ist der Name falsch. Einzelbuchstaben (außerhalb kurzer Schleifen) und Abkürzungen, die nur der Autor versteht, sind zu vermeiden — ein längerer, beschreibender Name ist einem kurzen, kryptischen vorzuziehen. Die IDE vervollständigt ihn ohnehin.

// Unclear: what is d? what unit? what is the magic 86400000?
int d = (t2 - t1) / 86400000;

// Clear: the names and a constant carry the meaning
long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
long elapsedDays = (endMillis - startMillis) / MILLIS_PER_DAY;

Boolesche Werte lesen sich am besten als Fragen oder Zustände: isActive, hasNext, shouldRetry. Methoden, die etwas tun, erhalten Verb-Namen (calculateTotal); Methoden, die etwas zurückgeben, erhalten Substantiv- oder Getter-Namen (total, getTotal). Zu den sprachlichen Konventionen hinter diesen Entscheidungen, siehe Java Naming Conventions und Java Naming Best Practices.

Kleine Methoden mit einer einzigen Verantwortung

Eine Methode sollte eine Sache auf einer Abstraktionsebene tun. Wenn man einen Kommentar wie // now validate the input hinzufügt, möchte dieser Block meist eine eigene, gut benannte Methode werden. Kurze Methoden sind einfacher zu benennen, zu testen und wiederzuverwenden — und die Aufrufstelle liest sich wie ein Satz.

// Before: one method juggling validation, calculation, and formatting
String receipt(Order order) {
  if (order == null || order.items().isEmpty())
    throw new IllegalArgumentException("empty order");
  int total = 0;
  for (var i : order.items()) total += i.price() * i.qty();
  return "Total: $" + total / 100 + "." + (total % 100);
}

// After: each step is a named method; receipt() now reads top-down
String receipt(Order order) {
  requireNonEmpty(order);
  int total = totalCents(order);
  return formatCents(total);
}

Guard Clauses halten Methoden flach. Randfälle werden behandelt und früh zurückgegeben, anstatt den Hauptpfad in immer tieferen if-Blöcken zu vergraben.

Unveränderlichkeit bevorzugen und Mutation minimieren

Veränderbarer gemeinsamer Zustand ist die Wurzel der meisten Nebenläufigkeitsfehler und einer Menge gewöhnlicher Verwirrung. Standardmäßig final-Felder und lokale Variablen verwenden; nur mit Begründung auf Veränderbarkeit wechseln. Javas Records machen unveränderliche Wertobjekte einzeilig und generieren einen kanonischen Konstruktor, equals, hashCode und toString.

// An immutable value object with validation in the compact constructor
record Money(long cents, String currency) {
  Money {
    if (cents < 0) throw new IllegalArgumentException("cents must be >= 0");
  }
  Money plus(Money other) { return new Money(cents + other.cents, currency); }
}

plus gibt ein neues Money-Objekt zurück, anstatt this zu mutieren. Unveränderliche Objekte können sicher über Threads hinweg geteilt werden, sicher als Map-Schlüssel verwendet werden und können nach der Konstruktion nicht mehr korrumpiert werden. Für tiefergehende Informationen siehe Java Records, unveränderliche Klassen und Immutability Best Practices; das final-Schlüsselwort behandelt das Sperren von Feldern und Variablen.

Früh scheitern und Exceptions statt Fehlercodes verwenden

Argumente an der Grenze validieren und sofort eine Exception werfen, wenn etwas falsch ist, damit der Fehler nahe seiner Ursache auftaucht — nicht drei Ebenen tiefer als verwirrender NullPointerException. Exceptions verwenden, um Fehler zu signalisieren; nicht null oder magische Sentinel-Werte zurückgeben, die jeder Aufrufer prüfen muss.

MusterVermeidenBevorzugen
Fehlender Wertreturn null;Optional<T> oder Exception werfen
Ungültiges Argumentreturn -1;throw new IllegalArgumentException(...)
Unmöglicher Zustandstiller Standardthrow new IllegalStateException(...)
Ressourcen-Bereinigungmanuelles finallytry-with-resources
static User findUser(String id) {
  Objects.requireNonNull(id, "id must not be null");   // fail fast
  return repository.lookup(id)
      .orElseThrow(() -> new NoSuchElementException("no user: " + id));
}

Objects.requireNonNull wandelt eine vage nachgelagerte NPE in eine präzise Nachricht am Einstiegspunkt um. Zum Modellieren von "kann fehlen" ohne null, siehe Java Optional; für das Entwerfen von Fehlertypen, siehe Exception Best Practices und benutzerdefinierte Exceptions.

DRY, aber nicht über-abstrahieren

DRY (Don't Repeat Yourself) bedeutet, dass ein einzelnes Wissen an einem Ort lebt. Wenn dieselbe Konstante oder Berechnung zweimal vorkommt, sollte sie extrahiert werden. Aber dem umgekehrten Fehler widerstehen: Zwei Ausschnitte, die heute nur ähnlich aussehen, können sich morgen auseinanderentwickeln. Duplizierter Code ist billiger zu reparieren als die falsche Abstraktion. Extrahieren, wenn die Bedeutung geteilt wird, nicht nur die Syntax.

// Knowledge duplicated: the threshold lives in two places
if (order.total() >= 5000) freeShip = true;     // here
if (cart.total() >= 5000) showBadge = true;     // and here

// One source of truth
static final int FREE_SHIPPING_THRESHOLD_CENTS = 5000;
boolean qualifiesForFreeShipping(int totalCents) {
  return totalCents >= FREE_SHIPPING_THRESHOLD_CENTS;
}

Ein ausgearbeitetes Beispiel: ein sauberer Warenkorb

Dieses Programm vereint die Prinzipien in einer kleinen, ausführbaren Datei: ein unveränderlicher LineItem-Record, der sich selbst validiert, Methoden mit einem einzigen Zweck und absichtsoffenbarenden Namen, benannte Konstanten statt magischer Zahlen, eine Guard Clause und wertbasierte Gleichheit. Kein Kommentar ist nötig, um zu erklären, was es tut — die Namen erledigen das.

java— editable, runs on the server

Was aus dem Lauf zu entnehmen ist:

  • Die Ausgabe liest sich genau wie die Domäne, die sie modelliert — 2 x Notebook @ $12.50 = $25.00 — weil jede Methode und jedes Feld nach ihrem Zweck benannt ist. Man braucht keinen einzigen Kommentar, um der Warenkorb-Mathematik zu folgen; absichtsoffenbarende Namen übernehmen die Dokumentation.
  • Der Zwischenbetrag ist $30.97 und der Versand ist $5.99, nicht kostenlos: die shippingCents-Guard-Clause hat den Zwischenbetrag gegen die benannte Konstante FREE_SHIPPING_THRESHOLD_CENTS (5000) verglichen, und 3097 liegt darunter. Die magische Zahl lebt an genau einer Stelle, sodass die Regel unmöglich inkonsistent werden kann.
  • value equality: true beweist, dass der record uns equals nach Wert kostenlos gegeben hat — zwei separat konstruierte Pen-Elemente sind gleich, weil ihre Daten gleich sind. Dieses equals/hashCode-Paar von Hand zu schreiben ist Boilerplate, die man nicht mehr pflegen muss.
  • rejected bad item: quantity must be >= 0 zeigt Fail-Fast in Aktion: der kompakte Konstruktor hat das Argument validiert und zum Konstruktionszeitpunkt eine IllegalArgumentException geworfen, sodass eine Menge von -1 nie als stille Fehlerdaten ins System gelangen kann.
  • Jede Hilfsmethode — subtotalCents, shippingCents, formatCents — tut eine Sache, sodass main von oben nach unten wie eine Geschichte gelesen werden kann. Kleine Methoden mit Einzelverantwortung machen das gesamte Programm überschaubar, statt einer Wand aus verschachtelter Logik.

Übungen

Übung
Warum gilt es als sauberer, 'LineItem' als Record mit Validierung im kompakten Konstruktor zu gestalten statt als einfache veränderbare Klasse mit Settern?
Warum gilt es als sauberer, 'LineItem' als Record mit Validierung im kompakten Konstruktor zu gestalten statt als einfache veränderbare Klasse mit Settern?
Was this page helpful?