[Android] ktlint の導入と感想

これは [ゆめみ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/yumemi) の５日目の投稿です。

いまさら感があるのですが、先日にとある Android プロジェクトへ [ktlint](https://github.com/shyiko/ktlint) を導入する機会があったので、その手順をここに残したいと思います。これから導入しようと考えられている方の参考になれば。

# 前提

- Android Studio: `3.2.1`
- ktlint:`0.29.0`

# ktlint とは

[ktlint の README](https://github.com/shyiko/ktlint/blob/master/README.md) にも書いてありますが、[kotlinlang.org](https://kotlinlang.org/docs/reference/coding-conventions.html) と [Android Kotlin Style Guide](https://developer.android.com/kotlin/style-guide) のスタイルガイドを元にコードスタイルのチェックをしてくれる Kotlin 用の Linter です。Gradle のタスクとしてコマンドラインで実行できるので、CI に組み込めたりします。
（追記：CI への組み込みについては [GitHub のプルリクエストを Bitrise x Danger x ktlint で自動レビューする](https://qiita.com/hkusu/items/32d29d90bf6e7a698c21) に書きました。）

# 導入

Gradle への導入方法は [ktlint の README](https://github.com/shyiko/ktlint/blob/master/README.md) のとおりですが、例えば Android Studio で新規にプロジェクトを作成した直後の `app/build.gradle` に導入（プラグインを使わない方法での導入）すると次のようになります。

```diff:app/build.gradle
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "io.github.hkusu.myapplication"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

+ configurations {
+     ktlint
+ }

+ dependencies {
+     ktlint "com.github.shyiko:ktlint:0.29.0"
+ }

+ task ktlint(type: JavaExec, group: "verification") {
+     description = "Check Kotlin code style."
+     classpath = configurations.ktlint
+     main = "com.github.shyiko.ktlint.Main"
+     args "src/**/*.kt"
+ }
+ check.dependsOn ktlint

+ task ktlintFormat(type: JavaExec, group: "formatting") {
+     description = "Fix Kotlin code style deviations."
+     classpath = configurations.ktlint
+     main = "com.github.shyiko.ktlint.Main"
+     args "-F", "src/**/*.kt"
+ }
```

ktlint のオプションを変更する場合は上記の `args` のところを変更します。例えば出力をカラフルにする場合は、`args "src/**/*.kt"` の箇所を `args "--color", "src/**/*.kt"` とします。参考までに、私の最近のプロジェクトでは、次のようなタスクにしています。

```gradle
task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.github.shyiko.ktlint.Main"
    args "--android", "--color", "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/reports/ktlint-results.xml", "src/**/*.kt"
    ignoreExitValue true
}
```

`--android` `--color` オプションを追加してるのと、CI 用に checkstyle 形式での出力を追加＆チェックにひっかかっても異常終了させない（`ignoreExitValue true`）ようにしています。もし手元での lint の実行と CI 上のものでオプションを分けたい場合は、タスクを分ければいいです。

またプロジェクトディレクトリの直下に `.editorconfig` ファイルを作成し、追加のルールを指定しておくと良いでしょう。これはファイル末尾に改行が入ってるかのチェックと、コード一行の文字数のチェックです。（ちなみに弊社では最大文字数が Android Studio のデフォルト値？の `100` だとなかなか辛いものがあるので `128` にしています。）

```:.editorconfig
[*.{kt,kts}]
insert_final_newline=true
max_line_length=128
```

# Lint の実行

プロジェクトのルートディレクトリで `$ ./gradlew ktlint` を実行します。結果が出力されます。

<img width="600" alt="スクリーンショット 2018-11-30 21.41.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/35733/b1f40399-c8d0-006d-9bb7-cd5617db1664.png">

ちなみに `$ ./gradlew ktlintFormat` でコード修正までやってくれるようですが、個人的にはまだ使ってません。

# Android Studio のコードスタイル

ktlint でチェックができるとはいえ、ある程度は Android Studio のコードスタイルで自動整形されり、フォーマット時に自動修正されるようにしておきたいものです。

[ktlint の README](https://github.com/shyiko/ktlint/blob/master/README.md) を見ると、次のようにおすすめの設定方法が書いてあります。

<img width="700" alt="スクリーンショット_2018-12-03_15_27_13.png" src="https://qiita-image-store.s3.amazonaws.com/0/35733/e7ebba8a-82dc-4349-fbeb-8a584a3c9bee.png">
（https://github.com/shyiko/ktlint から引用）

「Option #1」の方法は、コマンドライン版の ktlint（Gradle でなく curl で導入した ktlint）で実行でき、Android プロジェクトで実行すると、おすすめ設定が反映された下記の5ファイルが生成されます。

- .idea/codeStyles/codeStyleConfig.xml
- .idea/codeStyles/Project.xml
- .idea/inspectionProfiles/profiles_settings.xml
- .idea/inspectionProfiles/ktlint.xml
- .idea/workspace.xml

「Option #2」の方法は、「#1」の設定を手動でやる方法です。

個人的には「#1」の方法で自動生成されるのはなんとなく気持ち悪いし、それだけの為にコマンドライン版の ktlint を導入するのも嫌なので、「#2」の方法で十分かなと思います。

#### Continuation indent について

これは改行時のインデントのスペース数なのですが、上記のおすすめ設定では通常のインデントと同じ `4` となっています（Android Stuio のデフォルトは `8`）。個人的には改行時のインデントは `8` として通常のインデントと区別した方がよいと思うので `8` のままにしています。

もし `8` スペースで困るケースがあるなら、そもそも「Continuation indent」として扱うか否かを見直した方が良いというスタンスです。これは Android Studio のコードスタイルの設定で、下図のように各ケースについて制御できます。

<img width="400" alt="スクリーンショット_2018-12-03_16_06_15.png" src="https://qiita-image-store.s3.amazonaws.com/0/35733/c8cf72bc-6d24-9036-cff1-b202e06b0b2b.png">

# 感想

ktlint の場合、Lint のルールを個別に ON/OFF できる機能はないんですよね（もし勘違いならすみません）。いままで ktlint を導入していなかったプロジェクトへ導入して Lint をかけると大量にエラーになるし、Lint のルールはプロジェクト毎に決めればいいんじゃないか、と初めは思ったりもしました。

が、今は、個別のルールを ON/OFF できない事が逆に良い、と思っています。プロジェクト毎に適用するルールの検討で悩まなくていいし、プロジェクト間でルールに差異が生まれないからです。ktlint でこう決まってるから有無をいわず粛々と対応するだけ、という状況は、ある意味 楽でいいな〜と感じています。

あと、仮に ktlint を導入しない場合でも、Kotlin でコードを書くなら 「Option #2」の方法でも説明されている「Kotlin style guide」の導入はしておくと良いかなと思います。せっかく Android Studio が標準で用意しているものなので。

<img width="800" alt="スクリーンショット 2018-12-03 16.46.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/35733/c8a45161-783f-b514-cbc1-a69144d03913.png">