More than 1 year has passed since last update.

Checkstyle とは

Java のソースコードがコーディング規約に即しているかどうか判定するための静的解析ツール。

インデントサイズや変数名のつけ方(キャメルケースかどうかとか)、空白スペースの入れ方など、コーディングスタイルに関するチェックを行うことができる。

ビルドプロセス内に組み込むことで、自動でコーディングスタイルをチェックできるようになり、規約違反があればビルドを失敗させることができるようになる。

機械的にチェックできる問題はツールに任せることで、「実装が仕様を満たしているか?」「致命的なバグはないか?」「もっと効率的な実装方法はないか?」など、より重要な観点に集中してソースコードレビューをすることができるようになる。

Hello World

インストール

sourceforge から、最新の zip を落としてくる。
zip を解凍したら、中に jar ファイルが入っている。

解凍後の状態
checkstyle-7.0/
|- config/
|- antlr-2.7.7.jar
|- antlr4-runtime-4.5.3.jar
|- checkstyle-7.0-all.jar
|- checkstyle-7.0.jar
|- commons-beanutils-1.9.2.jar
|- commons-cli-1.3.1.jar
|- commons-collections-3.2.2.jar
|- commons-logging-1.1.1.jar
|- guava-19.0.jar
|- LICENSE
|- LICENSE.apache20
`- RIGHTS.antlr

対象コード

以下のコードを Checkstyle に食わしてみる。

Main.java
package sample.checkstyle;

public class Main {

    public static void main(String[] args) {
        System.out.println("Hello Checkstyle!!");
    }
}

実行

$ java -jar checkstyle-7.0-all.jar -c /sun_checks.xml -o out.txt src\main\java
Checkstyle ends with 5 errors.

結果

out.txt
Starting audit...
[ERROR] ...\Main.java:0: package-info.javaファイルがありません。 [JavadocPackage]
[ERROR] ...\Main.java:3: Javadoc コメントがありません。 [JavadocType]
[ERROR] ...\Main.java:3:1: ユーティリティクラスは、パブリックまたはデフォルトコンストラクタを持つべきではありません。 [HideUtilityClassConstructor]
[ERROR] ...\Main.java:5:5: Javadoc コメントがありません。 [JavadocMethod]
[ERROR] ...\Main.java:5:29: パラメータargs最終的にする必要があります。 [FinalParameters]
Audit done.

説明

  • checkstyle-7.0-all.jar が全部入りの jar になっており、コマンドラインから起動することができる。
  • デフォルトの設定ファイルはこの jar に同梱されているので、特に設定ファイルを用意しなくても使用できる。
    • sum_checks.xmlgoogle_checks.xml のいずれかを指定可能。
    • 設定ファイルは -c オプションで指定する。
  • 結果は、デフォルトだと標準出力に出力される。
    • -o で、任意のファイルに出力できる。
  • 処理対象はフォルダ指定もファイル指定も可能。

任意の設定ファイルを指定する

フォルダ構成
|-checkstyle-7.0-all.jar
`-my_config.xml

my_config.xml の中身は google_checks.xml と同じ。
前述と同じコードを食わせる。

$ java -jar checkstyle-7.0-all.jar -c my_config.xml -o out.txt src\main\java
out.txt
Starting audit...
[WARN] ...\Main.java:5: インデント階層 4 の method def modifier が正しいインデント 2 にありません [Indentation]
[WARN] ...\Main.java:6: インデント階層 8 の子 method def が正しいインデント 4 にありません [Indentation]
[WARN] ...\Main.java:7: インデント階層 4 の method def rcurly が正しいインデント 2 にありません [Indentation]
Audit done.
  • -c オプションで任意の設定ファイルをパスで指定できる。

設定ファイル

構造

<?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">
  <module name="FileLength">
     <property name="max" value="5"/>
  </module>
</module>
  • Checkstyle の設定は xml で記述する。
  • Checkstyle の検証機能はモジュールと呼ばれる単位で提供されている。
  • 設定ファイルは、このモジュールを指定することで記述する。
  • 設定ファイルのルートは、 nameChecker を指定した <module> タグにする。
  • この下に、適用したいモジュールを <module> タグで記述していく。
  • 上記例では、 FileLength というモジュールの使用を宣言している。
    • FileLength は、ファイルの行数をチェックするモジュール。
  • <module> にパラメータを渡す必要がある場合は、 <property> タグを使用する。
    • モジュールによってパラメータの有無は異なるので、ドキュメントを参照して必要かどうかを判断する。

