Analiza statyczna

Testowanie

  |   7 min czytania

Definicja

Statyczna analiza kodu wykorzystywana jest w celu znajdywania błędów w kodzie, wyszukiwanie luk i niedopatrzeń oraz sprawdzania czy napisany kod spełnia zasady dobrego programowania i przestrzega wytycznych. Co więcej pozwala na detekcję hipotetycznych błędów, które mogły nie zostać wykrytę przez testy jednostkowe i manualne, a także ułatwia definiowanie i przestrzeganie standardów kodu w zespole. Dzięki temu utrzymanie odpowiedniej jakości kodu staje się łatwiejsze. Statyczna analiza jest częścią procesu ciągłej integracji (continous integration) i może być wykorzystywana również jako narzędzie wspierające testowanie (białoskrzynkowe) oraz refactoring. Dokonywana analiza przeprowadzana jest bez wykonania kodu i odbywa się przy pomocy różnych narzędzi analitycznych takich jak np. lint, checkstyle, pmd czy findbugs dostępnych jako wtyczka (plugin) dla Gradle oraz platform wspierających ciągłą integracje jak np. SonarQube. Weryfikują one zgodność kodu w zestawieniu do zbioru reguł. W przypadku niespełnienia zasad informują o potencjalnych błędach i zagrożeniach. Nierzadko różne wtyczki zawierają podobną funkcjonalność co nie wyklucza ich ze wzajemnego użycia, wręcz przeciwnie, uzupełniają się.

Checkstyle

Checkstyle analizuje kod źródłowy weryfikując jego standardy oraz konwencję w stosunku do zbioru wybranych zasad. Skupia się przede wszystkim na analizie stylu kodu (np. nazewnictwo, klamry). Aby stworzyć konfigurację dla checkstyle należy dodać plik zasad (np. checkstyle.xml) oraz uzupełnić plik gradle aplikacji (lub stworzyć nowy) o nowe zadanie co przedstawia poniższy listing.

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
    "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">

<module name="Checker">
    <!-- more modules and properties -->

    <module name="TreeWalker">

        <module name="MethodName"/>
        <module name="ConstantName"/>
        <module name="UnusedImports"/>
        <module name="ParameterNumber">
            <property name="max" value="2"/>
        </module>

        <!-- more modules and properties -->
    </module>

</module>
apply plugin: 'checkstyle'

task checkstyle(type: Checkstyle) {
    description 'checkstyle analyze'
    group 'verification'
    configFile file('./analyze/checkstyle.xml')
    source 'src/main/java'
    include '**/*.java'
    exclude '**/gen/**'
    classpath = files()
}

Wykonane zadanie checkstyle dla klasy CheckstyleCode zwróci raport z uwagami dotyczącymi nieużywanego import, niepoprawnych nazw dla stałej i metody oraz zbyt dużą ilość argumentów metody.

import java.math.BigInteger; //unused import

public class CheckstyleCode {

    public final static int someConstant = 1; //should be SOME_CONSTANT instead

    public void some_method() { //should be someMethod instead
        //body
    }

    public void methodTooManyParameters(int a, int b, int c) { //too many parameters - max 2
        //body
    }
}

Pmd

Pmd podobnie jak checkstyle przeprowadza analizę kodu w stosunku do zbioru zasad, jednakże koncentruje się na newralgicznych i opuszczonych fragmentach kodu (np. nieużywane zmienne, puste bloki). Aby stworzyć konfigurację dla pmd należy dodać plik zasad (np. pmd.xml) oraz uzupełnić plik gradle aplikacji o nowe zadanie co przedstawia poniższy listing.

<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         name="PMD rules"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">

    <exclude-pattern>.*/R.java</exclude-pattern>
    <exclude-pattern>.*/gen/.*</exclude-pattern>

    <rule ref="rulesets/java/unusedcode.xml"/>
    <rule ref="rulesets/java/empty.xml"/>
    <rule ref="rulesets/java/braces.xml"/>
</ruleset>
apply plugin: 'pmd'

task pmd(type: Pmd) {
    description 'pmd'
    group 'verification'
    ruleSetFiles = files("./analyze/pmd.xml")
    source 'src/main/java'
    include '**/*.java'
    exclude '**/gen/**'
}

