Checkstyle




Allgemeines

Checkstyle ist ein Open Source Programm, das die Einhaltung von Coding Conventions in Java Programmen unterstützt. Die Entwickler bringen den Nutzen auf den Punkt:

It automates the process of checking Java code to spare humans of
this boring (but important) task

 

Durch den hohen Konfigurationsgrad lässt sich fast jeder beliebige Coding Standard einstellen und überprüfen.

Seit Version 3 überprüft Checkstyle nicht mehr nur das Layout des Codes, auch andere Schwachstellen werden mittlerweile erkannt (z.B. Codeverdopplung, Probleme beim Klassendesign, bug patterns).


Konfiguration

Eine Checkstyle Konfiguration ergibt sich aus der Zusammensetzung der verschiedenen Modulen bzw. Checks, die man auf den Code anwenden will

Für jede Regel/jeden Problemfall gibt es ein Modul, welches die entsprechende Regel überprüft
So überprüft z.B. das Modul FinalClass ob jede Klasse, die nur private Konstruktoren enthält, als final deklariert ist. Dagegen überprüft das Modul MethodLength die Länge jeder Methode auf einen bestimmten Wert. Dieser Wert ist eine Eigenschaft des Moduls MethodLength.

Jedes Modul hat bestimmte Eigenschaften, die sich verändern lassen (z.B. die Eigenschaft max des Moduls MethodLength).

Es gibt insgesamt 125 Module die Checkstyle zur Verfügung stellt. Durch die beliebige Zusammenstellung dieser Module lässt sich fast jeder beliebige Coding Standard unterstützen.

Außerdem lässt sich Checkystyle leicht erweitern: es können eigene Checks/Module geschrieben und importiert werden.


Eigene Konfigurationen

Mit dem Eclipse Checkstyle Plugin lassen sich leicht eigene Konfigurationen erstellen. Dies geschieht entweder durch die Zusammenstellung der einzelnen Module/Checks über die GUI des Plugins oder durch das Laden eines entsprechenden XML-Dokuments. Im Folgenden wird der Aufbau eines solchen XML-Dokuments anhand eines Beispiels erklärt.

Beispiel XML-Dokument myConfig.xml


1     <module name="Checker">
2       <property name="severity" value="error"/>
3       <module name="PackageHtml"/>
4       <module name="NewLineAtEndOfFile"/>
5       <module name="TreeWalker">
6         <property name="cacheFile" value="target/cachefile"/>
7         <property name="tabWidth" value="4"/>
8         <module name="FinalClass"/>
9         <module name="MethodLength">
10          <property name="max" value="60"/>
11        </module> 
12        <module name="TypeName">
13          <property name="format" value="^I_[a-zA-Z0-9]*$"/>
14          <property name="tokens" value="INTERFACE_DEF"/>
15        </module>     
16        <module name="LineLength">
17          <property name="severity" value="info"/>
18        </module>
19      </module>
20      <module name="SeverityMatchFilter">
21        <property name="severity" value="info"/>
22        <property name="acceptOnMatch" value="false"/>
23      </module>
24    </module>
    

Das Wurzelelement ist immer das Checkermodul (Zeile 1). Dieses enthält den Checkstyle Kernel. Die folgenden Unterelemente sind die Module, die verwendet werden sollen.

So werden in Zeile 3 und 4 die beiden Module/Checks PackageHtml (prüft ob jedes Paket einen HTML Kommentar besitzt) und NewLineAtEndOfFile (prüft das Dateiende auf eine Leerzeile) eingebunden.

Bei dem in Zeile 5 eingebundenen Treewalker handelt es sich um ein besonderes Modul. Dieses Modul bekommt eine .java-Datei und erstellt anhand dieser einen abstrakten Syntaxbaum (AST). Jeder Knoten in diesem Baum hat ein Token. Bei der Traversierung werden callbackartig die entprechenden Untermodule von TreeWalker aufgerufen,
die sich für das Token des aktuellen Knotens registriert haben. In unserem Beispiel hat das TreeWalker Modul folgende Untermodule:

Zeile 8, FinalClass (überprüft ob jede Klasse, die nur private Konstruktoren besitzt, als final deklariert ist)
Zeile 9, MethodLength (überprüft jede Methode auf eine bestimmte Länge)
Zeile 12, TypeName (überprüft Klassen/Interfaces auf eine bestimmte Namenskonvention)
Zeile 16, LineLength (überprüft jede Zeile auf eine bestimmte Länge)

All diese Module benötigen zur Ausführung den abstrakten Syntaxbaum des Quelltextes und müssen deshalb als Unterlemente des TreeWalker Moduls eingebunden werden. Jedes dieser Untermodule hat sich für bestimmte Tokens registriert (z.B. das Modul MethodLength für das Token METHOD_DEF, welches für den Beginn einer Methode steht).

 

 

Abstrakter Syntaxbaum (die zweite Spalte enthält die Tokens)

 

Jedes Modul kann wiederum als Unterlemente sogenannte propertys haben. Durch diese lassen sich die Eigenschaften eines Moduls verändern.

