Softwareentwicklung

Warum du als Junior nicht immer zum Standard-Framework greifen solltest

Der beliebte Clean-Code-Default lautet: erst Regeln, dann Architektur, dann Refactoring, dann Feature. Ich halte das für die falsche Reihenfolge für die meisten Teams, weil sie Junioren beibringt, sichtbare Ordnung höher zu bewerten als überprüfbares Verhalten. Sauberer Code ist wertvoll, aber als Startpunkt macht er oft langsam, teuer und überraschend fragil.

Der Clean-Code-Default macht Junioren langsam, weil er falsche Risiken belohnt

Viele Teams behandeln Clean Code wie ein moralisches Ziel: kurze Funktionen, keine Duplikate, sprechende Namen, SOLID, Tests, Patterns, Schichten. Das klingt vernünftig, aber als Default ist es für die meisten Teams falsch, weil ein Junior nach einem oder zwei Jahren Erfahrung noch nicht zuverlässig unterscheiden kann, ob eine Abstraktion schützt oder nur versteckt.

Der Beitrag Clean Code Prinzipien fuer bessere Softwareentwicklung macht den richtigen Punkt, dass Lesbarkeit Kosten senkt. Ich widerspreche trotzdem der üblichen Umsetzung, weil Teams Lesbarkeit oft mit formaler Eleganz verwechseln und dadurch Code erzeugen, der schön aussieht, aber schwer zu ändern ist.

Ein Beispiel: Du siehst zwei ähnliche Funktionen und extrahierst sofort eine gemeinsame Helper-Funktion. Das fühlt sich nach Clean Code an, ist aber oft voreilig, weil die beiden Stellen fachlich auseinanderlaufen können. Die Duplikation kostet vielleicht 12 zusätzliche Zeilen, ein gemessener Änderungsradius von fünf Dateien pro Feature kostet dagegen Review-Zeit, Merge-Konflikte und Angst vor Seiteneffekten.

Die populäre Regel „keine Funktion über 20 Zeilen“ ist ebenfalls zu grob, weil sie eine lokale Form misst und nicht das Risiko der Änderung. Eine 45-Zeilen-Funktion, die eine HTTP-Anfrage validiert, ein OpenAPI-3.1-Schema prüft und dann klar endet, kann wartbarer sein als sechs private Methoden, zwischen denen du springen musst. 40 Zeilen pro Funktion sind für mich ein einstellbarer Startwert, kein Qualitätsgesetz, weil Teams Domäne, Sprache und Teststil berücksichtigen müssen.

Ich würde nicht als erstes eine große Clean-Architecture-Struktur mit Domain, Application, Infrastructure, Ports und Adapters einführen, weil diese Struktur in kleinen Codebasen mehr Navigationskosten als Schutz erzeugt. Wenn dein Service 18 Endpunkte hat, ist eine flache Modulstruktur mit klaren Namen oft besser, weil du Verhalten schneller findest und weniger Dateien anfassen musst.

Das heißt nicht, dass Clean Code egal ist. Es heißt, dass der Default „alles sofort sauber abstrahieren“ das falsche Optimierungsziel setzt, weil die meisten Teams nicht an zu wenig Schönheit scheitern, sondern an unklaren Grenzen, langsamen Tests, heimlichen Seiteneffekten und Pull Requests, die niemand mehr wirklich versteht.

Ein kleiner Qualitätsvertrag schlägt den Regelkatalog, weil er Verhalten ändert

Ein Regelkatalog wirkt professionell, aber er verliert gegen einen kleinen Qualitätsvertrag, weil Menschen im Alltag nur wenige Regeln zuverlässig anwenden. Für Junioren ist ein Vertrag aus drei bis fünf Teamregeln hilfreicher als 30 Clean-Code-Gebote, weil er Entscheidungen in Pull Requests vorhersehbar macht.

