Automatyczna analiza kodu z Checkstyle
Wstęp
Utrzymanie wysokiej jakości kodu jest kluczowym elementem skutecznego i efektywnego rozwoju oprogramowania.
W tym kontekście narzędzia do kontroli jakości kodu odgrywają istotną rolę, a jednym z najpopularniejszych jest Checkstyle.
W tym artykule przedstawię jak w prosty sposób wdrożyć to narzędzie do naszej codziennej pracy na przykładzie aplikacji skonfigurowanej w Gradle.
1. Inicjacja projektu Gradle
mkdir ~/j-labs-blog-checkstyle
cd ~/j-labs-blog-checkstyle
gradle init --type java-application --dsl groovy --test-framework testng --project-name j‑labs-blog-checkstyle --package jlabsblog.jwt 2. Konfiguracja Checkstyle
Konfiguracja Checkstyle dzieli się na dwa elementy, które zostaną opisane w kolejnych podrozdziałach:
- Włączenie pluginu (oraz ewentualna dodatkowa konfiguracja) w pliku konfiguracyjnym Gradle,
- Konfiguracja w postaci XML zawierająca listę zasad, zgodnie z którymi będzie testowany kod.
2.1 Konfiguracja Gradle
Aby włączyć Checkstyle, należy dodać plugin checkstyle do konfiguracji build.gradle:
plugins {
// ...
id 'checkstyle'
}2.2 Zawartość checkstyle.xml
Składnia pliku checkstyle polega na 2 elementach:
- Document Type Definition (DOCTYPE)
- Lista reguł (Znaczniki
module)
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="TreeWalker">
...
<module name="MethodName">
<property name="format" value="^[a-z]([a-zA-Z0-9]+)*$"/>
</module>
<module name="MethodLength">
<property name="max" value="4"/>
</module>
...
</module>
</module>Poniżej przedstawię kilka przykładowych reguł wraz z przykładem użycia.
Adnotacje
Jest to kategoria reguł stosowana do walidacji adnotacji. Przykładem z tej kategorii jest reguła AnnotationLocation, która będzie sprawdzać, czy występuje więcej niż jedna adnotacja w tej samej linijce:
// <module name="Checker">
// <module name="TreeWalker">
// <module name="AnnotationLocation"/>
@SomeAnnotation @OtherAnnotation // violation
public void foo() {
}
@SomeAnnotation
@OtherAnnotation
public void bar() {
}Bloki kodu
Jest to kategoria reguł stosowana do walidacji bloków kodu. Przykładem z tej kategorii jest reguła AvoidNestedBlocks, która będzie sprawdzać, czy występuje zagnieżdżony blok:
// <module name="Checker">
// <module name="TreeWalker">
// <module name="AvoidNestedBlocks"/>
public void foo() {
{ // violation
System.out.println();
}
}
public void bar() {
System.out.println();
}Projekt klas
Jest to kategoria reguł stosowana do walidacji projektu klasy. Przykładem z tej kategorii jest reguła OneTopLevelClass, która będzie sprawdzać, czy w pliku występuje więcej niż jedna definicja niezagnieżdżonej klasy:
// <module name="Checker">
// <module name="TreeWalker">
// <module name="OneTopLevelClass">
// --- Foo.java ---
public class Foo {}
class Bar {} // violationKodowanie
Jest to kategoria reguł stosowana do walidacji pisanego kodu. Przykładem z tej kategorii jest reguła ReturnCount, która będzie sprawdzać, czy implementacja metod nie przekracza dozwolonej ilości deklaracji return (domyślnie 2 dla zwracających typ i 1 dla void) :
// <module name="Checker">
// <module name="TreeWalker">
// <module name="ReturnCount">
// <property name="max" value="2"/>
public String foo(int i) { // violation - Return count is 3
switch (i) {
case 200: return "ok";
case 400: return "client_error";
}
return "unknown";
}
public String bar(int i) {
return switch (i) {
case 200 -> "ok";
case 400 -> "client_error";
default -> "unknown";
};
}Nagłówki
Jest to kategoria reguł stosowana do walidacji nagłówków plików. Przykładem z tej kategorii jest reguła Header, która będzie sprawdzać, czy występuje odpowiedni nagłówek.
// <module name="Checker">
// <module name="Header">
// <property name="header" value="// Copyright (C) J-Labs\n// All rights reserved"/>
// --- Foo.java ---
public class Foo {} // violation
// --- Bar.java ---
// Copyright (C) J-Labs
// All rights reserved
public class Bar {}Importy
Jest to kategoria reguł stosowana do walidacji importów. Przykładem z tej kategorii jest reguła IllegalImport, która będzie sprawdzać, czy występuje nieodpowiedni import.
// <module name="Checker">
// <module name="TreeWalker">
// <module name="IllegalImport"/>
// <property name="illegalPkgs" value="forbiddenmodule"/>
// --- Foo.java ---
import forbiddenmodule.ForbiddenClass; // violation
public class Foo {} // violationKomentarze JavaDoc
Jest to kategoria reguł stosowana do JavaDoc. Przykładem z tej kategorii jest reguła JavadocMethod, która będzie walidować istniejące dokumentacje. W poniższym przykładzie zostanie zgłoszony jeden błąd z powodu braku opisu parametru i:
// <module name="Checker">
// <module name="TreeWalker">
// <module name="JavadocMethod"/>
/**
*
*/
public void foo(int i) {} // violation
public void bar(int i) {}
/**
*
* @param i
*/
public void baz(int i) {}Konwencje nazewnictwa
Jest to kategoria reguł stosowana do walidacji nazewnictwa. Przykładem z tej kategorii jest reguła LocalVariableName, która będzie walidować nazwy zmiennych lokalnych:
// <module name="Checker">
// <module name="TreeWalker">
// <module name="LocalVariableName"/>
public void foo(int i) {
int VAR = 0; // violation
int variable = 0;
}Lista możliwych do zaaplikowania reguł dostępna jest bezpośrednio na stronie wtyczki Checkstyle
Jeśli specyfika naszego projektu wymaga pominięcia poszczególnych klas możemy to skonfigurować przy pomocy pliku supressions.xml, który podobnie jak checkstyle polega na 2 elementach:
2.3 Zawartość supressions.xml
- Document Type Definition (DOCTYPE)
- Lista reguł (Znaczniki
supressions)
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD Suppressions 1.1//EN"
"https://checkstyle.org/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files="DatabaseMigrationScript.java" checks="MethodName"/>
</suppressions>Aby zmiany zadziałały należy uwzględnić plik supresssions.xml w checkstyle.xml:
<module name="Checker">
...
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
</module>
</module>2.4 Lokalizacja konfiguracji XML
Domyślnie konfiguracja checkstyle.xml powinna znajdować się w głównym folderze projektu:
<root>
- config
-- checkstyle
--- checkstyle.xml
--- suppressions.xml2.5 Nadpisanie domyślnej konfiguracji w Gradle
checkstyleMain
Jeśli chcemy nadpisać spodziewane miejsce konfiguracji XML należy wskazać je w konfiguracji build.gradle:
checkstyleMain {
configFile = file("${rootDir}/app/src/main/resources/config/checkstyle/checkstyle.xml")
configDirectory = file("${rootDir}/app/src/main/resources/config/checkstyle")
}To podejście pozwala nam również na separację konfiguracji pomiędzy modułami, wystarczy dodać powyższe ustawienia w plikach build.gradle każdego z modułów, a następnie dodać konfigurację XML w odpowiednich miejscach, przykładowo:
<root>
- app
-- config
--- checkstyle
---- checkstyle.xml
---- suppressions.xml
- app2
-- config
--- checkstyle
---- checkstyle.xml
---- suppressions.xml
...checkstyleTest
Aby ustalić osobną konfigurację dla testów, należy wskazać plik z konfiguracją w build.gradle :
checkstyleTest {
configFile = file("${rootDir}/config/checkstyle/checkstyle-test.xml")
configDirectory = file("${rootDir}/app/src/main/resources/config/checkstyle")
}3. Raport
Aby uruchomić Checkstyle należy zbudować projekt:
gradle build
lub
gradle checkstyleMain checkstyleTestJeśli w kodzie znajdują się błędy, konsola zwróci błąd, a w logach znajdzie się odpowiednia wiadomość, na przykład:
> Task :app:checkstyleMain
[ant:checkstyle] [ERROR] \j-labs-blog-checkstyle\app\src\main\java\jlabsblog\App.java:8:17: Name 'Bad_Method_Name' must match pattern '^[a-z]([a-zA-Z0-9]+)*$'. [MethodName]
> Task :app:checkstyleMain FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:checkstyleMain'.
> A failure occurred while executing org.gradle.api.plugins.quality.internal.CheckstyleAction
> Checkstyle rule violations were found. See the report at: file:///j-labs-blog-checkstyle/app/build/reports/checkstyle/main.html
Checkstyle files with violations: 1
Checkstyle violations by severity: [error:1]W zwróconym wyniku w konsoli będzie również widoczny link do raportu w postaci HTML:
4. Konfiguracja IDE
Największą korzyść z używania wtyczki Checkstyle możemy osiągnąć poprzez integrację ze środowiskiem programistycznym.
Lista dostępnych integracji dostępna jest na stronie Checkstyle.
W kolejnych podrozdziałach zostanie przedstawiony przykład integracji z Intellij IDEA na podstawie wtyczki Checkstyle-IDEA, która pozwala na analizowanie kodu i podkreślanie błędów w czasie rzeczywistym.
4.1 Instalacja wtyczki
Otwórz ustawienia IDE i przejdź do instalacji wtyczki:
File -> Settings (Ctrl+Alt+S) -> Plugins -> Checkstyle-IDEA
4.2 Konfiguracja wtyczki
Po restarcie aplikacji ponownie otwórz ustawienia IDE i przejdź do konfiguracji wtyczki:
File -> Settings (Ctrl+Alt+S) -> Tools -> Checkstyle -> Configuration File -> +
Następnie należy wskazać lokalizację pliku z konfiguracją XML:


