Technische Schulden sind eines dieser Begriffe, die du früh hörst und erst später wirklich spürst. Gemeint ist kein “Fehler”, sondern eine bewusste oder unbewusste Abkürzung im Code oder in der Architektur, die dir heute Zeit spart und dich morgen Zeit kostet. Wie bei einem Kredit: Du bekommst sofort etwas, zahlst aber Zinsen, solange du die Sache nicht sauber nachziehst.

Wenn du ein Feature schnell “irgendwie” fertig machst, dabei aber Tests weglässt, doppelte Logik kopierst oder ein schlechtes Datenmodell akzeptierst, dann entsteht Schuld. Sie ist nicht automatisch schlimm. Manchmal ist sie sogar sinnvoll, wenn du unter Zeitdruck eine Entscheidung treffen musst. Problematisch wird es, wenn die Zinsen anfangen: Änderungen dauern länger, Bugs häufen sich, und du traust dich irgendwann nicht mehr, bestimmte Stellen anzufassen.

 

Was technische Schulden konkret sind

Technische Schulden zeigen sich auf mehreren Ebenen. Im Code sind das zum Beispiel Duplikate, schwer lesbare Methoden, schlechte Benennungen, zu große Klassen, “magische” Zahlen oder fehlende Tests. In der Architektur merkst du sie an harter Kopplung, unklaren Verantwortlichkeiten, fehlenden Schichten oder daran, dass Sonderfälle immer weiter wachsen. Im Build und Betrieb tauchen Schulden als instabile Maven-Abhängigkeiten, unklare Versionen, manuelle Schritte oder fehlende Automatisierung auf. Und auch in der Zusammenarbeit können sie entstehen, etwa wenn unklar ist, was “fertig” bedeutet, Reviews fehlen oder es keinen gemeinsamen Standard gibt.

Warum passiert das? Meistens nicht, weil jemand “schlecht” ist, sondern weil die Situation es begünstigt: Zeitdruck, wechselnde Anforderungen, fehlende Routine oder schlicht unterschätzte Folgekosten. “Das räumen wir später auf” klingt harmlos, wird aber im Alltag schnell zu “nie”.

Du erkennst technische Schulden früh an sehr konkreten Signalen. Eine kleine Änderung zieht plötzlich viele Anpassungen nach sich. Du hast ein ungutes Gefühl, etwas anzufassen, weil es keine Tests gibt. Du landest im Debugger immer wieder in sehr langen Methoden oder in Klassen, die alles irgendwie können. Du kopierst Code, weil es schneller geht, und merkst erst später, dass du dieselbe Regel an drei Stellen ändern musst. Und wenn du regelmäßig Kommentare brauchst, um zu erklären, was der Code macht, ist das häufig ein Zeichen dafür, dass Struktur oder Benennung nicht trägt.

Der gemeinsame Nenner ist: Du bezahlst später. Nicht unbedingt mit “alles neu”, sondern mit kleinen Reibungsverlusten bei jeder Änderung. Je länger du wartest, desto höher werden die Zinsen.

 

Wie du den Großteil von Anfang an vermeidest

Der größte Hebel ist nicht ein Tool, sondern konsequente Kleinarbeit. Ein paar pragmatische Gewohnheiten verhindern sehr viele Schulden, ohne dass du in Perfektion abrutschst.

Fang bei den Basics an: klare Benennungen, kurze Methoden, saubere Verantwortlichkeiten. Wenn du eine Methode nicht in einem Satz erklären kannst, ist sie oft zu groß. Wenn Namen wie “doStuff” oder “handle” ohne Kontext auftauchen, ist das fast immer ein Schuldenkonto. Und wenn du merkst, dass du ständig null-Checks stapelst, lohnt sich ein kleiner Schritt zurück: Was ist hier eigentlich die Erwartung an die Daten?

Ein kurzes Beispiel. Vorher:

public void process(User u) {
    if (u != null && u.getOrders() != null) {
        for (Order o : u.getOrders()) {
            if (o.getTotal() > 100) {
                o.setPriority(true);
            }
        }
    }
}

Nachher, ohne Overengineering:

public void markPriorityOrders(User user) {
    for (Order order : safeOrders(user)) {
        order.setPriority(isPriority(order));
    }
}

private List<Order> safeOrders(User user) {
    return user == null || user.getOrders() == null ? List.of() : user.getOrders();
}

private boolean isPriority(Order order) {
    return order.getTotal() > 100;
}

Du hast hier keinen Trick, sondern Wartbarkeit. Du kannst Regeln ändern, ohne den ganzen Block zu zerlegen, und du liest den Ablauf schneller. Genau das hält Schulden klein.