Ein guter Vertrag kann so aussehen: Jede Änderung bekommt einen Test auf der richtigen Ebene. Jede neue Abhängigkeit braucht einen Grund im Pull Request. Jede Funktion darf kompliziert sein, wenn ihr Verhalten direkt getestet ist. Jede Abstraktion braucht mindestens zwei echte Aufrufer. Jede Änderung soll ihren Änderungsradius klein halten.

Diese Regeln sind absichtlich langweilig, weil langweilige Regeln im Alltag überleben. Eine von DORA definierte Gruppe aus vier Metriken, Deployment Frequency, Lead Time for Changes, Change Failure Rate und Time to Restore Service, ist nützlicher als ein subjektiver Clean-Code-Score, weil sie zeigt, ob euer Prozess wirklich schneller und stabiler wird. Die Zahl vier ist hier kein Teamziel, sondern die in der Forschung etablierte Anzahl der Kernmetriken.

Tools helfen, aber nur, wenn sie dem Vertrag dienen. ESLint 9 mit Flat Config, Prettier 3.3 mit printWidth, Ruff 0.6 für Python, Black 24.8, ktlint 1.3, Checkstyle 10, PMD 7, SonarQube 10.6, JaCoCo 0.8.12, pytest 8 und JUnit 5 können Qualität sichtbar machen. Sie können aber keine Prioritäten setzen, weil sie nicht wissen, ob eine Regelverletzung gerade ein echtes Risiko oder nur ein Stilproblem ist.

SonarSource veröffentlicht für Cognitive Complexity häufig den Schwellenwert 15 als Default für Methoden in vielen Sprachen; diese vendor-publizierte Zahl ist nützlich als Warnsignal, aber gefährlich als Dogma, weil manche fachliche Entscheidungen zwangsläufig verzweigt sind. Prettier nutzt standardmäßig printWidth: 80; dieser offiziell dokumentierte Default ist praktisch, aber kein Beweis, dass 100 Zeichen in eurem Repository schlechter wären.

Als Junior solltest du deshalb nicht fragen: „Ist das Clean Code?“ Die bessere Frage lautet: „Welche Änderung wird durch diesen Code leichter, und welche wird schwerer?“ Diese Frage ist unbequemer, aber sie verhindert kosmetisches Refactoring, weil jede Verbesserung einen konkreten Nutzen benennen muss.

Automatisierung darf bremsen, aber nicht den Pull Request übernehmen

Viele Teams kippen in den nächsten falschen Default: Sie automatisieren alles und nennen das Qualität. Das ist verständlich, aber gefährlich, weil ein hartes Tool-Gate leicht die Diskussion ersetzt. Wenn ESLint, SonarQube oder Checkstyle den Pull Request blockieren, streitet das Team über Regelumgehung statt über Designrisiko.

Ich würde Linter zuerst als schnelles Feedback konfigurieren und nur wenige Regeln blockierend machen, weil blockierende Regeln selten, eindeutig und billig zu beheben sein müssen. Formatierung eignet sich gut für harte Automatisierung, weil Prettier 3.3 oder Black 24.8 Diskussionen beendet. Architekturentscheidungen eignen sich schlecht für harte Automatisierung, weil ein Tool nicht erkennt, ob eine temporäre Kopplung fachlich sinnvoll ist.

mkdir clean-default-demo && cd clean-default-demo
npm init -y >/dev/null
npm i -D eslint@9.13.0 @eslint/js@9.13.0 >/dev/null
cat > eslint.config.mjs <<'EOF'
import js from "@eslint/js";
export default [
  js.configs.recommended,
  { rules: { "max-lines-per-function": ["warn", 40] } }
];
EOF
printf 'function ok(){ return 1 }\nconsole.log(ok())\n' > index.js
npx eslint index.js

Dieses kleine Beispiel läuft lokal und warnt erst ab 40 Zeilen pro Funktion. Die 40 ist ein bewusst zu tunender Wert, weil ein Team mit vielen Parsern andere Grenzen braucht als ein Team mit simplen CRUD-Endpunkten. Der wichtige Punkt ist nicht die Zahl, sondern der Status warn, weil Warnungen Lernen auslösen können, während zu viele Fehler nur Umgehungen erzeugen.