親モジュール

  • モジュールは親子構造になっており、決まった親モジュールの子どもとしてモジュールを指定しないといけない。
  • 例えば FileLength モジュールの親は Checker モジュールなので、 Checker の子供としてだけ記述できる。
  • そのモジュールの親が何なのかは、ドキュメントに記載されている。
    • 例えば FileLength の親は ここParent に書いてある 。

TreeWakler

<?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">
    <module name="TreeWalker">
        <module name="AnnotationLocation" />
        <module name="LocalFinalVariableName" />
        :
        :
    </module>
</module>
  • ほとんどのモジュールの親は、この TreeWalker になっている。
  • TreeWalker 自体は何かをチェックするものではなく、 Java ソースの構文解析が役割になっている。
  • TreeWalker の子モジュールは、 TreeWalker が解析して作った構文木を走査していく中で、個々のチェック処理を行うようになっている。
  • なので、 Java の構文解析が必要になるモジュール(すなわち、ほとんどのモジュール)は、全てこの TreeWalker の子モジュールとなっている。
  • FileLength はファイルの行数をチェックするだけのモジュールなので、 Java ソースを解釈する必要はなく Checker モジュールの子供になっている。

プロパティ

<module name="AnnotationLocation">
  <property name="allowSamelineMultipleAnnotations" value="true" />
</module>
  • モジュールのなかには、プロパティを指定できるものがある。
  • プロパティを指定することで、デフォルトの動作を変更することができる。
  • 各モジュールがどのようなプロパティを持つのかは、それぞれのドキュメントに書かれている。

Checker のプロパティ

  • localeCountry
    • 出力するメッセージのロケールの国コードを指定する。
  • localeLanguage
    • 出力するメッセージのロケールの言語コードを指定する。
  • charset
    • 読み込むファイルの文字コードを指定する。
  • fileExtensions
    • 読み込むファイルを拡張子で絞る。
    • カンマ区切りで複数指定可能(例:java, xml, properties)。

TreeWalker のプロパティ

  • tabWidth
    • タブ文字(\t)があった場合に、半角スペース何個分でカウントするかを定義する。
    • デフォルトは 8
    • LineLength のような行の桁数に関係するモジュールの動作に影響する。
  • fileExtensions
    • 読み込むファイルを拡張子で絞る。
    • デフォルトは java

違反時の挙動を指定する

  • デフォルトでは、ルールに違反したコードがあると「エラー」としてメッセージが出力される。
  • severity プロパティを指定することで、違反時の挙動を「警告」などに変更することができる。
    • 指定できるのは ignore, info, warning, error のいずれか。
実装
package sample.checkstyle;

public class Main {
    static final int aaa = 1;
}

severityを指定しない場合

設定ファイル
<?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">
    <module name="TreeWalker">
        <module name="ConstantName" />
    </module>
</module>
実行結果
Starting audit...
[ERROR] ...\Main.java:4:22: 名前 'aaa' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。 [ConstantName]
Audit done.

severityにwarningを指定した場合

設定ファイル
<?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">
    <module name="TreeWalker">
        <module name="ConstantName">
            <property name="severity" value="warning" />
        </module>
    </module>
</module>
実行結果
Starting audit...
[WARN] ...\Main.java:4:22: 名前 'aaa' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。 [ConstantName]
Audit done.
  • メッセージの先頭が WARN になっている。

メッセージを変更する

  • 検証エラー時のメッセージは、任意のものに変更することができる。
  • デフォルトのメッセージだと意味が分からない、といったときは分かりやすいメッセージに変更することができる。
実装
package sample.checkstyle;

public class Main {
    int Aaa = 1;
}

デフォルト

設定ファイル
<?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">
    <module name="TreeWalker">
        <module name="MemberName" />
    </module>
</module>
実行結果
Starting audit...
[ERROR] ...\Main.java:4:9: 名前 'Aaa' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
Audit done.

メッセージを変更した場合

設定ファイル
<?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">
    <module name="TreeWalker">
        <module name="MemberName">
            <message key="name.invalidPattern"
                     value="許可されたパターン=''{1}'' 実際の名前=''{0}''" />
        </module>
    </module>
</module>
実行結果
Starting audit...
[ERROR] ...\Main.java:4:9: 許可されたパターン='^[a-z][a-zA-Z0-9]*$' 実際の名前='Aaa' [MemberName]
Audit done.

  • <message> タグを追加して、 key に変更したいメッセージを識別するためのキー値、 value に変更後のメッセージを記述する。
  • key は、各モジュールのドキュメントを見ればどういうキーが存在するか記述されている。
  • {0} のようにしてプレースホルダーを定義することで、実際の値やプロパティの値を取得することができる。
  • 何番目に何の値が来るのかについて、明示的なルールのようなものはドキュメントには記載されていなかったぽい。
  • 各モジュールのデフォルトのメッセージを確認して、何の値が渡ってくるか推測する必要がある気がする。

