こんにちは!
XI本部 プロダクトイノベーションセンター の佐藤です。
先日、社内のとある既存の Java プロジェクト(Gradle プロジェクト)に静的解析ツールの SpotBugs を導入する機会がありました。
『SpotBugs の Gradle プラグインを追加して、 CI で実行すれば良いだけ。簡単かな』
と高をくくっていたら意外と工夫が必要な取り組みだったので、その知見を共有したいと思います。
なお、本記事は以下の方々を対象とした内容になります。
- 静的解析の基本について復習しておきたい方
- 冒頭にて、静的解析の概要についてあらためて説明します
- 既存プロジェクトに対する静的解析ツールの導入を検討されている方
- 本記事では SpotBugs を扱いますが、導入時の観点は他のツールにも適用可能です
- reviewdog の利用を検討されている方
- 本記事では GitHub Actions における reviewdog の利用例を掲載しています
静的解析とは
せっかくなので、はじめに静的解析の特徴を簡単におさらいしておきましょう。
概説
静的解析とは、プログラムを実行する事なく(=静的に)隠れた潜在バグやコーディング規約違反を検出する解析手法です。
「プログラムを実際に動かさない」ため、静的解析の解析対象は必然的にソースコード(あるいはバイトコード)それ自体となります。今回利用した SpotBugs も、解析対象は Java バイトコードです。プログラムの実行環境を整備する必要がない分、実行の容易性が高いと言えます。実際、SpotBugs を含め、ツールによっては IDE 上で気軽に実行可能です。
また、多くの静的解析ツールには、標準のルールセットが用意されています。したがって、例えば動的なテストにおけるテストケース設計のような手間をかける事なく、簡単にプロジェクトに導入する事ができる点も、静的解析の特徴の一つと言えるでしょう。
実行すべきタイミング
もう一つ、静的解析ツールの重要な特徴として、開発者へのフィードバックの早さが挙げられます。
これは上述した「実行の容易性」とも関連しますが、コード自体を解析対象とする分、実際に手を動かしている開発者により近い場所から、迅速なフィードバックが可能です。
したがって、静的解析ツールは開発プロセスのより早い段階で実行するのがベストプラクティスと言えます。
IDE、あるいは CI に組み込むのが良いでしょう。
今回は、SpotBugs による検出箇所の修正が確実に完了している事を担保するため、CI にて実行しています。
既存のプロジェクトに対して導入する際の課題
ここからが本題です。
既存プロジェクトに対して SpotBugs を導入する場合、「既存のコードに対する検出」をどう扱うかが課題となります。
SpotBugs が検出してくれる問題は、確かに緊急で修正すべきバグや脆弱性の場合もあります。
一方で、あくまでベストプラクティスから逸脱しているに過ぎない、プロジェクトの状況次第では今すぐ修正すべきとまでは言えない「リファクタリング推奨」程度の問題である事も多いでしょう。
前者の指摘箇所は迅速に改修してしまえば良い(せざるを得ない)のですが、問題は後者です。
今回、私が SpotBugs を導入したプロジェクトは既にある程度開発が進んでおり、かつ規模が大きい事もあって、ざっと 400 件ほど「リファクタリング推奨」の検出箇所がありました。
これらの指摘を一気に改修するのは、工数的な観点から現実的ではありません。
かと言って、 CI 実行時にそれらの解析結果を無視する事を認めてしまっては、SpotBugs の運用が形骸化してしまう未来が見えてきます。
したがって
- 要件1:新規コードに対する SpotBugs 検出箇所の改修は必須とする(=SpotBugs の運用を形骸化させない)
- 要件2:既存コードに対するリファクタリングを徐々に進めていく事ができる
これらの要件を満たす運用を考える必要がありました。
要件を満たす運用案
今回、上述の要件を満たすために利用したのが reviewdog というツールです。
reviewdog に吠えてもらう
reviewdog は、各種解析ツールの結果を入力値として受け取り、 GitHub や GitLab といったコードホスティングサービス上のレビューコメントとして出力してくれます。
そして、この優秀な番犬は GitHub Actions と組み合わせる事で解析ツールの実行結果を GitHub Checks のレポートとして出力する事もできるのです。
したがって、Pull Request 時の GitHub Actions による CI において SpotBugs の解析結果を reviewdog に渡す事で、当該 Pull Request の差分上に検出されたバグパターンだけを GitHub Checks で検証する事が可能となります。
なお、reviewdog はメジャーな解析ツールについては標準のパーサーがプリセットされており、-f
オプションでツール名を指定するだけで、解析結果から良い感じの指摘メッセージを生成してくれます。
残念ながら 2024年4月23日 時点で SpotBugs はサポートされていませんが、SpotBugs の出力形式として選択可能な OASIS 標準である SARIF 形式には対応済みのため、今回はこちらを利用しました。
以下、build.gradle および GitHub Actions の ワークフロー定義を掲載します。
plugins { id 'java' id("com.github.spotbugs") version "6.0.12" } // ... 中略 spotbugs { // Pull Request の変更ファイル以外に検出されたバグパターンにより // CI が失敗してしまう事を回避するため、SpotBugs の失敗判定は無視 ignoreFailures = true; } spotbugsMain { // 出力形式に sarif を指定 reports { sarif { required = true } } jvmArgs = [ '-Duser.language=ja' ] }
name: reviewdog_demo on: pull_request jobs: reviewdog: name: reviewdog runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - name: Set up Java 17 uses: actions/setup-java@v4 with: distribution: corretto java-version: 17 - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Grant permission for gradlew run: chmod +x ./gradlew - name: Run spotBugsMain run: ./gradlew spotBugsMain - name: Set up reviewdog uses: reviewdog/action-setup@v1 with: reviewdog_version: v0.17.2 - name: Run reviewdog env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: > cat ./build/reports/spotbugs/main.sarif | reviewdog -f sarif -reporter=github-pr-check -name run-reviewdog -tee
reviewdog が吠えてくれたおかげで、開発者は「リファクタリング推奨」である既存コード上の検出は意識せず、自身が変更を加えたコード上の検出だけに集中し、CI 成功を目指すことになります。
これで、上述の要件1を満たす事ができました。
リファクタリングを推進するもう一吠え
先ほどの reviewdog によるアノテーションを確認すると、SpotBugs が検出しているはずの赤枠部分のコードにはアノテーションが付与されていない事が分かります。
これでは、要件1は満たせていますが、既存コード上の検出は無視され続けてしまいます。
少しずつでもリファクタリングを進めていくためには、たとえ自分が書いたコードでないとしても「自身が変更を加えたファイル上に存在する既存バグパターン」くらいは一緒に修正していく事が望ましいと言えるでしょう。
そこで今回は、 reviewdog の -filter-mode
という実行時オプションを利用します。
このオプションは、reviewdog がツールから受け取った解析結果を出力する際のフィルタリング方法を制御するためのフラグです。この値を file
に設定する事で「追加あるいは変更されたファイル」に対する解析結果を出力させることができます。
name: reviewdog_demo on: pull_request jobs: reviewdog: name: reviewdog runs-on: ubuntu-latest steps: #... 中略 - name: Run reviewdog env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} # -filter-mode file を指定 run: > cat ./build/reports/spotbugs/main.sarif | reviewdog -f sarif -reporter=github-pr-check -name run-reviewdog -filter-mode file -tee
これにより、Pull Request の変更ファイル上のバグパターンはすべて CI で検出される事となり、要件2も良い感じに満たす事ができました。
細かい要望にも答えてくれる reviewdog、ハチ公もびっくりの忠犬ですね 🐶
最後に
本記事では、既存プロジェクトに対する SpotBugs の導入を、reviewdog + GitHub Actions により着実に進めていくノウハウを紹介しました。
「既存コードに対する検出をどう扱うか」という問題は、SpotBugs に限った話ではないと考えています。
本記事のアプローチが、既存プロジェクトに対する解析ツール運用を検討している方々の一助となれば幸いです。
採用リンク
私たちは一緒に働いてくれる仲間を募集しています!
次世代会計プロダクトDevOpsエンジニア(Ci*Xシリーズ)執筆:@satorin、レビュー:@miyazawa.hibiki
(Shodoで執筆されました)