Eine gemessene Zahl solltest du aus eurer CI nehmen: den Median der letzten 30 Builds. Wenn dieser Median über 10 Minuten liegt, ist jedes zusätzliche Gate teuer, weil Entwickler Kontext verlieren und Reviews später beginnen. Die 10 Minuten sind hier kein universeller Grenzwert, sondern eine praktische Schwelle, ab der Wartezeit im Alltag spürbar wird.

Testabdeckung ist ähnlich. JaCoCo kann 70 Prozent Line Coverage erzwingen, und diese Grenze kann als interner Zielwert sinnvoll sein, wenn sie kritische Module schützt. Sie wird schädlich, wenn Junioren Tests schreiben, die nur Getter ausführen, weil dann die Kennzahl steigt und das Vertrauen sinkt. Mutation Testing mit PIT 1.15 ist in solchen Fällen ehrlicher, weil es prüft, ob Tests Fehler finden, aber es kostet Laufzeit und sollte deshalb gezielt auf riskante Module laufen.

Auch Standards brauchen Maß. OpenAPI 3.1 und JSON Schema 2020-12 sind stark, wenn ein Service externe Konsumenten hat, weil Vertragsbrüche früh sichtbar werden. Für ein internes Modul ohne Netzwerkschnittstelle ist ein Schema pro Zwischenschicht oft Ballast, weil du mehr Spezifikation als Verhalten pflegst. HTTP/2 nach RFC 9113 oder gRPC über HTTP/2 kann sinnvoll sein, wenn Streaming oder viele kleine Calls dominieren; für einfache Integrationen gewinnt oft REST mit klarer OpenAPI-Beschreibung, weil Debugging mit curl und Logs einfacher bleibt.

Regel-Gate verliert gegen Änderungsradius-Budget in den meisten Produktteams

Die explizite Wahl lautet: Regel-Gate gegen Änderungsradius-Budget. Beide Optionen können gewinnen, aber sie lösen verschiedene Probleme und kosten an verschiedenen Stellen.

Regel-Gate heißt: Der Pull Request wird blockiert, wenn SonarQube 10.6 Quality Gate, ESLint 9, Checkstyle 10, PMD 7 oder Coverage-Grenzen fehlschlagen. Diese Option gewinnt, wenn viele Teams an einem großen Repository arbeiten, wenn Compliance Nachweise verlangt oder wenn bekannte Fehlerklassen zuverlässig maschinell erkennbar sind. Sie kostet Beweglichkeit, weil auch harmlose Verstöße warten müssen, und sie kostet Aufmerksamkeit, weil Entwickler lernen, Regeln formal zu erfüllen.

Änderungsradius-Budget heißt: Das Team begrenzt, wie weit eine Änderung greifen darf, bevor sie aufgeteilt oder begründet wird. Diese Option gewinnt in den meisten Produktteams, weil kleine Pull Requests schneller verstanden werden und weniger Nebenwirkungen verstecken. Sie kostet Disziplin, weil niemand von einem Tool gezwungen wird, und sie akzeptiert vorübergehend unperfekten Legacy-Code, weil nicht jede unschöne Stelle sofort repariert wird.

Ein konkretes Budget kann lauten: Ein Feature-Pull-Request soll höchstens 400 geänderte Zeilen haben; diese Zahl ist ein Teamwert zum Kalibrieren, weil ein UI-Refactoring andere Mengen erzeugt als eine API-Korrektur. Mehr als 400 Zeilen sind nicht verboten, aber erklärungspflichtig, weil Review-Qualität mit wachsender Diff-Größe sinkt. Zwei Reviewer sind als soziale Grenze oft genug, weil ein dritter Reviewer selten neue Details findet und häufig nur Durchlaufzeit erhöht.