Od tej pory wtyczka będzie nas informowała o błędach w czasie rzeczywistym:
4.3 Uwzględnienie wielu konfiguracji XML
Jeśli w naszym projekcie znajduje się wiele modułów, które mają odrębne konfiguracje XML, należy dla każdego z nich powtórzyć poprzedni punkt.
Z racji tego, że wtyczka Checkstyle-IDEA nie uwzględnia konfiguracji build.gradle, konfiguracja z punktu checkstyleMain nie zostanie zastosowana.
W związku z tym należy zastosować workaround, który spowoduje użycie konfiguracji checkstyle.xml jedynie w obrębie podanego modułu / pakietu.
W poniższym przykładzie będzie to główny pakiet jlabsblog w module app:
<module name="Checker">
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="^(?!.*([\\/]app[\\/]src[\\/]main[\\/]java[\\/]jlabsblog[\\/].*\.java)).*$"/>
...
</module>
</module>Jest to ekwiwalent konfiguracji w checkstyleMain:
checkstyleMain {
source = [
'../app/src/main/java/jlabsblog'
]
configFile = file("${rootDir}/app/src/main/resources/config/checkstyle/checkstyle-test.xml")
configDirectory = file("${rootDir}/app/src/main/resources/config/checkstyle")
}Podsumowanie
Checkstyle to wartościowe narzędzie wspierające utrzymanie wysokiej jakości kodu w projektach programistycznych.
Automatyczna analiza kodu zapewnia zgodność z przyjętymi standardami, eliminuje błędy oraz ułatwia codzienną pracę poprzez wspieranie procesu code-review.
W powyższym artykule został przedstawiony sposób włączenia wtyczki w projekcie Gradle. Na konkretnym przykładzie zostało pokazane jak skonfigurować narzędzie w przypadku wielomodułowych aplikacji.
Po przeczytaniu tego artykułu powinieneś umieć skonfigurować Checkstyle w swoim projekcie tak, aby w czasie rzeczywistym wyłapywać błędy oraz wszelkie złamania konwencji przyjętych w Twoim zespole.
Bibliografia
- https://docs.gradle.org/current/userguide/checkstyle_plugin.html
- https://checkstyle.org/checks.html
- https://github.com/checkstyle/checkstyle/issues/991
- https://github.com/checkstyle/checkstyle/blob/master/config/checkstyle-checks.xml
Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!
Skontaktuj się z nami


