author-avatar
Dominik Martyniak
Quality 9 minut

Jak usprawniać jakość kodu z AI i statyczną analizą kodu

Im więcej kodu powstaje z asystą AI, tym łatwiej o fałszywe poczucie bezpieczeństwa kod wygląda dobrze, kompiluje się, testy przechodzą, a mimo to w środku mogą siedzieć podatności, martwy kod czy metryki, które nic nie mówią, bo są liczone na pół gwizdka. Dlatego zamiast dokładać kolejne AI-review na oko, postanowiłem dodać do projektu twardą, automatyczną warstwę: statyczną analizę kodu, która sprawdza każdy PR według tych samych, mierzalnych kryteriów.

Wybrałem SonarQube Cloud, głównie dlatego że ma darmowy plan dla projektów open source, a dodatkowo udostępnia własny serwer MCP czyli agent AI, z którym pracuję, może sięgnąć po wyniki analizy bezpośrednio, bez przeklejania raportów. Poniżej opisuję, jak to wdrożenie wyglądało krok po kroku w devset-ce (lokalny silnik do testowania systemów event-driven na Kafce i RabbitMQ)  na podstawie trzech konkretnych commitów  i jakie realne korzyści to dało.

1. Workflow analizy w CI

Punkt wyjścia: commit a6746d3, PR #44 — dodanie .github/workflows/sonar.yml i sonar-project.properties.

Workflow buduje backend (Gradle, JDK 25), instaluje i testuje frontend (Node 22), a na końcu odpala oficjalną akcję SonarSource/sonarqube-scan-action:

- name: Build backend
  working-directory: devset-ce-be
  run: ./gradlew build -x test

- name: Test frontend
  working-directory: devset-ce-fe
  run: npm run test

- name: SonarQube Scan
  uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Zwróć uwagę na ./gradlew build -x test — testy backendu były na tym etapie pomijane. To był świadomy pierwszy krok, ale też od razu widoczna luka: Sonar dostawał coverage z frontendu (przez sonar.javascript.lcov.reportPaths), a z backendu żadnego.

sonar-project.properties ustawia sources osobno dla backendu i frontendu, wyklucza build/, dist/, node_modules/ i pliki testowe z analizy głównego kodu, a testy backendu wskazuje osobno przez sonar.tests.

2. Realny coverage z JaCoCo

Drugi krok to commit 91822a8, PR #50 — „wire backend JaCoCo coverage into SonarQube analysis". To bezpośrednia odpowiedź na lukę z punktu 1.

Zmiany w build.gradle: dodanie pluginu jacoco (przypięty do wersji 0.8.14), spięcie go z zadaniami test i integTest przez finalizedBy jacocoTestReport, oraz konfiguracja raportu tak, żeby scalał execution data z testów jednostkowych i integracyjnych:

plugins {
    id 'java'
    id 'jacoco'
    ...
}

jacoco {
    toolVersion = "0.8.14"
}

jacocoTestReport {
    dependsOn test
    executionData fileTree(layout.buildDirectory.dir("jacoco")) { include "*.exec" }
    reports {
        xml.required = true
    }
}

W workflow krok budowania backendu zmienia się z pominięcia testów na pełne uruchomienie testów i raportu:

- name: Build backend with coverage
  working-directory: devset-ce-be
  run: ./gradlew build integTest jacocoTestReport

A w sonar-project.properties dochodzi jedna linijka, która spina to z Sonarem:

sonar.coverage.jacoco.xmlReportPaths=devset-ce-be/build/reports/jacoco/test/jacocoTestReport.xml

Efekt: SonarQube liczy realne pokrycie kodu backendu (unit + integration), a nie tylko frontendowe. To dopiero czyni metrykę coverage w Sonarze wiarygodną — wcześniej była częściowa i mogła dawać fałszywe poczucie bezpieczeństwa.

3. UTF-8 tam, gdzie się o tym nie myśli

