Statische Code-Analyse im KI-Zeitalter: überflüssig oder unverzichtbar?
Seit ein paar Tagen ist Fable 5 da, und der Tenor in jedem zweiten Dev-Thread ist derselbe: Code entsteht jetzt schneller, als ein Mensch ihn lesen kann. Wenn ein Modell in einer Minute einen ganzen Service hinstellt — wozu dann noch ein statischer Analyzer, der dieselben Zeilen nochmal durchkämmt? Sind SonarQube, SonarCloud und Konsorten nicht das Faxgerät unter den Dev-Tools, kurz vor dem Aussterben?
Die kurze Antwort: nein. Die etwas längere, die ich hier vertrete: Genau jetzt, wo KI den Code-Output vervielfacht, wird die deterministische Qualitäts-Schranke wertvoller, nicht überflüssiger. Wer viel produziert, muss viel prüfen — und zwar mit etwas, das nicht selbst probabilistisch rät.
Kurz gesagt — Ein LLM schreibt Code; ein Static Analyzer urteilt reproduzierbar über jede Zeile, bei jedem Commit, nach derselben Regel. Das eine ersetzt das andere nicht. Die KI beschleunigt, Sonar verifiziert. Und genau diese Trennung ist es, die KI-Code auf Produktionsniveau hält.
Die falsche Frage
„KI oder statische Analyse" ist ein Scheingegensatz. Die beiden lösen nicht dasselbe Problem — sie sitzen an verschiedenen Stellen der Pipeline und haben grundverschiedene Eigenschaften.
Ein KI-Review ist kontextstark, aber probabilistisch. Es versteht Intent, erkennt „der Code tut nicht, was der Kommentar verspricht", und argumentiert über Geschäftslogik. Aber es ist nicht reproduzierbar: derselbe Diff, zweimal vorgelegt, liefert nicht garantiert dasselbe Urteil. Es sieht meist nur den Diff, selten die ganze Codebase. Und es kennt kein Pass/Fail, an dem eine Pipeline hart abbrechen kann.
Ein Static Analyzer ist das Gegenteil: kontextarm, aber deterministisch. Dieselbe Regel, dieselbe Zeile, dasselbe Ergebnis — heute, morgen, in jedem Branch. Er scannt 100 % der Codebase, nicht nur den Diff. Er liefert ein hartes Quality Gate, das im CI blockiert. Und er hält jede Messung über Monate als Trend fest.
Wenn aber beides gebraucht wird, lohnt der genaue Blick darauf, was jedes Werkzeug beiträgt — und wo das eine strukturell scheitert, während das andere glänzt.
Was ein Analyzer kann, was ein LLM strukturell nicht kann
Vier Dinge, die kein noch so gutes Modell aus seiner Natur heraus liefern kann:
Reproduzierbarkeit. Eine Aussage wie „dieser Release enthält keine neuen Vulnerabilities der Severity Blocker" muss bei jeder Wiederholung gleich ausfallen — sonst trägt sie weder im Audit noch im Vertrag noch vor dem Kunden. Ein deterministischer Scanner garantiert das. Ein Sampling aus einem Wahrscheinlichkeitsmodell nicht.
Vollständige Abdeckung. Sonar analysiert den gesamten Baum bei jedem Lauf. Ein KI-Reviewer bekommt typischerweise den Diff und ein Stück Kontext — was außerhalb des Fensters liegt, bleibt ungeprüft. Bei einer Mono-Repo mit sieben Backend-Services ist „außerhalb des Fensters" der Normalfall, und genau dort fährt sonst ein Risiko unbemerkt mit.
Ein hartes Gate. sonar.qualitygate.wait=true lässt die Pipeline auf das Urteil warten und bricht ab, wenn neue Issues die Schwelle reißen. Das ist eine binäre, automatisierbare Entscheidung, an der fehlerhafter Code hängenbleibt, bevor ihn ein Mensch übersieht. „Das Modell findet den PR eher okay" ist keine.
Trend über Zeit. Maintainability-Rating, Coverage-Verlauf, Security-Hotspots pro Sprint, Duplication-Quote — Sonar hält das als Zeitreihe. Ein LLM hat kein Projektgedächtnis über Sessions hinweg; es weiß nicht, ob die technische Schuld seit März steigt oder fällt. Sonar weiß es auf den Tag genau.
Dazu kommt die Security-Dimension: Taint-Analysis verfolgt, wie nicht vertrauenswürdiger Input durch den Code bis in eine SQL-Query oder einen Shell-Aufruf fließt — über Methodengrenzen hinweg, mit Mapping auf OWASP und CWE. Das ist reproduzierbarer, auditierbarer Security-Nachweis, kein „sieht mir sicher aus". Und je mehr Code automatisch entsteht, desto mehr Zeilen müssen durch genau diesen Filter.
KI schreibt schnell — wer prüft das Volumen?
Womit wir beim eigentlichen Hebel sind. Die vier Punkte oben klingen nach „nice to have", solange ein Mensch in überschaubarem Tempo Code schreibt. Mit KI kippt das.
Wenn ein Team mit KI-Unterstützung das Drei- oder Fünffache an Code produziert, skaliert die Reviewer-Kapazität nicht mit. Der menschliche Review wird zum Flaschenhals, und die Versuchung, KI-Output „sieht gut aus, merge" durchzuwinken, steigt. Genau dann brauchst du eine objektive, unermüdliche Schranke, die jede einzelne generierte Zeile am selben Maßstab misst — egal ob sie ein Mensch um drei Uhr nachts oder ein Modell in 200 Millisekunden geschrieben hat. Sie ist nicht das, was KI-Tempo ausbremst; sie ist das, was KI-Tempo überhaupt erst verantwortbar macht. Ohne sie ist mehr Output schlicht mehr ungeprüftes Risiko.
Ich nutze KI massiv in der eigenen Entwicklung. Aber alles, was generiert wird, läuft durch dieselbe SonarQube-Instanz wie handgeschriebener Code — same rules, same gate. Das ist kein Misstrauen gegen die KI. Es ist der Grund, warum ich der KI so viel Code überhaupt anvertrauen kann. Wie das konkret aussieht, zeigt das nächste Beispiel.
Praxis: ein eigenes Produkt
Eines meiner eigenen Produkte — ein KI-Ernährungsassistent — ist eine .NET-Mono-Repo mit sieben Backend-Services und vier Frontends (Web, iOS, Android, Watch). Jeder Service hat sein eigenes SonarQube-Projekt (identity, recipe, …), die Apps ihres (web, ios, …), alles läuft gegen eine selbst gehostete SonarQube Developer Edition im k3s-Cluster — erst diese Edition bringt Branch-/PR-Analyse und Taint-Analysis; die kostenlose Community Build kann nur den main-Branch. Kein Code verlässt meine Infrastruktur — für einen datenschutzsensiblen Anwendungsfall ist das kein Detail, sondern Voraussetzung.
Der Backend-Scan hängt im GitLab-CI hinter den Testjobs, damit der Scanner die Coverage-Reports einsammelt:
dotnet sonarscanner begin \
/k:backend \
/d:sonar.host.url="$SONAR_HOST_URL" \
/d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" \
/d:sonar.coverage.exclusions="**/Program.cs,**/Migrations/**,**/Configuration/*Options.cs" \
/d:sonar.pullrequest.key="$CI_MERGE_REQUEST_IID" \
/d:sonar.qualitygate.wait=true
Auf Merge Requests blockiert das Quality Gate — aber es beurteilt nur die Änderungen des MR, nicht die gesamte Altlast. Pre-existing Debt hält also keinen unbeteiligten PR auf. Das ist das „Clean as You Code"-Prinzip: Der neue Code muss sauber sein, der Altbestand wird separat und planbar abgetragen (SonarSource: Clean as You Code).
Das Setup passt zu meinen .NET-Regeln, die ohnehin auf Qualität gepolt sind: Nullable=enable, TreatWarningsAsErrors=true, CA1031 als Error (kein leeres catch), plus Architektur-Tests, die *Service/*Manager/*Helper-Namen und mehr als sieben Konstruktor-Parameter verbieten. Sonar ist die Ebene, die das über die ganze Codebase und über die Zeit durchsetzt, nicht nur lokal beim Build. So weit, so rund. Bis man entdeckt, dass genau dieses Setup auf zwei subtile Arten lügen kann.
Zwei Geschichten aus demselben Repo
Hier kommt der Teil, den die „lass die KI das eben einrichten"-Fraktion unterschätzt. Ein Scanner aufzusetzen ist trivial. Ihn so aufzusetzen, dass er die Wahrheit sagt, ist die eigentliche Arbeit — und die zwei teuersten Fehler sind unsichtbar. Sie werfen keinen Error. Sie melden grün und lügen.
Geschichte 1: Der inkrementelle Build, der Analyzer verschluckt. Der Sonar-Job lief im selben Workdir wie der vorherige Build-Job. Ein inkrementeller dotnet build überspringt dann die Kompilierung bereits gebauter Projekte — und mit ihr laufen die Roslyn-Analyzer für diese Projekte nicht. Ihre Dateien fielen still aus der Analyse. Die gemeldete Coverage war 46 % statt der realen ~97 % und schwankte von Lauf zu Lauf, je nachdem, was zufällig schon gebaut war. Fix: --no-incremental ist hier load-bearing. Eine Zeile, ohne die alle Zahlen Fiktion sind.
Geschichte 2: Der Cache, der alte Coverage wiederverwendet. Sonars Analyse-Cache markierte Dateien als „unverändert", wenn sich nur ihre Tests geändert hatten — und zog dann stale Coverage für sie heran. Neue Tests bewegten die PR-Coverage nie. Man schreibt Tests, der Wert rührt sich nicht, man sucht den Fehler im falschen Code. Fix: sonar.analysisCache.enabled=false. Kostet ~1 Minute extra pro Lauf und liefert dafür korrekte Zahlen.
Das ist der Kern: Statische Analyse ist genau deshalb wertvoll, weil sie nicht rät — aber dieser Wert steht und fällt mit der Konfiguration. Und dieselbe KI, die den Code generiert, ist nicht die Instanz, die ihr eigenes Prüf-Setup absegnen sollte. Das ist die Handwerks-Seite. Die andere Seite ist Governance — und die sieht man am besten im Enterprise.
Praxis: ein Enterprise-Mandat
In einem meiner Enterprise-Mandate — ein Industriekonzern mit vielen Teams — zeigt sich die zweite Hälfte des Bilds: nicht ein Repo sauber halten, sondern Qualität über viele Teams hinweg wiederholbar machen. Hier laufen die .NET-Services über Azure DevOps Pipelines, und die Sonar-Integration kommt aus einem zentralen, geteilten Template-Repo (DevOps/sonar-templates), das jede Service-Pipeline als Ressource einbindet:
resources:
repositories:
- repository: sonar_templates
type: git
name: 'DevOps/sonar-templates'
ref: 'refs/heads/master'
Der Effekt: Quality-Profile, Gate-Schwellen und Scanner-Setup sind einmal zentral konfiguriert und nicht in dutzende Pipelines kopiert, die langsam auseinanderdriften. Ein Standard gilt für alle, Änderungen passieren an einer Stelle. Ein skipSonar-Parameter existiert — als bewusste, sichtbare Ausnahme für Sonderfälle wie Hotfixes, nicht als stiller Default, an dem die Prüfung unbemerkt verschwindet. Und SonarLint liegt mit committeter Regel-Konfiguration (.sonarlint/) direkt in der IDE, sodass dieselben Regeln schon beim Tippen greifen, nicht erst im CI.
Das ist der Unterschied zwischen „ein Tool, das jemand mal angeworfen hat" und einer gepflegten Qualitäts-Infrastruktur: zentral versioniert, konsistent über Teams, mit Audit-Trail und Approval drum herum. Genau diese Ebene liefert dir kein Modell nebenbei. Womit sich der Kreis schließt — zur Frage, wie KI und Sonar eigentlich zusammenspielen.
Sonar und KI sind Komplemente, keine Konkurrenten
So setze ich beide zusammen ein, und so empfehle ich es:
| KI-Modell (LLM) | Static Analysis (Sonar) | |
|---|---|---|
| Stärke | Intent, Logik, Kontext, Geschwindigkeit | Determinismus, Vollabdeckung, Trend |
| Rolle | Generieren + erklärendes Review | Hartes Gate + Security-Nachweis |
| Findet | „tut nicht, was gemeint war" | „reißt Regel X, neue Vulnerability Y" |
| Schwäche | nicht reproduzierbar, Diff-Sicht | versteht Intent nicht, False Positives |
Die KI generiert und gibt das schnelle, kontextstarke Feedback. Sonar zieht die deterministische, auditierbare Linie, die im CI hält. Wo Sonar False Positives produziert, hilft die KI beim Einordnen. Wo die KI Logikfehler übersieht, fängt sie der menschliche Review — den beide Tools entlasten, aber keiner ersetzt. Mehr zur Security-Seite dieses Zusammenspiels in Wie KI-Agents Zugangsdaten leaken.
Fazit
Jede neue, leistungsfähigere KI-Modellgeneration macht die Frage „brauchen wir Static Analysis noch?" nicht obsolet — sie macht sie dringlicher. Je mehr Code eine KI produziert, desto mehr brauchst du eine reproduzierbare, vollständige, unbestechliche Schranke, die jede Zeile am selben Maßstab misst und im CI hart blockiert. Nicht statt KI, sondern damit du KI im großen Stil verantworten kannst.
Und die beiden Geschichten aus diesem Projekt sind die Pointe: Der Wert dieser Schranke hängt komplett an ihrer Konfiguration — sie ist nur so ehrlich, wie jemand sie verdrahtet hat. Deshalb konfiguriere ich solche Tools von Hand, halte sie über die Zeit gepflegt und setze sie ganz bewusst ein, um auch KI-generierten Code auf Produktionsniveau zu halten und Sicherheitslücken zu stoppen, bevor sie ins Release wandern.
Wenn du KI-Geschwindigkeit in deiner Delivery willst, ohne die Qualitäts- und Security-Schranke aufzugeben — und ein Setup brauchst, das die Wahrheit sagt statt nur grün zu leuchten — lass uns reden.