特例でチェック対象外にする

何らかの理由で特定のコードをチェック対象外にしたい場合の方法。

SuppressionCommentFilter

設定ファイル
<?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">

    <module name="SuppressionCommentFilter" />

    <module name="TreeWalker">
        <module name="FileContentsHolder" />
        <module name="MemberName" />
    </module>
</module>
実装
package sample.checkstyle;

public class Main {
    // CHECKSTYLE:OFF
    int Aaa;
    // CHECKSTYLE:ON
    int Bbb;
}
実行結果
Starting audit...
[ERROR] ...\Main.java:7:9: 名前 'Bbb' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
Audit done.
  • SuppressionCommentFilter というフィルターを Checker の子モジュールとして設定する。
  • また、 TreeWalker の子モジュールとして FileContentHolder を追加する。
  • これで、コメントを使ってチェックのオン・オフを切り替えることができるようになる。
  • デフォルトでは、 CHECKSTYLE:OFF と記述した場所から CHECKSTYLE:ON と記述した場所までがチェック対象外になる。

特定のモジュールだけ対象外になるようにする

設定ファイル
<?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">

    <module name="SuppressionCommentFilter">
        <property name="checkFormat" value="MemberName" />
    </module>

    <module name="TreeWalker">
        <module name="FileContentsHolder" />
        <module name="MemberName" />
        <module name="ConstantName" />
    </module>
</module>
実装
package sample.checkstyle;

public class Main {
    // CHECKSTYLE:OFF
    int Aaa;
    static final int aaa = 1;
    // CHECKSTYLE:ON
    int Bbb;
    static final int bbb = 1;
}
実行結果
Starting audit...
[ERROR] ...\Main.java:6:22: 名前 'aaa' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。 [ConstantName]
[ERROR] ...\Main.java:8:9: 名前 'Bbb' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
[ERROR] ...\Main.java:9:22: 名前 'bbb' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。 [ConstantName]
Audit done.
  • checkFormat プロパティで、除外対象にするモジュールを指定できる。
  • 上記設定の場合 MemberName を指定しているので、 OFF から ON までの間 MenberName についてのチェックは実施されなくなっている。
  • ConstantName は除外対象になっていないので、コメントの間でも常に有効になっている。

どのモジュールを対象外にするかコメント上で指定する

設定ファイル
<?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">

    <module name="SuppressionCommentFilter">
        <property name="offCommentFormat" value="CHECKSTYLE\:OFF (\w+)" />
        <property name="onCommentFormat" value="CHECKSTYLE\:ON (\w+)" />
        <property name="checkFormat" value="$1" />
    </module>

    <module name="TreeWalker">
        <module name="FileContentsHolder" />
        <module name="MemberName" />
        <module name="ConstantName" />
    </module>
</module>
実装
package sample.checkstyle;

public class Main {
    // CHECKSTYLE:OFF ConstantName
    int Aaa;
    static final int aaa = 1;
    // CHECKSTYLE:ON ConstantName
    int Bbb;
    static final int bbb = 1;
}
実行結果
Starting audit...
[ERROR] ...\Main.java:5:9: 名前 'Aaa' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
[ERROR] ...\Main.java:8:9: 名前 'Bbb' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
[ERROR] ...\Main.java:9:22: 名前 'bbb' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。 [ConstantName]
Audit done.
  • offCommentFormat および onCommentFormat で、チェック対象外の開始・終了コメントを定義できる。
  • このとき、正規表現のグループを定義する((\w+) の部分)。
  • すると、このグループにマッチした文字列を他のプロパティで $1 という形で参照できるようになる。
  • これ($1)を checkFormat に指定する。
  • そして、除外したいモジュール名をコメントの末尾に記載する。
  • こうすることで、コメントで指定したモジュールがチェック対象外になる。

SuppressWithNearbyCommentFilter

設定ファイル
<?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">

    <module name="SuppressWithNearbyCommentFilter" />

    <module name="TreeWalker">
        <module name="FileContentsHolder" />
        <module name="MemberName" />
    </module>
</module>
実装
package sample.checkstyle;