So wird in unserem Beispiel im Modul MethodLength die Eigenschaft max auf den Wert 60 gesetzt (Zeile 10).

Für das TypeName Modul legen wir per propertys fest, dass es nur für das Token INTERFACE_DEF registriert wird (Zeile 14, so werden nun nur Interfacenamen überprüft, per Default würden sonst auch Klassennamen überprüft werden) und geben einen regulären Ausdruck an auf den geprüft werden soll (Zeile 13, alle Interfaces sollen mit "I_" beginnen).

Diese property-Elemente können entweder global (wie in Zeile 2, 6, 7) oder lokal (z.B. Zeile 10) deklariert werden. Globale property-Elemente gelten für alle folgenden Module der gleichen oder niedrigeren Hierachiestufen im XML-Dokument. So gilt die Eigenschaft, die in Zeile 2 festgelegt wurde, für alle Module des Dokuments. Die Eigenschaften in Zeile 6 und 7 jedoch nur für alle Untermodule des TreeWalker-Elements.

Globale propertys werden von allen Modulen verwendet, die im Gültigkeitsbereich liegen und die diese Eigenschaft besitzen. So wird in Zeile 2 für alle Module die Eigenschaft "severity" festgelegt. Diese Eigenschaft hat jedes Element. Sie gibt an welchen Eclipse-Schweregrad die Meldungen eines Moduls haben, falls dieses einen Fehler gefunden hat. In Zeile 2 wird nun z.B. festgelegt, dass alle Modulmeldungen als "error" angezeigt werden sollen. Für einzelne Module lässt sich dies dann noch ändern, wie z.B. in Zeile 17.

Das Checkermodul enthält auch einige Filter-Module. So lassen sich z.B. Meldungen eines bestimmten Schweregrades filtern. Dies geschieht durch das SeverityMatchFilter Modul (Zeile 20-23). Hier legen wir fest, dass alle Meldungen mit dem Schweregrad "info" nicht angezeigt werden.

Dieses fertige XML-Dokument können wir nun über die GUI des Plugins als Konfiguration festlegen.


Eigene Checks

Es gibt zwei Arten von Checks:

FileSetChecks sind also direkte Untermodule des Wurzelelements, Checks hingegen sind Untermodule des TreeWalker Moduls.

Im folgenden Beispiel wollen wir nun einen Check mit dem Namen MethodLimitCheck schreiben, der die Anzahl der
Methoden in einer Klasse/einem Interface überprüfen soll.

Wir erstellen also ein neues Java Projekt in Eclipse und legen eine neue Klasse "MethodLimitCheck" an.

Beispiel MethodLimitCheck.java


1     package myChecks;
2     import com.puppycrawl.tools.checkstyle.api.*;
3    
4     public class MethodLimitCheck extends Check
5     {
6         private int max = 30;
7    
8         public void setMax(int limit)
9         {
10            max = limit;
11        }
12    
13        public int[] getDefaultTokens()
14        {
15            return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
16        }
17    
18        public void visitToken(DetailAST ast)
19        {
20            DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
21    
22            int methodDefs = objBlock.getChildCount(TokenTypes.METHOD_DEF);
23    
24            if (methodDefs > max) {
25                log(ast.getLineNo(),
26                    "too many methods, only " + max + " are allowed");
27            }
28        }
29    }
    

Zu Beginn schnüren wir ein Paket mit beliebigen Namen (Zeile 1) und binden die Checkstyle API ein (Zeile 2).

Anschließend deklarieren wir unsere Klasse MethodLimitCheck (über den Namen der Klasse wird der Check später in der Konfiguration eingebunden) und leiten diese von Check ab (Zeile 4).

In Zeile 6 deklarieren wir die Klassenvariable max und initialisieren sie mit 30. Dies ist eine Eigenschaft unseres Moduls, mit dem default Wert 30.

Um diese Variable/Eigenschaft auch später ändern zu können deklarieren wir eine Set-Methode (Zeile 8-11). Dabei entspricht der Name der nach dem "set" folgt dem Namen, mit dem wir später die Eigenschaft ansprechen. Nennen wir also unsere Set-Methode "setMaximum" so können wir später die Eigenschaft über den Namen "maximum" ansprechen. Nennen wir die Methode "setMax", so ist der Name der Eigenschaft "max", usw.

Die Methode in Zeile 13-16 dient zum Registrieren der Tokens. Wir wollen dass die Methodenanzahl jeder Klasse und jedes Interfaces überprüft wird, also registrieren wir hier die Tokens CLASS_DEF und INTERFACE_DEF.

Die Methode visitToken wird immer dann ausgeführt wenn beim Traversieren des abstrakten Syntaxbaums ein Knoten mit einem registrierten Token besucht wird. In der Methode passiert nun Folgendes:

Zuerst sucht man den ersten Knoten mit dem OBJBLOCK-Token (Zeile 20), denn die Methoden, deren Anzahl überprüft werden soll, sind Kinder dieses Knotens (siehe Abbildung oben, die Tokens METHOD_DEF sind alle Kinder des OBJBLOCK-Tokens).

