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 に食わしてみる。
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.
結果
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.xml
とgoogle_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
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 の検証機能はモジュールと呼ばれる単位で提供されている。
- 設定ファイルは、このモジュールを指定することで記述する。
- 設定ファイルのルートは、
name
にChecker
を指定した<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+)
と定義されている。 - このためか、必ず除外したいモジュール名を指定しなければいけなくなっている。
- モジュール名をいちいち指定しなくても、すべてのモジュールをチェックしないようにしたい場合は、
commentFormat
にSUPPRESS 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
<?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>
apply plugin: 'java'
apply plugin: 'checkstyle'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
repositories {
mavenCentral()
}
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 を使えるようになる。- ただし、
repositories
にmavenCentral()
を入れないと実行時にエラーになった。
- ただし、
- 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] にチェックが入っていることを確認して設定を閉じる。
設定が完了すると、チェックエラーとなるコードがあるとエディタ上でエラーが表示されるようになる。
IntelliJ
- CheckStyle-IDEA というプラグインがあるので、インストールする。
- [File] -> [Settings]
- [Other Settings] -> [Checkstyle]
- [Configuration File] に使用したい Checkstyle の設定ファイルを追加する。
-
Store relative to project location
のチェックを入れればいいのかどうかはよくわからない。 - どちらにしても、設定ファイル(
.idea/checkstyle-idea.xml
)上は相対パスで保存しているように見える。。。
-
- 設定ファイルを追加したら、 [Active] のチェックをオンにする。
- すると、チェックエラーになった箇所がエディタ上でエラーとして表示されるようになる。