public class Main {
    int Aaa; // SUPPRESS CHECKSTYLE MemberName
    int Bbb;
}
実行結果
Starting audit...
[ERROR] ...\Main.java:5:9: 名前 'Bbb' はパターン '^[a-z][a-zA-Z0-9]*$' に一致しなければなりません。 [MemberName]
Audit done.
  • SuppressWithNearbyCommentFilter は1行のコメントでチェック対象を指定したい場合に使用する。
  • SUPPRESS CHECKSTYLE 【除外したいモジュール名】 と記述することで、その行では指定されたモジュールがチェックされなくなる。
    • デフォルトはコメントのフォーマット(commentFormat)が SUPPRESS CHECKSTYLE (\w+) と定義されている。
    • このためか、必ず除外したいモジュール名を指定しなければいけなくなっている。
    • モジュール名をいちいち指定しなくても、すべてのモジュールをチェックしないようにしたい場合は、 commentFormatSUPPRESS CHECKSTYLE など (\w+) が無い値を設定すればいい。
設定ファイル
    <module name="SuppressWithNearbyCommentFilter">
        <property name="commentFormat" value="SUPPRESS CHECKSTYLE" />
    </module>

Gradle で使用する

フォルダ構成
|-build.gradle
|-src/main/java/
|  `-sample/checkstyle/
|    `-Main.java
`-config/checkstyle/
   `-checkstyle.xml
checkstyle.xml
<?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">
    <module name="TreeWalker">
        <module name="ConstantName" />
    </module>
</module>
build.gradle
apply plugin: 'java'
apply plugin: 'checkstyle'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

repositories {
    mavenCentral()
}
Main.java
package sample.checkstyle;

public class Main {
    static final int aaa = 1;
}
実行
$ gradle check -q
[ant:checkstyle] ...\Main.java:4:22: 名前 'aaa' はパターン '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$' に一致しなければなりません。                                                                                           

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':checkstyleMain'.
> Checkstyle rule violations were found. See the report at: file:///.../checkstyle/build/reports/checkstyle/main.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
  • 標準で checkstyle というプラグインが用意されているので、それを有効にすると Gradle で Checkstyle を使えるようになる。
    • ただし、 repositoriesmavenCentral() を入れないと実行時にエラーになった。
  • Checkstyle の設定ファイルは、デフォルトでは config/checkstyle/checkstyle.xml に配置する。
    • 別のファイルを指定したい場合は、 checkstyle.configFile プロパティを変更する。
任意の設定ファイルを指定する
apply plugin: 'checkstyle'

...

checkstyle.configFile = file('my_config.xml')
  • checkstyle プラグインを有効にすると、 check というタスクが使えるようになる。
    • このタスクは、 build などのタスクを実行したら勝手に実行してくれるようになっている。
  • Checkstyle による検証でエラーがあれば、その時点でビルドは失敗する。
  • 検証結果は build/reports/checkstyle の下に html と xml の2つの形式で出力される。

IDE で使用する

Eclipse

  • マーケットプレイスからインストールできる。
    • 「checkstyle」で検索したら「Checkstyle Plugin-in」というのが出てくるので、それをインストールする。
  • プロジェクトのプロパティを開き、 [Checkstyle] を選択する。
  • [Local Check Configurations] タブを開き、 [New] で設定ファイルを新規追加する。
    • [Type] で [Project Relative Configuration] を選べば、当該プロジェクトからの相対パスで設定ファイルを管理できる。
    • [Name] に任意の名前を入力。
    • [Location] に設定ファイルのパスを入力([Browse] で選択すれば楽)。
  • 設定ファイルの登録ができたら、次に [Main] タブを開く。
  • デフォルトだと、内部に組み込まれている Google Checks を使用する simple configuration というのが有効になっている。
  • 右上の [Use simple configuration] のチェックを外して、これを無効にする。
  • 代わりに使用する設定ファイルを指定する [Advanced] という設定領域が表示されるので、 [Add] で先ほど追加した設定ファイルを選択する。
  • [Enabled] にチェックが入っていることを確認して設定を閉じる。

checkstyle.jpg

設定が完了すると、チェックエラーとなるコードがあるとエディタ上でエラーが表示されるようになる。

checkstyle.jpg

IntelliJ

  • CheckStyle-IDEA というプラグインがあるので、インストールする。
  • [File] -> [Settings]
  • [Other Settings] -> [Checkstyle]
  • [Configuration File] に使用したい Checkstyle の設定ファイルを追加する。
    • Store relative to project location のチェックを入れればいいのかどうかはよくわからない。
    • どちらにしても、設定ファイル(.idea/checkstyle-idea.xml)上は相対パスで保存しているように見える。。。
  • 設定ファイルを追加したら、 [Active] のチェックをオンにする。
  • すると、チェックエラーになった箇所がエディタ上でエラーとして表示されるようになる。

checkstyle.jpg

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.