Es gibt einen Satz, den man am Anfang ständig still mit sich herumträgt: Hauptsache, es funktioniert. Das ist nachvollziehbar. Du willst ein Ergebnis sehen, Fehler wegklicken, eine Ausgabe bekommen und endlich das Gefühl haben, dass du es verstanden hast. Genau an diesem Punkt fangen aber viele schlechte Gewohnheiten an. Denn Code, der gerade so läuft, ist nicht automatisch guter Code. Er kann unklar sein, unnötig kompliziert, schwer testbar oder so fragil, dass schon die nächste kleine Änderung neue Fehler produziert.

Guter Code fällt oft gar nicht besonders auf. Er ist lesbar, nachvollziehbar und verhält sich so, wie sein Name und seine Struktur es erwarten lassen. Schlechter Code dagegen macht dich langsam. Nicht nur später, sondern oft schon ein paar Tage nach dem Schreiben. Du liest deine eigene Methode und merkst, dass du erst wieder entschlüsseln musst, was du dir dabei gedacht hast. Genau daran erkennst du eines der wichtigsten Muster: Guter Code reduziert Reibung. Schlechter Code erzeugt sie.

 

Woran du schlechten Code früh erkennst

Schlechter Code sieht nicht immer spektakulär kaputt aus. Oft wirkt er sogar erst einmal praktisch. Die Warnzeichen stecken meistens in kleinen Dingen. Eine Methode macht zu viel auf einmal. Eine Variable heißt einfach data, value oder tmp. Eine Klasse kennt plötzlich alles und entscheidet alles. Ein if hängt im nächsten if, darin noch eine Schleife, darin noch eine Sonderbehandlung. Spätestens wenn du beim Lesen geistig einrücken musst, obwohl dein Editor es schon für dich macht, solltest du genauer hinschauen.

Ein gutes Erkennungsmuster ist die Frage: Kann ich in einem Satz erklären, was diese Methode tut? Wenn du dafür ein Komma nach dem anderen brauchst, macht sie wahrscheinlich zu viel. Schau dir dieses Beispiel an:

public void processOrder(Order order) {
    validate(order);
    calculateTotal(order);
    save(order);
    sendConfirmation(order);
}

Das ist deutlich greifbarer als eine einzige Methode mit fünfzig Zeilen, in der Validierung, Berechnung, Datenbankzugriff und E-Mail-Versand wild durcheinanderlaufen. Gute Struktur ist selten magisch. Meistens ist sie einfach sauber getrennt.

Ein weiteres Muster ist Wiederholung. Wenn du denselben Block mehrmals fast identisch kopierst, ist das ein Hinweis auf fehlende Abstraktion. Nicht jede doppelte Zeile ist sofort ein Problem. Aber sobald du dieselbe Logik an mehreren Stellen ändern müsstest, hast du ein Wartungsproblem gebaut. Dann wird aus einer kleinen Anpassung schnell eine Fehlerquelle.

Auch Namen verraten viel. Wenn eine Methode handleData() heißt, sagt sie dir fast nichts. Wenn sie loadUserByEmail() heißt, weißt du sofort, was du erwarten darfst. Gute Namen sparen Kommentare. Schlechte Namen erzeugen sie erst.

 

Typische schlechte Angewohnheiten im Java-Code

Ein häufiger Fehler ist es, Logik in die falsche Schicht zu packen. Gerade bei Java-Anwendungen landet schnell alles in einer Klasse, weil es am Anfang bequem ist. Dann macht ein Controller nicht nur HTTP, sondern validiert Daten, baut Geschäftslogik zusammen, spricht das Repository an und formatiert nebenbei noch Fehlermeldungen. In JavaEE oder auch in einer klassischen Schichtung mit Service und Persistence ist genau das ein Warnsignal. Wenn jede Klasse alles darf, ist am Ende nichts mehr klar getrennt.

Ein anderes Problem ist defensive Unordnung. Damit ist Code gemeint, der aus lauter Unsicherheit mit null, Sonderfällen und Sicherheitsabfragen vollgestellt wird, ohne dass ein klares Modell dahintersteht. Dann sieht eine Methode ungefähr so aus:

if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
    return user.getAddress().getCity();
}
return "";

So etwas ist nicht automatisch falsch. Aber es ist oft ein Zeichen dafür, dass die Datenstruktur unscharf ist oder Verantwortlichkeiten fehlen. Besser ist meistens die Frage, wo dieser Fall fachlich sauber behandelt werden sollte. Muss eine Adresse wirklich optional sein? Sollte hier vielleicht ein Standardwert existieren? Oder braucht die Methode eine klarere Rückgabe, etwa Optional<String>?

Schwierig wird es auch, wenn du Exceptions verschluckst. Ein leeres catch wirkt im ersten Moment harmlos, ist aber einer der schnellsten Wege zu unsichtbaren Fehlern.

try {
    orderService.save(order);
} catch (Exception e) {
}

Damit nimmst du dir selbst jede Chance zu verstehen, warum etwas schiefläuft. Guter Code macht Fehler nicht unsichtbar. Er behandelt sie bewusst. Entweder durch sinnvolles Logging, durch gezielte Exception-Typen oder durch ein Verhalten, das fachlich nachvollziehbar ist.

Dazu kommt die Gewohnheit, Dinge zu früh kompliziert zu bauen. Generische Helferklassen, übertriebene Vererbung, zu viele Abstraktionsebenen oder Patterns an Stellen, an denen ein sauberer Methodenaufruf gereicht hätte, machen Code nicht automatisch besser. Guter Code ist nicht der mit den meisten Fachbegriffen, sondern der mit der klarsten Absicht.

 

Wie du systematisch besser wirst

Der wichtigste Lösungsweg ist nicht, jedes Clean-Code-Prinzip auswendig zu lernen. Viel hilfreicher ist ein fester Blick auf Erkennungsmuster. Frag dich beim Lesen deines Codes immer wieder: Würde ich das in zwei Wochen noch sofort verstehen? Würde ein anderer Entwickler erkennen, wo die Fachlogik steckt? Muss ich scrollen, um eine Methode komplett zu erfassen? Muss ich Kommentare lesen, damit Namen verständlich werden?

Ein gutes Mittel ist kleine, gezielte Überarbeitung direkt nach dem Laufenlassen. Nicht stundenlang perfektionieren, sondern kurz aufräumen. Methode kürzen. Namen verbessern. Doppelte Logik zusammenziehen. Bedingungen lesbarer machen. Das ist in IntelliJ oft in wenigen Minuten erledigt, gerade mit Extract Method, Rename und dem konsequenten Blick auf Warnungen aus der IDE. Gute Werkzeuge ersetzen keine saubere Denkweise, aber sie helfen dir, schlechte Gewohnheiten schneller zu sehen.

Auch Tests helfen dir beim Unterscheiden. Nicht, weil getesteter Code automatisch gut ist, sondern weil schlecht geschnittener Code oft schwer testbar ist. Wenn du für eine einfache Fachregel erst die halbe Anwendung hochziehen musst, ist das selten ein Testproblem, sondern oft ein Designproblem. Gute Methoden haben klare Ein- und Ausgaben. Gute Klassen haben nachvollziehbare Verantwortlichkeiten. Das macht Tests einfacher und den Code meist gleich mit.

Git kann dir ebenfalls viel zeigen. Wenn bei kleinen Änderungen ständig viele Dateien betroffen sind, ist das ein Hinweis auf starke Kopplung. Wenn ein Bugfix an drei Stellen fast denselben Patch braucht, fehlt dir vermutlich eine zentrale Logik. Solche Muster sieht man oft erst im Verlauf, nicht in der einzelnen Datei. Gerade deshalb lohnt es sich, nicht nur Code zu lesen, sondern Änderungen als Ganzes zu betrachten.

Und noch etwas, das gern unterschätzt wird: Lies fremden guten Code. Nicht blind kopieren, sondern beobachten. Wie sind Klassen geschnitten? Wie benennen andere ihre Methoden? Wo endet eine Verantwortung, wo beginnt die nächste? Gerade in etablierten Projekten mit Maven-Struktur, klaren Packages und sauber getrennten Modulen lernst du oft mehr als aus jeder isolierten Regel.

 

Fazit

Schlechter Code ist nicht nur der mit Bugs. Schlechter Code ist auch der, der dich beim Denken ausbremst, Änderungen unnötig riskant macht und seine Absicht versteckt. Guter Code dagegen ist selten spektakulär. Er ist klar, ruhig und vorhersehbar. Genau das macht ihn wertvoll.

Du musst nicht alles sofort perfekt schreiben. Darum geht es nicht. Entscheidend ist, dass du ungünstige Muster erkennst, bevor sie zu Gewohnheiten werden. Zu lange Methoden, unklare Namen, vermischte Verantwortlichkeiten, kopierte Logik, verschluckte Fehler und unnötige Komplexität sind keine Kleinigkeiten. Sie sind Signale. Wenn du lernst, diese Signale früh zu sehen, wird dein Code Schritt für Schritt besser. Nicht, weil er cleverer aussieht, sondern weil er sauberer gedacht ist.