Nun zählt man ausgehend von diesem OBJBLOCK-Knoten alle Kinder mit dem Token METHOD_DEF (Zeile 22). So ermittelt man die Anzahl der Methoden dieser Klasse/dieses Interfaces.

Sollte diese Anzahl größer sein als der festgelegte Maximalwert gibt das Modul eine Meldung aus (Zeile 24-27).


Integration eines eigenen Checks

Um diesen Check nun in eine Konfiguration zu integrieren muss man Folgendes machen:

Zuerst erstellt man aus dem Java Projekt eine .jar-Datei. Diese kopiert man in den "extension-libraries" Ordner des Checkstyle Plugins (z.B. PfadZuEclipse/plugins/com.atlassw.tools.eclipse.checkstyle_4.4.2/extension-libraries, je nach Version verschieden).

Nach einem Neustart von Eclipse ist dieses Modul nun auch dem Checkstyle Plugin bekannt. Um diesen Check nun auch über die GUI des Plugins auswählen zu können müsste man noch weitere XML-Dokumente mit den benötigten Metainformationen erstellen und dem JAR-Archiv hinzufügen. Der Aufbau dieser zusätzlichen XML-Dateien ist hier beschrieben.

Da man aber auch Konfigurationen aus XML-Dateien laden kann wollen wir nun unseren Check in unser XML-Dokument (siehe oben) einbinden:


1     <module name="Checker">
2       <property name="severity" value="error"/>
3       <module name="PackageHtml"/>
4       <module name="NewLineAtEndOfFile"/>
5       <module name="TreeWalker">
6         <property name="cacheFile" value="target/cachefile"/>
7         <property name="tabWidth" value="4"/>
8         <module name="FinalClass"/>
9         <module name="MethodLength">
10          <property name="max" value="60"/>
11        </module> 
12        <module name="TypeName">
13          <property name="format" value="^I_[a-zA-Z0-9]*$"/>
14          <property name="tokens" value="INTERFACE_DEF"/>
15        </module>     
16        <module name="LineLength">
17          <property name="severity" value="info"/>
18        </module>
19        <module name="myChecks.MethodLimitCheck">
20 <property name="max" value="2"/>
21 </module>
22 </module> 23 <module name="SeverityMatchFilter"> 24 <property name="severity" value="info"/> 25 <property name="acceptOnMatch" value="false"/> 26 </module> 27 </module>

Unser Check muss ein Unterlement des TreeWalker Moduls sein, da der abstrakte Syntaxbaum benötigt wird. Der Name des Moduls ergibt sich aus dem Paketpfad (myChecks) und dem Klassennamen (MethodLimitCheck). Über das property-Elementsetzen wir die Eigenschaft max (unsere Set-Methode heisst "setMax") auf 2. Somit wird nun für alle Klassen/Interfaces überprüft ob sie mehr als zwei Methoden enthalten.

Diese XML-Datei importieren wir jetzt wieder über die GUI des Plugins und unser Check ist einsatzbereit.


Anwendung unter Eclipse

Folgende Angaben beziehen sich auf das Eclipse Checkstyle Plugin.Die Einstellungen von Checkstyle findet man unter den Projekteigenschaften. Als Default sind die Sun Coding Conventions aktiviert.

 

Um nun eine eigene Konfiguration zu verwenden muss man diese zunächst erstellen. Dies geschieht durch einen Klick auf "Neu..." im Tab "Lokale Checkstyle-Konfigurationen". Hier wählt man nun aus ob es sich um eine interne (das Auswählen der Module erfolgt über die GUI) oder externe (XML-Datei) Konfiguration handelt, gibt evtl. den Pfad zur Datei an und vergibt einen Namen.

 

Interne Konfigurationen lassen sich durch einen Klick auf "Konfigurieren" nun nach Belieben einstellen. Auch externe Konfigurationen können so jederzeit verändert werden (Die XML-Datei wird dann angepasst).

 

Nun erstellt man im Tab "Main" ein neues Datei-Set. Dabei legt man über einen regulären Ausdruck fest welche Dateien überprüft werden sollen (default: alle .java Dateien) und wählt die gewünschte Konfiguration aus.


Ist das Datei-Set aktiviert ist die dazugehörige Konfiguration einsatzbereit.

Führen wir Checkstyle mit unserer erstellten externen Konfiguration (myConfig.xml, siehe Beispiel oben) auf ein Beispielprojekt aus (Rechtsklick auf eine Klasse, ein Paket oder ein Projekt -> Checkstyle -> Überprüfen), bekommen wir folgende Ausgabe:

 

Checkstyle Meldungen werden im Editorfenster durch ein Lupensymbol am linken Rand dargestellt. Fährt man mit der Maus darüber erhält man nähere Informationen darüber, die auch im "Problems"-Tab angezeigt werden. Folgende Fehler wurden hier erkannt: