Tech Racho エンジニアの「?」を「!」に。
  • 開発

[Java] Checkstyleと正規表現でコーディングスタイルをチェックする

こんにちは、hachi8833です。今回はBPSアプリチームkawawaさんと共同でJavaコードのスタイルチェックに使う正規表現を作ってみました。その過程でちょっといい正規表現テクニックも会得できましたので、記事の最後でご紹介します。

背景

アプリチームではAndroid StudioをIDEとして採用しています。また、アプリチームのJavaコーディングスタイルに違反している部分のチェックを自動化し、随時チェック内容を調整しています。

アプリチームのJavaコーディングスタイルから以下を抜粋しました。太字部分がポイントです。コード例のショットには赤で補助線を追加しました。

メソッド宣言、呼び出し時の引数リストの折り返しは8スペースインデント、または、並列インデントする。

161021_1404_yuApjm

2項演算子の折り返しは8スペースインデント、または、並列インデントする。ただし、if文の条件式第1階層では並列インデントを使用してはならない。(ステートメントの開始列から丁度4スペースとなり、紛らわしいため。)

161021_1404_OiEXdw

惜しいことに、if文の条件の折り返し行インデントについてはAndroid Studioのオートフォーマットで細かく設定できません。たとえば以下のif文をAndroid Studioでオートフォーマットすると、条件折り返し行とブロック内ステートメント行のインデントが同じ深さになってしまいます(注: Android Studioの Code Style設定によります)。

if (first
    * second                 // X 並列インデントだと下と並んで見づらい
    / third
    < 10) {
    // some processing here  // ブロック内ステートメント
    // some processing here
    // some processing here
}

if文の条件の折り返し行に適用したいのは、以下のスタイルです。

if (first
        * second             // ○ 並列インデントではなく8スペースインデントにする
        / third
        < 10) { 
    // some processing here  // ブロック内ステートメント
    // some processing here
    // some processing here
}

Android Studioのオートフォーマットを実行するかどうかは開発者に任されており、こうしたスタイル違反も含めて最終的にCheckstyleで詳細に検出し、手動で修正する運用になっています。なので、Android StudioでできないスタイルについてはCheckstyleでカバーすることになります。

CheckstyleはソースをGitLabにpushしたときにGitLab CI上で自動実行され、結果がSlackチャンネルに流れますが、ローカルのAndroid Studioでも実行できます。

今回必要なのは、上のスタイル違反をCheckstyleで検出するための正規表現です。なお、Checkstyleの設定項目については公式サイトをご覧ください。

使ったツール

正規表現の作成にはrubular.comを使ってみました。表示がとても見やすく、チェック結果のパーマネントリンクを作れるのでやりとりしながら修正するのに便利ですが、Rubyの正規表現を使っているので、正規表現の方言を使わないよう注意する必要があります。

161020_1116_PAXp7R

正規表現の修正過程

最初は以下の正規表現にしてみました(rubular)。

^(([ ]{4})\2*)if \([^\n]+(\n(\1([ ]{4})[^ ][^\{\n]*))+\)[ ]\{

161020_1123_apDebp

当初はCheckstyleの正規表現ライブラリとrubyの正規表現で微妙に挙動が違っているのかと思いましたが、実は直後のif文がネストしている場合を誤検出していたのでした(rubular)。量指定子(quantifier)の*+に最小一致の?がなかったので欲張りマッチになってしまいました。

161020_1839_jIbuiL

同じ誤検出が他のパターンでも見つかりました。

161020_1134_ezGEaF

インデントのスペースを4と仮定していたので、スペース1つの違いについても検出できていません。飛び込み参加の悲しさで、他にも仕様の理解に不備があったことが後々わかりました。

その後kawawaさんが修正を重ね、最終的に以下のようにもっと簡潔な正規表現になりました(rubular)。

^( *)if \([^{\n]*\n(\1 {0,7}|\1 {9,})[^ ]

kawawaさんによる修正のポイント

  • if条件末尾の「) {」をチェックする\)[ ]\{はなくてよいので外した
  • ifの前のインデントスペース数は別途チェック済みなので、インデント数が4の倍数であるかどうかを(([ ]{4})*?)でチェックしなくても、\1でキャプチャできるように( *)と簡略化できる
  • if文の条件式第1階層のインデントはスペース8つのみが合格、それ以外が不合格なので、(\1 {0,7}|\1 {9,})と書けばスペース数が8以外の場合を簡潔にチェックできる

注意

上の正規表現は条件内に文字列リテラルが含まれないことが前提となっています。たとえば"\/:*?"<>|.{}[]"のような文字が文字リテラルとして条件に含まれていると、正規表現内の[^{\n]が文字列リテラルの{のところで誤動作する可能性があります。

チームでは条件内に文字列リテラルを書くことはほぼないとのことです。

まとめ

この「([文字]{0,7}|[文字]{9,})」のような書き方は他でも応用が効きますね。文字が特定の個数でない場合を簡潔にチェックできます。

お疲れさまでした!

追記

記事で触れたGitLab CIからSlackチャンネルへの通知周りを実装してくれたのは、先日TechRachoにインタビューが掲載されたyoshi.kさんです。そのあたりを今後記事にしたいと思いますのでご期待ください。

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。