‼️

detektで `!!` が検出されないときの対処法

2023/10/24に公開

はじめに

普段、開発していると「きれいにコーディングをしたい!」「汚いコードは消毒だ!」というのはエンジニアであれば誰しもが思ったことのあることだと思います。

ログラスではコードをきれいにする取り組みとして、ボーイスカウトルールでのリファクタリングや技術的投資というスプリントに枠を設けてお掃除をするなど、初期の頃からかなり仕組み化されております。

更に一律で汚いコードが紛れ込まないように、CIによる静的解析も行っております。
この記事では、ログラスで利用している静的解析ツールである 「detekt」!! が検出されない問題に最近ぶち当たったので、その対処法を書いておこうと思います。

kotlinを使う人達のタメになれば幸いです😌

detektとは

detektはkotlin用の静的解析ツールです。
https://detekt.dev/

ベースラインの設定やルールの拡張など既存プロジェクトにも導入しやすいものになっています。

ログラスでは初期の頃からktlintを使ったフォーマッティングは行っておりましたが、detektはktlintのwrapperになるプラグインも用意されており、detektに移行しようとしている最中になります。

ログラスのCI (detekt + reviewdog)

ログラスではPRが出されたタイミングでGihtub Actions上でdetektが実行され、結果をreviewdogでPRにコメントする形で運用しております。

本来であれば、detektで引っかかった部分は落とすべきなのですが、導入過程ということもありコメントにとどめています。
(ktlintのCIも動いており、そちらは引っかかった場合にCIが落ちるようになっています。)

!! が検出されない

ある時、 !! があることによってヌルポが発生してしまうという問題が発生しました。
その対応策として、detektで !! を検出できるようにしようということが決まりました。

しかし、detektの設定を見てみると...、 既に !! を検出するルールが設定されているではありませんか!!

ルールには設定されているのに、検出されないという事象がここで発覚しました。

原因

調べて見ると、detektのRepositoryに同じ問題のIssueが上がっていることが判明しました。
https://github.com/detekt/detekt/issues/2036

「v1.1.0から !! が検出されなくなったんだけど」という内容のIssueです。
それに対してコラボレーターが「ルールが変わって型解決が必要になった。 !! の検出には型情報が必要だからclasspathを渡す必要がある。」と答えています。

ドキュメントの方を見てみると確かにそのように書いてありました (翻訳してます)。

https://detekt.dev/docs/gettingstarted/type-resolution#is-my-rule-using-type-resolution

ということで、 型情報を渡さないと!!が検出されなくなった というのが原因でした。

対処法

JVMでkotlinを動かしている場合の一番簡単な対処法は、Detekt Gradleプラグインを利用する方法です (翻訳してます)。

https://detekt.dev/docs/gettingstarted/type-resolution#enabling-on-a-jvm-project

ドキュメントにもあるように、 ./gradlew detektMain detektTest を実行するとclasspathをいい感じに渡した上でdetektを実行してくれます。

また、ログラスでは複数モジュールをGradleのsubprojectsを使って管理しています (古い書き方なのでアプデしたい)。
そのため設定が少し特殊にはなってしまいましたが、subprojectsを使う場合はこのような形で動作するようになりました。

build.gradle.kts
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.report.ReportMergeTask

val reportMerge by tasks.registering(ReportMergeTask::class) {
    output.set(rootProject.layout.buildDirectory.file("reports/detekt/detekt.xml"))
}

repositories {
    mavenCentral()
}

plugins {
    alias(libs.plugins.io.gitlab.arturbosch.detekt)
}


subprojects {
    apply(plugin = "kotlin")
    apply(plugin = "io.gitlab.arturbosch.detekt")
    
    detekt {
        config.setFrom("$rootDir/config/detekt/config.yml")
        baseline = file("$rootDir/config/detekt/baseline.xml")
        parallel = true
        ignoreFailures = true // 移行中なのでfailしても落とさない
    }
    
    tasks {
        withType<Detekt>().configureEach {
            finalizedBy(reportMerge)
        }
    }

    reportMerge {
        input.from(tasks.withType<Detekt>().map { it.xmlReportFile })
    }

}

GitHub Actions側はこんな感じです。
detektMainとdetektTestで同じファイル名で出力されるようにしてしまったので、2回に分けて実行するようにしています。

name: detekt

on:
  pull_request:

jobs:
  run_detekt_command:
    runs-on: ubuntu-latest
    env:
      REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - uses: reviewdog/action-setup@v1
      - name: run detekt main
        run: |
          ./gradlew detektMain
          reviewdog -f=checkstyle -name=detektMain -reporter=github-pr-review < ./build/reports/detekt/detekt.xml
      - name: run detekt test
        run: |
          ./gradlew detektTest
          reviewdog -f=checkstyle -name=detektTest -reporter=github-pr-review < ./build/reports/detekt/detekt.xml

こんな感じできちんとコメントも出てきました。

おわりに

ログラスでは開発者体験を高める様々な取り組みを実施しており、今回はその一部である静的解析ツールのdetektについて取り上げさせて頂きました。

今回の対応で !! 以外にも型情報が必要なルールが検出されるようになりました🎉
detektはCode Smellの分析などができる静的解析ツールであり、きれいなコードを作る礎となっていく予定です。

ログラスではこの他にもコードの保守性や開発者体験を上げるための様々な取り組みをしております。
少しでもご興味を持たれた方はTwitterのDMなどで気軽にご連絡頂けますと幸いです!

https://twitter.com/urmot2

WE ARE HIRING

エンジニア採用もオープンしております!
皆様のご応募お待ちしております!
https://job.loglass.jp/

株式会社ログラス テックブログ

Discussion