Automatyczna analiza kodu z Checkstyle

Adam Ostrowski

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 {} // violation

Kodowanie

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 {} // violation

Komentarze 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.xml

2.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 checkstyleTest

Jeś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:
Raport 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
Instalacja wtyczki

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 -> +
Konfiguracja wtyczki

Następnie należy wskazać lokalizację pliku z konfiguracją XML:

Definicja nowego pliku z konfiguracją
Potwierdzenie dodania nowego pliku z konfiguracją

Od tej pory wtyczka będzie nas informowała o błędach w czasie rzeczywistym:
Powiadomienie o wystąpienie błędu

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
kobieta pracuje na macbooku pracownicy j labs dwóch mężczyzn i kobieta w biurze