Trzecia zmiana to commit 4921e9c, PR #54 — jedna linijka w build.gradle:

tasks.withType(JavaCompile).configureEach {
    options.encoding = 'UTF-8'
    options.compilerArgs += "-parameters"
}

Bez jawnego ustawienia encoding kompilator Javy używa domyślnego kodowania platformy, na której działa build — co na różnych środowiskach CI bywa niespójne i potrafi po cichu psuć znaki spoza ASCII w źródłach czy zasobach. Drobna poprawka, ale dokładnie taka, jaką łatwo przeoczyć, dopóki nie zacznie się jej szukać systematycznie — a nie przy okazji.

Konkretne korzyści

Po tych trzech commitach mam realnie inny punkt startowy niż wcześniej, gdy jedyną linią obrony był ludzki code review:

  • Quality gate blokujący merge. Nowy kod, który nie spełnia progów (duplication, code smells, security hotspots), nie wejdzie do main — nie trzeba tego pilnować ręcznie w review.
  • Wiarygodny coverage zamiast liczby na pokaz. Przed PR #50 backend budował się z -x test, więc metryka pokrycia w Sonarze była w praktyce fikcją. Po podpięciu JaCoCo (unit + integration) coverage backendu odzwierciedla to, co faktycznie jest przetestowane, więc mogę na tej liczbie polegać przy podejmowaniu decyzji, gdzie brakuje testów.
  • Security hotspots i CVE wyłapywane automatycznie. Analiza statyczna i CVE resolutionStrategy w Gradle (PR #54) łapią podatności w zależnościach i podejrzane wzorce w kodzie, zanim ktokolwiek je zobaczy w review — nie polegam już na tym, że recenzent akurat pamięta o konkretnym CVE.
  • Klasy błędów, które normalnie giną w review. Fix z encodingiem UTF-8 to dobry przykład — to nie jest coś, co człowiek zwykle zauważy, czytając diff, a mimo to potrafi po cichu psuć dane w innym środowisku CI.
  • Krótszy cykl feedbacku dzięki MCP. Agent AI dostaje wynik analizy z kontekstem (plik, linijka, typ problemu) i sam proponuje poprawkę — nie muszę ręcznie kopiować raportu z dashboardu do edytora, więc drobne poprawki nie czekają „na później”.
  • Metryki jako dokumentacja stanu projektu. Coverage, duplication i liczba otwartych issues w Sonarze dają mierzalny obraz jakości repo w danym momencie — przydatne, gdy ktoś z zewnątrz (kontrybutor open source) ocenia, czy warto się zaangażować.

Co z tego wynika

Te trzy commity dobrze pokazują, jak w praktyce wygląda dokładanie statycznej analizy do istniejącego projektu: nie jednorazowy „wielki bang", tylko kolejne, małe iteracje. Najpierw podstawowy workflow, nawet z pominiętymi testami. Potem uzupełnienie prawdziwego coverage, bo bez niego metryki są tylko połowiczne. Na końcu drobna poprawka, którą sama analiza (albo build) prędzej czy później by wymusiła.

To, co realnie przyspiesza tę pętlę, to serwer MCP od SonarQube — agent AI dostaje bezpośredni dostęp do wyników analizy i kontekstu (który plik, która linijka, jaki typ problemu), więc przejście od zgłoszenia do konkretnej poprawki w kodzie jest szybsze niż ręczne przeklejanie raportów między dashboardem a edytorem.

Deweloper, agent AI i statyczna analiza kodu robią tu trzy różne rzeczy: deweloper decyduje, co ma sens biznesowo, AI przyspiesza samą pracę, a Sonar pilnuje, żeby żadna z tych dwóch stron nie wprowadziła regresji. Całość spięta w CI/CD oznacza, że quality gate i realne metryki pokrycia działają na każdym PR, zanim cokolwiek trafi do main.

2
[tests, quality]

Więcej od Dominik Martyniak

Więcej artykułów