Der Regel-Gate-Ansatz sieht sauberer aus, weil er eine grüne Ampel produziert. Das Änderungsradius-Budget ist unbequemer, weil es Verantwortung nicht an Tools delegiert. Genau deshalb ist es für Junioren lehrreicher: Du musst erklären, warum deine Änderung klein genug ist, warum die Abstraktion jetzt nötig ist und warum du eine Stelle bewusst nicht anfasst.

Ich würde bei neuen Teams zuerst das Änderungsradius-Budget einführen und erst danach strenge Gates aktivieren, weil Menschen die Qualitätslogik verstehen sollten, bevor sie von Tools sanktioniert werden. Ein Team, das nur grüne Checks produziert, kann trotzdem schlechten Code schreiben; ein Team, das kleine, getestete Änderungen liefert, hat bessere Chancen, schrittweise wirklich sauberen Code zu entwickeln.

Nachhaltigkeit entsteht durch Rückbau, nicht durch schönere Abstraktionen

Der Beitrag Clean Code Prinzipien fuer nachhaltige Softwareentwicklung betont zu Recht, dass Code langfristig tragfähig sein muss. Mein Einwand ist die Richtung: Nachhaltigkeit entsteht meistens durch Entfernen, Vereinfachen und Begrenzen, nicht durch weitere Schichten, weil jede neue Abstraktion eine künftige Entscheidung vorwegnimmt.

Als Junior erkennst du nachhaltige Software nicht daran, dass sie besonders akademisch wirkt. Du erkennst sie daran, dass du eine fachliche Änderung an einer vorhersehbaren Stelle machen kannst. Wenn ein Rabatt, eine Validierung oder ein Statuswechsel über fünf Interfaces verteilt ist, ist das selten nachhaltig, weil die geistige Last bei jeder Änderung steigt.

Rückbau ist schwerer zu verkaufen als Refactoring, weil er weniger elegant klingt. Trotzdem ist er oft wertvoller. Entferne tote Feature Flags, wenn sie länger als 90 Tage aktiv waren und keine geplante Entscheidung mehr offen ist; diese Frist ist ein Betriebswert, den ihr anpassen solltet, weil manche Releases langsamer laufen. Lösche ungenutzte Adapter, statt sie „für später“ zu behalten, weil ungenutzter Code trotzdem gelesen, getestet und migriert wird.

Architecture Decision Records helfen hier mehr als Architekturdiagramme allein. Ein ADR im Format von Michael Nygard, kurz mit Kontext, Entscheidung und Konsequenzen, zwingt dich zu begründen, warum eine Abstraktion existiert. C4-Diagramme können zusätzlich helfen, wenn sie aktuell bleiben, aber sie werden schnell Dekoration, wenn niemand sie bei Änderungen prüft.

OpenTelemetry 1.36 kann Nachhaltigkeit ebenfalls messbar machen, wenn Traces zeigen, welche Pfade tatsächlich genutzt werden. Das ist besser als Bauchgefühl, weil du ungenutzte Komplexität mit Daten findest. Eine beobachtete P95-Latenz von 250 Millisekunden für einen kritischen Endpunkt kann ein echtes Signal sein; ein perfekt formatierter, aber langsamer Pfad ist dagegen nur hübsch langsam.

Der populäre Clean-Code-Default sagt: Verbessere den Code, sobald du Unordnung siehst. Mein Gegenentwurf sagt: Verbessere den Code, wenn eine konkrete Änderung dadurch sicherer, kleiner oder schneller wird. Diese Position ist weniger romantisch, aber sie passt besser zu Teams, die liefern müssen und trotzdem nicht im Chaos enden wollen.

Öffne deinen letzten Pull Request und markiere drei Stellen: eine Regelverletzung, eine unnötige Abstraktion und eine Änderung, die zu viele Dateien berührt. Schreibe zu jeder Stelle einen Satz, welches Risiko sie wirklich erzeugt. Danach ändere nur die Stelle, deren Risiko du konkret benennen kannst, weil genau dort Clean Code anfängt, nützlich zu werden.