Der nächste Klassiker sind Duplikate. Kopieren wirkt am Anfang schnell, aber du zahlst doppelt, sobald sich eine Kleinigkeit ändert. In JavaEE-Projekten passiert das gerne bei Validierungen, Mapping und Berechnungen. Wenn du denselben Ausdruck dreimal siehst, ist das ein guter Moment für eine kleine Hilfsmethode oder eine zentrale Komponente, die genau diese Regel kapselt.

Hilfreich ist auch ein einfacher Architektur-Deal mit dir selbst: Fachlogik gehört nicht in die äußerste Schicht. Ein REST-Endpoint, ein Controller oder ein UI-Adapter sollte orchestrieren, nicht entscheiden. Die Entscheidungen leben in einem Service, den du testen kannst. Gerade auf WildFly ist das angenehm: CDI macht Abhängigkeiten sauber, JPA kümmert sich um Persistenz, und ein klarer Service-Layer hält Regeln dort, wo sie hingehören. Das ist nicht “Enterprise um des Enterprise willen”, sondern eine praktische Trennung, die dir später Arbeit spart.

Was dir im Alltag wirklich hilft, ist eine kleine, stabile Definition von “fertig”. Nicht als dickes Dokument, sondern als wiederholbare Linie: Der Code ist verständlich, du akzeptierst Copy-Paste nur mit gutem Grund, und du lässt keine halbherzigen Quickfixes stehen, wenn du sie in zwei Minuten sauber machen kannst. Dazu gehört auch, dass du dir zumindest einen reproduzierbaren Check baust. Idealerweise ein Test, mindestens aber ein Ablauf, der sich lokal zuverlässig wiederholen lässt.

Automatisierung reduziert Schulden sehr effektiv. Wenn dein Projekt in IntelliJ “grün” ist, ist das gut. Wenn es zusätzlich über Maven reproduzierbar baut und testet, ist es besser, weil du “funktioniert nur bei mir” vermeidest. Halte Abhängigkeiten bewusst überschaubar und konsistent, und vermeide Versionsdrift, indem du Versionen zentral steuerst. So bleiben Builds stabil, und du kaufst dir keine schleichenden Probleme ein.

Auch Git zahlt direkt auf Wartbarkeit ein. Kleine Commits, die jeweils genau eine Sache ändern, sind wie kleine Ratenzahlungen: Der Verlauf bleibt nachvollziehbar, Reviews sind einfacher, und ein Revert ist im Zweifel ungefährlich. Große Sammel-Commits wirken effizient, sind aber oft das Gegenteil, wenn später etwas schiefgeht.

 

Was du tust, wenn dir etwas auffällt

Schulden verschwinden selten von selbst. Wenn du eine Stelle bemerkst, die weh tut, hast du ein paar sinnvolle Optionen, ohne gleich alles umzubauen.

Der beste Zeitpunkt für Aufräumen ist oft genau dann, wenn du ohnehin dort bist. Kleine Refactorings sind völlig okay, solange sie das Feature nicht gefährden. In IntelliJ sind “Extract Method”, “Rename” und “Move” schnell gemacht und relativ sicher, wenn du danach kurz testest. Du musst nicht mit dem Ziel starten, “perfekt” zu werden, sondern mit dem Ziel, dass die nächste Änderung leichter fällt.

Wenn es größer ist, mach es sichtbar. Ein Ticket, ein kurzer Kommentar im Issue oder eine Notiz im Backlog reicht. Wichtig ist nur, dass es eine Spur gibt und nicht im Kopf verschwindet. Priorisieren kannst du über Zinsen: Tut es heute schon weh, wird es meist schnell schlimmer. Wird die Stelle häufig geändert, lohnt sich Investition fast immer. Und wenn es sicherheitsrelevant ist oder Daten gefährdet, hat es Vorrang, auch wenn es unangenehm ist.

Wenn du nur einen Grundsatz mitnimmst: Bezahle Schulden in kleinen Raten, nicht als großes “Refactoring-Projekt” irgendwann. Große Umbauten sind riskant, sie konkurrieren immer mit neuen Features und sie scheitern häufig daran, dass unterwegs wieder neues “schnell” dazwischenkommt.

 

Ein realistischer Blick auf Qualität

Qualität heißt nicht, dass alles perfekt ist. Qualität heißt, dass du Änderungen beherrschst. Du wirst Kompromisse machen, das ist normal. Entscheidend ist, ob du sie bewusst machst und ob du sie später wieder einlösen kannst.

Wenn du dir am Anfang einfache Standards setzt, wirst du den Großteil technischer Schulden gar nicht erst aufnehmen. Und wenn doch, erkennst du sie früh und kannst sie gezielt abbauen. Das ist am Ende der Unterschied zwischen einem Projekt, das sich zäh anfühlt, und einem Projekt, das mit dir wächst.