AndroidのPRレビューbotを導入した話

## はじめに
この記事は、[ジモティー Advent Calendar 2018](https://qiita.com/advent-calendar/2018/jmty) の20日目の記事です。
複数人でアプリ開発している場合、ほとんどのプロダクトでPRレビューを行うと思います。
しかし、人力で「インデントが1ずつずれている」「変数を大文字で定義している」といった書き方の問題を指摘していくと、
動きにバグがある、設計がずれているといったより本質的な指摘まで手が回らないことが多々あります。
そこで、書き方の間違いは自動で指摘してもらえるようにするのがPRレビューBotです。

今回は、そのPRレビューBotでなにができるのかとジモティーでの導入方法を書いていこうと思います。

## 書いてあること
 - PRレビューBotができること
 - Java＋Androidのコード解析ツールとの連携
 - Checkstyle,lint,SpotBugsの導入方法
 - 導入してよかったこと

## PRレビューBotができること
PRレビューBotとして、Rubyライブラリの[Danger](https://danger.systems/)を導入しています
このライブラリはCIサーバ内で動かすことでブランチに対応するPRページにコメントすることができます。

例:WIPをタイトルにつけると、「作業中PRです」と警告を出してくれる
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44174/bc9deb31-6548-7155-3816-62b154bb0fdc.png)

Dangerの導入方法はCIサーバによって多岐にわたるため、[公式のページ](https://danger.systems/guides/getting_started.html)を参照してみてください

## Java＋Androidのコード解析ツールとの連携
JavaやAndroidには数々のコード解析ツールが有り、それらはDangerと連携して、解析した結果をコメントできます
 - [Checkstyle](https://github.com/checkstyle/checkstyle)
 - [lint](https://developer.android.com/studio/write/lint?hl=ja)
 - [FindBugs](http://findbugs.sourceforge.net/)(現在開発停止中、代替ツールとしてSpotBugsがある)
 - 等

## Checkstyleの導入
Checkstyleはコードの書き方のルールを決めて行き、それに沿わない書き方をしているものを検出するツールです
Android StudioにあるCodeStyle設定と同じくらい細かく設定ができます。
レビューBotにコメントしてもらうにはCIで実行する必要があるため、今回はGradleにCheckstyleを導入します。

 - http://gradle.monochromeroad.com/docs/userguide/checkstyle_plugin.html

``` #{プロジェクトルート}/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">
    <property name="charset" value="UTF-8"/>
    <property name="localeCountry" value="JP"/>
... //チェックしたいコーディングルールを記載
```

``` #{プロジェクトルート}/build.gradle
allprojects{
    repositories{...}
    task checkstyle(type: Checkstyle) {
        showViolations = true
        configFile file("../config/checkstyle/checkstyle.xml")//コーディングルールを書いたファイルを指定

        source 'src/main/java'
        include '**/*.java'
        exclude '**/gen/**'//自動生成コードはチェックしない
        exclude '**/R.java'//自動生成コードはチェックしない
        exclude '**/BuildConfig.java'//自動生成コードはチェックしない

        classpath = files()
    }
}

```

``` #{プロジェクトルート}/app/build.gradle
apply plugin: 'checkstyle'

android{...}
repositories{...}
dependencies{...}
checkstyle{
    toolVersion = "8.4"
    //この環境では他に設定していないけど適宜設定できる※1
}

```

※1[設定できる値](http://gradle.monochromeroad.com/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.html)

設定した後に``` ./gradlew checkstyle ```を実行するとコードのチェックが行われ、xmlとhtmlに解析結果が出力されます
※``` #{プロジェクトルート}/app/build/reports/checkstyle/ ``` がデフォルトの出力ディレクトリになっています
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44174/afd3f3a6-e246-8b5e-53df-e0b97c2c7a23.png)

Dangerへの設定方法は連携プラグインを作成した@noboru_iさんの[導入記事](https://qiita.com/noboru_i/items/32e4ad1cc2f07184b7e4)がわかりやすいので、そちらを参考するとうまくできると思います

## lintとDangerの連携方法
lintはAndroid開発ではおなじみの、バグのもとになりそうな書き方や未使用リソースを検出してくれる検査ツールです。
何もしなくても``` ./gradlew lint ``` と実行することができます
CIで実行して、Dangerでコメントしてもらうために少し手を加えます


``` #{プロジェクトルート}/app/build.gradle
apply plugin: 'checkstyle'

android{
  ...
  defaultConfig{...}
  productFlavors{...}
  buildTypes{...}
  lintOptions {
    abortOnError false // lintで指摘があってもエラー扱いにしない(全部解決しないとCIが失敗になる)
    xmlReport true //xmlに出力する
    enable 'Interoperability' // Kotlinと併用時の注意点を指摘項目に追加する
  }
}
repositories{...}
dependencies{...}
checkstyle{...}

```

Dangerとの連携には[danger-android_lint](https://github.com/loadsmart/danger-android_lint)というDangerプラグインを使います


``` ..#{プロジェクトルート}/Gemfile
gem 'danger'
gem 'danger-android_lint'
```


``` ..#{プロジェクトルート}/Dangerfile
...
android_lint.gradle_task = "lintProdDebug" # 実行するコマンドの設定、buildTypeやFlavorにあったコマンドを設定
android_lint.filtering = true # 変更されたファイルのみ指摘する．
android_lint.report_file = "app/build/reports/lint-results-prodDebug.xml" # コマンドを実行後に出力されるレポートファイルの場所を指定する
android_lint.lint(inline_mode: true) # PRコメントを行に直接書かれるようになる(Githubのみ)
...
```

この様に設定すると、lintの実行結果がPRコメントに出力されるようになります

# SpotBugs の導入
SpotBugsは、現在更新が終わっているFindBugsの後継ツールで、静的解析を使用してJavaコードのバグを探すツールです
クラスファイルを解析するため、より細かいバグの可能性を検査することができます。

``` #{プロジェクトルート}/build.gradle
buildscript {
    
    repositories {...}
    dependencies {
        ...
        classpath "gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.4"
        ...
    }
...
}
```

``` #{プロジェクトルート}/app/build.gradle
...
apply plugin: 'com.github.spotbugs'
...

spotbugs {
    toolVersion = "3.1.7"
    ignoreFailures = true //指摘があっても失敗扱いにしない(false = 全部解決しないとCIが失敗する)
    effort = 'max' // 最大限分析を詳しく実行する。
    reportLevel = 'low' // 一番細かい指摘もレポートに出力する

}

task spotBugs(type: com.github.spotbugs.SpotBugsTask,
        dependsOn: "assembleDevDebug",//クラスファイルを検査するため、検査前にビルドコマンドを指定する。
        group: "verification") {
    classes = fileTree(
            dir: "${project.buildDir}/intermediates/javac/devDebug",//コンパイル後に出力されるクラスファイルを指定する
            excludes: ['**/R.class',
                       '**/R$*.class',
                       'android/**/*.class', ...]) //検査対象から除外するファイル、自動生成ファイル等を指定する
    source = fileTree('src/main/java')
    include '**//*.java', '**/*.kt'
    exclude '**//gen//**'
    reports {
        xml.enabled = true // Dangerが解析できるxmlで出力する
        html.enabled = false // htmlとxml両方一度に出力できないため、いつもはfalseにする
        html {
            destination "$project.buildDir/reports/findbugs.html" // どこに出力するか指定する
        }
        xml {
            destination "$project.buildDir/reports/findbugs.xml"// どこに出力するか指定する
            withMessages = true
        }
    }
    jvmArgs "-Duser.language=ja" // 実行言語を日本語に指定することで、指摘内容を日本語にする
    classpath = files()
    pluginClasspath = project.configurations.spotbugsPlugins
    spotbugsClasspath = buildscript.configurations.classpath
}
```

設定できたら ```  ./gradlew spotbugs ```を実行すると``` app/build/reports/findbugs.xml ```に解析結果が出力される

### Dangerと連携
SpotBugsの連携プラグインは今の所無いが、前ライブラリのFindBugsとの連携プラグインである[danger-findbugs](https://github.com/kazy1991/danger-findbugs)を使う

``` ..#{プロジェクトルート}/Gemfile
gem 'danger'
...
gem 'danger-findbugs'
```


``` ..#{プロジェクトルート}/Dangerfile
...
findbugs.gradle_task = "spotBugs" #spotBugsを実行するためのコマンドを指定
findbugs.report_file = "app/build/reports/findbugs.xml" # コマンド実行した結果、出力されるxmlファイルの場所を指定
findbugs.report
...
```

上記内容を追加したCI&Dangerを実行すると、コメントに追加される

 - SpotBugsでの指摘内容の例
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44174/25acd916-1dee-caab-8e28-08ced74dcc36.png)


## 導入してよかったこと
解析ツールを導入して運用してみてよかったことがいくつかあります

 - 事前にバグに気づける
    - lintやSpotBugsによって、無限ループはnull参照の危険性がある場合に指摘されます
    - PR動作確認や結合テスト時にクラッシュするため気づけますが、早ければ早いほど良いです
 - レイアウト系の指摘を人がすることが減った
    - インデント、不適切なスペース、空白行、未使用import等はCheckstyleにて指摘されます
    - 厳しすぎる指摘の調整や、Android Studioのフォーマットルールとの調整が少し面倒ですが、安定してくるとレイアウトで人が指摘することがぐっと減らせます
 - 早めにPRを上げて確認する習慣ができた
    - 人だけが見るPRだと、完成直前にPRを上げて指摘されて手直しする事が多く、それによって時間がぎりぎりになることがありました。
    - botは仕様的に中途半端なPRでもしっかり見れるため、早めに出してbotの指摘をもらえるので、そういったことを少なくすることができました。

## まとめ
今回は、レビューBotの紹介と、各連携ツールの導入方法を書きました。
PRにプッシュするごとにいつでも一定の品質を持って指摘をしてくれるPRレビューBotは、アプリの生産性と品質の担保に役に立つと思うので、導入してみるといいと思います。