Wykonane zadanie pmd dla klasy PmdCode zwróci raport z uwagami dotyczącymi nieużywanej zmiennej, pustej metody, braku klamr dla pętli oraz bloku warunkowego zawsze prawdziwego.

public class PmdCode {

    public void someMethod() {
        int variable = 1; //unused variable

        if(true) {
            //always true
        }

        for(int i=0; i<=1; i++)
            emptyMethod(); //no braces in loop
    }

    public void emptyMethod() {
    }
}

Findbugs

Findbugs działając w oparciu o kod bajtowy wykonuje analizę w poszukiwaniu potencjalnych problemów z listy znanych błędów projektowych i złych praktyk (np. jawnie zapisane hasło, klasa testowa nie posiada testów, metoda prywatna nie jest nigdy wywoływana). Aby stworzyć konfigurację dla findbugs należy dodać plik filtrów (np. findbugs.xml) oraz uzupełnić plik gradle aplikacji o nowe zadanie co przedstawia poniższy listing.

<FindBugsFilter>

    <Match><Class name="~.*R\$.*"/></Match>
    <Match><Class name="~.*Manifest\$.*"/></Match>

</FindBugsFilter>
apply plugin: 'findbugs'

task findbugs(type: FindBugs) {
    description 'findbugs'
    group 'verification'
    excludeFilter file('./analyze/findbugs.xml')
    classes = files("$project.buildDir/intermediates/javac")
    source 'src/main/java'
    effort 'max'
    classpath = files()
}

Wykonane zadanie findbugs dla kodu bajtowego klasy FindbugsCode zwróci raport z uwagami dotyczącymi nieużywanej metody prywatnej, porównywania obiektów typu String za pomocą operatora == oraz ignorowanie rezultatu metody.

public class FindbugsCode {

    public void someMethod() {
        //boxed primitive allocated only for String value
        String text = new Integer(1).toString(); //should use just Integer(1).toString()
        equalityMethod(text, "b");
    }

    private boolean equalityMethod(String a, String b) {
        boolean equality = (a == b); //== instead of equals
        return equality;
    }

    private void unusedPrivateMethod() {
    }
}

Lint

Lint jest narzędziem analitycznym, który dokonuje skanowania struktury projektu Android w poszukiwaniu potencjalnych błędów. Sprawdza pliki źródłowe i zasoby pod kątem optymalizacji poprawności, bezpieczeństwa, wydajności, użyteczności, dostępności i wielojęzykowości. Z uwagi na integrację z Android Studio, mnogości funkcjonalności oraz weryfikację nie tylko kodu źródłowego, ale i zasobów projektu, a także wsparcia dla Kotlin jest rekomendowanym narzędziem analizy statycznej dla programistów Android. Poza podstawową funkcjonalnością znaną z pozostałych narzędzi analitycznych pozwala także na znalezienie błędów m.in. w obszarze brakujących tłumaczeń, nieodpowiednich rozmiarów czy gęstości ikon, nieużywanych zasobów, problemów z plikami layout i Manifest. Aby uruchomić pełną konfigurację dla lint wystarczy wykonać zadanie w zależności od typu build (lint, lintDebug, lintRelease). Wybierając Analyze z menu kontekstowego środowiska programistycznego można dokonać spersonalizowanej inspekcji kodu ze względu na lokalizację, charakter czy przedmiot analizy.

SonarQube

SonarQube jest platformą wspierającą proces ciągłej integracji poprzez użycie narzędzi do analizy statycznej oraz prezentacje wyników w przyjaznej formie graficznej. Skupia się przede wszystkim na kondycji projektu, wyciekach pamięci, wyszukiwaniu błędów w strukturze kodu i łamaniu zasad dobrego kodu, wykrywaniu luk w bezpieczeństwie, analizie gałęzi projektu czy śledzenie długu technologicznego. Dostępny jest dla ponad 20 języków programowania (w tym Java, Kotlin, C++) i oferuje limitowane darmowe użycie poprzez aplikację internetową lub desktopową. SonarQube oferuje również wtyczkę SonarLint przeznaczoną dla środowiska programistycznego, która przeprowadza analizę w rzeczywistym czasie pracy programisty wyświetlając odpowiednie uwagi i wskazówki.