2021年12月04日 - Tomo Masakura    

静的コード解析と軽量Code Quality

GitLabの静的コード解析機能であるCode Qualityを活用する。

GitLabの静的コード解析機能であるCode Qualityを活用する方法を紹介します。また、GitLab標準のCode Qualityではなく、軽量なCode Qualityの設定方法も合わせて紹介します。

このブログのサンプルコードはJavaですが、他の言語でも同じようなことができるので、他の言語の方も参考にしてください。

GitLab Code Qualityとは?

GitLab Code Qualityは静的コード解析をGitLab CIで実行し、その結果をマージリクエストなどで確認できる機能です。

Code Qualityを利用すると、次のように静的コード解析の結果がマージリクエストの概要で確認できます。この例では、Sample.javaファイルの一行目にJavadocコメントがないと指摘されています。

マージリクエストのCode Quality結果

実際に触ってみたい方は、お試しのマージリクエストにアクセスし、コードの品質 1ポイント悪化の行を展開ボタンで展開してください。

なお、このマージリクエストの概要に静的コード解析の結果が表示される機能は、無償のGitLabでも利用できます。それ以外の機能については有償の契約が必要です。ただし、 GitLab.comの公開プロジェクトには、プロジェクトレベルのUltimateプランの機能が開放されている ので、OSSの開発やUltimateプランの機能を試してみたいなあというときにはGitLab.comの公開プロジェクトを利用してください。

有償のUltimateプランを利用していれば、次のようにマージリクエストの変更タブのコード差分上でも確認ができます。

コード差分でのCode Quality結果

お試しのマージリクエストにアクセスし、変更タブでコード差分を表示してください。

このブログの執筆時点で、GitLabにはコード差分のインラインビューでCode Quality結果が表示されないバグがあります。うまく表示できない場合は、並列ビューに切り替えてください。

有償のPremium以上のプランを利用していれば、次のようにパイプラインの詳細ページでも確認ができます。

パイプライン詳細でのCode Quality結果

お試しのパイプライン詳細ページにアクセスしてください。

静的コード解析とは?

静的コード解析とは、プログラムを実行せずにソースコードの問題を検出することです。一般的に、人の目で問題を探し出すのはコードレビューと呼ばれ、静的コード解析用のツールを使うことを静的コード解析と区別することが多いです。

例えば、次のJavaのコードは括弧の位置が一般的ではありません。

class Sample
{
}

静的コード解析ツールはこのようなソースコードの問題を検出します。次はJavaの静的コード解析ツールの一つ、Checkstyleの実行結果です。

[ant:checkstyle] [WARN] /home/masakura/works/blog/code-quality/code-quality-java-sample/src/main/java/Sample.java:2:1: 'class def lcurly' のインデントレベル 0 は正しくありません。期待されるレベルは 2 です。 [Indentation]
[ant:checkstyle] [WARN] /home/masakura/works/blog/code-quality/code-quality-java-sample/src/main/java/Sample.java:2:1: 列 1 の '{' は前の行に書いてください。 [LeftCurly]
[ant:checkstyle] [WARN] /home/masakura/works/blog/code-quality/code-quality-java-sample/src/main/java/Sample.java:3:1: 'class def rcurly' のインデントレベル 0 は正しくありません。期待されるレベルは 2 です。 [Indentation]

Checkstyleはコーディングスタイル違反の検出が主ですが、SpotBugsのように、バグと思われる、もしくはバグを引き起こしやすいコードの問題を検出するツールもあります。

次のコードはJavaでコードを書いているときにありがちなミスです。

if (status == "active") {
    // ...
}

SpotBugsは次のように問題があることを指摘します。

H B ES: String パラメータを == や != を使用して比較しています。Sample.main(String)  該当箇所 Sample.java:[line 3]

もちろん、このような問題をコードレビューで指摘することができますが、レビュアーにもレビューイにも大きな負荷がかかります。

括弧の位置の間違いのような些細な問題と、分かりにくい名前・アルゴリズムの間違い・仕様違反では、後者のほうが重要であることはみなさんおわかりでしょう。しかし、前者はとにかく数が多くなりがちで、コードレビューでの指摘では数の少ない後者の問題が埋もれがちになります。最悪、重要な問題が無視されることに繋がりかねません。

静的コード解析ツールを活用すると、このような些細で数の多い問題をコードレビューに回す前に解決することができます。つまり、コードレビューではより重要な問題に集中できるようになるわけですね。

余談ですが、私はいろんなプロジェクトに並行して参加しています。プログラム言語はそれぞれ違いますし、たとえ同じであってもコーディング規約が異なります。静的コード解析ツールのおかげでなんとかなってるところもあります。また、初めて使う言語でも、やってはいけない書き方を指摘してくれたりするので、とても助かっています。

GitLab標準のCode Qualityの問題

GitLab Code Qualityを使うと、静的コード解析をGitLab上で完結できます。プロジェクトに組み込むのも、公式ドキュメントにあるとおり、.gitlab-ci.ymlファイルに次のように追加するだけです。

include:
  - template: Code-Quality.gitlab-ci.yml

これだけで、冒頭で紹介したCode Qualityが使えるようになります。

大変お手軽なのですが、Code Qualityは内部でCode Climateを利用しているためか、処理に時間がかかります。(GitLab.comの共有Runnerでは6分程度でした)また、Docker Engineが利用できるGitLab Runnerが必要です。(GitLab.comの共有Runnerでは利用できます)

しかし、実はGitLabのCode QualityにCode Climateは必須ではありません。必要なことは、GitLab CIジョブで静的コード解析を実行することと、そのレポートをCode Quality形式のJSONで出力することの二点だけです。

つまり、Checkstyleなどの静的コード解析ツールを実行し、そのレポートをCode Quality形式のJSONに変換するようにGitLab CIジョブを設定すれば、Code Qualityの機能を使うことができるわけです。

CIジョブの設定を自力で書く必要がありますが、Code Climateを使わないことで、実行時間が短く、Docker Engineが利用できない自前のGitLab Runnerでも使えるCode Qualityが手に入ります。

このブログではこのCode Climateを使わない方法を軽量Code Climateと呼ぶことにします。その方法を紹介します。

軽量Code Qualityの設定

以下のものは構築済みとします。

  • JDK / Gradle
  • Git
  • GitLabプロジェクト

なお、いちいちプロジェクト作成して設定するのだるいという方のために、サンプルプロジェクトを用意しています。次の手順に従ってプロジェクトを作成してください。

  1. GitLabにアクセスして、上部メニューの+ボタンからNew project/repositoryをクリックします
  2. プロジェクトのインポート->リポジトリ URLをクリックします
  3. Git リポジトリ URLhttps://gitlab.com/creationline/code-quality-java-sample.gitを入力し、プロジェクト名Project slug可視レベルを適当に入力し、プロジェクトを作成ボタンをクリックしてプロジェクトを作成します
  4. 左側のメニューのCI/CD->パイプラインより、Run pipeline をクリックします
  5. Run for branch namemasterを選んでRun pipelineをクリックし、パイプラインを実行します
  6. 4 と 5 の同じ手順で、branch1のパイプラインも実行します
  7. 左側のメニューのマージリクエストを選んで、新規マージリクエストをクリックします
  8. ソースブランチを選択をクリックして、branch1を選んだ後、Compare branches and continue->Create マージリクエストをクリックし、マージリクエストを作成します
  9. パイプラインが完了すると、マージリクエストの概要でCode Qualityの結果が確認できるようになります

対象となるGradleプロジェクトを作成

静的コード解析を行う対象のプロジェクトをまずは作成します。

プロジェクトの作業ディレクトリを作成して、次の内容でbuild.graldeファイルを作成して下さい。

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

src/main/java/Sample.javaファイルを作成します。ここでは、静的コード検査に引っかかるようにわざと括弧の位置を間違えています。

class Sample
{
}

プロジェクトの作成できました。念の為、ビルドできるか試してみましょう。

$ gradle build --continue

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed

Checkstyleの組み込み

Code Qualityを実行する前に、静的コード解析ツールを組み込まなければいけません。このブログではCheckstyleを利用します。

組み込むにはbuild.graldeファイルに追記していくことになるのですが、何でもかんでもbuild.gradleに書いていくと肥大化しまい、あとあとメンテナンスが大変になるので、Code Qualityに関する設定は別のファイルに分離することにします。(このサンプルレベルだとそれほど意味はありませんが…)

gradle/codequality.gradleファイルを次の内容で作成します。

apply plugin: 'checkstyle'

checkstyle {
    toolVersion = '9.0'
    ignoreFailures = false
    maxWarnings = 0
}

build.gradleファイルでこれを読み込むように修正します。

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

apply from: 'gradle/codequality.gradle'

Checkstyleは検査ルールが必要です。今回はGoogle Java Style GuideのCheckstyleルールgoogle_checks.xmlを利用します。ダウンロードして、config/checkstyle/checkstyle.xmlに保存してください。

これでCheckstyleの組み込みが完了しました。buildタスクを実行して、Checkstyleで静的コード解析を行います。

$ gradle build --continue

> Task :checkstyleMain FAILED
[ant:checkstyle] [WARN] /home/masakura/tmp/project1/src/main/java/Sample.java:2:1: 'class def lcurly' has incorrect indentation level 0, expected level should be 2. [Indentation]
[ant:checkstyle] [WARN] /home/masakura/tmp/project1/src/main/java/Sample.java:2:1: '{' at column 1 should be on the previous line. [LeftCurly]
[ant:checkstyle] [WARN] /home/masakura/tmp/project1/src/main/java/Sample.java:3:1: 'class def rcurly' has incorrect indentation level 0, expected level should be 2. [Indentation]

FAILURE: Build failed with an exception.

...

実行結果にあるとおり、括弧の位置がおかしいよと指摘されています。

build/reports/checkstyleディレクトリに生データのXMLファイルと、人が見てわかるレポートのHTMLファイルが生成されていますので、指摘の数が多いときはこのHTMLレポートを見ると良いでしょう。

CheckstyleのHTMLレポート

Code Quality形式にレポートを変換

説明したとおり、Code Qualityを利用するために、静的コード解析の結果をCode Quality形式のJSONに変換しなければなりません。

ちなみに、Code Qulaity形式のJSONはCode Climate形式のサブセットなので、様々なレポートをCode Climate形式に変換できるGradleプラグイン、Violations Gradle Pluginが使えます。

gradle/codequality.gradleファイルを次のように修正します。

buildscript {
    repositories {
        maven { url 'https://plugins.gradle.org/m2/' }
    }
    dependencies {
        classpath "se.bjurr.violations:violations-gradle-plugin:1.50.16"
    }
}

apply plugin: 'checkstyle'

checkstyle {
    toolVersion = '9.0'
    ignoreFailures = false
    maxWarnings = 0
}

task codequality(type: se.bjurr.violations.gradle.plugin.ViolationsTask) {
    codeClimateFile = file 'build/reports/codequality.json'
    violations = [
            ['CHECKSTYLE', buildDir.path, '.*/checkstyle/.*\\.xml$', 'Checkstyle']
    ]
}

checkstyleMain.finalizedBy codequality
checkstyleTest.finalizedBy codequality

タスクを実行します。

$ gradle build --continue

...

* What went wrong:
Execution failed for task ':checkstyleMain'.
> Checkstyle rule violations were found. See the report at: file:///home/.../project1/build/reports/checkstyle/main.html
  Checkstyle files with violations: 1
  Checkstyle violations by severity: [warning:3]

...

次のようにCode Quality形式のbuild/reports/codequality.jsonファイルが生成されているはずです。

[
  {
    "description": "\u0027class def rcurly\u0027 has incorrect indentation level 0, expected level should be 2.",
    "fingerprint": "b17a1fc2c977f0eabf09c43918de5b01c2f1c7402220adaae9da47a67c776388",
    "location": {
      "path": "/home/.../project1/src/main/java/Sample.java",
      "lines": {
        "begin": 3
      }
    },

...

GitLab CIジョブで軽量Code Qualityを実行する

静的コード解析ツールの実行とレポートの変換までできましたので、GitLab CIジョブでこれを実行するようにします。

次の内容で.gitlab-ci.ymlファイルを作成してください。

image: gradle

build:
  script:
    - gradle build --continue
  artifacts:
    reports:
      codequality: build/reports/codequality.json

artifacts:reports:codequalityでCode Quality形式のレポートファイルを成果物に含めています。この指定で、GitLabはレポートをマージリクエストなどに表示できるようになります。

余談ですが、artifacts:reportsで指定できるのはcodequalityだけではありません。単体テストレポート用のjunitなどもあります。興味がある方は公式リファレンスをご覧ください。

修正がおわったら、コミットしてGitLabプロジェクトにプッシュしてください。

GitLabプロジェクトをウェブブラウザーで開いて、左のメニューよりパイプラインを選択します。おそらくパイプラインは青い丸の実行中になっていると思います。パイプラインが完了すると赤いバツ(失敗)にステータスが変わります。

パイプラインの完了後に、各パイプラインの右にあるケバブメニューから、Code Quality形式のレポートファイルをダウロードできるようになります。

Code Quality形式のJSONファイルのダウンロード

有償のPremium以上のプランを利用していれば、該当するパイプラインをクリックして詳細ページを開くと、Code Qualityタブで静的コード解析のすべての結果にアクセスできます。

パイプライン詳細でのCode Quality結果

レポート内のコードファイルパスをCode Qualityで扱えるようにする

Code Qualityの設定は以上で完了しています。ですので、あとはマージリクエストを作って静的コード解析結果を確認するだけなのですが…

残念ながらそうもいきませんでした。

今回変換したcodequality.jsonファイルでは、問題のあるソースコードのファイルパスは絶対パスになっていました。

"location": {
  "path": "/home/.../project1/src/main/java/Sample.java",
    "lines": {
      "begin": 3
    }
  }

ですが、Code Qualityは次のようにプロジェクトルートからの相対パスであることを期待しています。

"location": {
  "path": "src/main/java/Sample.java",
  "lines": {
    "begin": 3
  }
}

Violationsは元の静的コード解析結果にあるファイルパスをそのまま利用します。CheckstyleのXMLレポートは絶対パスとなっていますので、codequality.jsonも絶対パスになってしまうわけです。

面倒なのですが、ファイルパスを相対指定に変換するコードを追加します。gradle/codequality.gradleファイルを次のように修正してください。

import groovy.json.*

buildscript {
    repositories {
        maven { url 'https://plugins.gradle.org/m2/' }
    }
    dependencies {
        classpath "se.bjurr.violations:violations-gradle-plugin:1.50.16"
    }
}

apply plugin: 'checkstyle'

checkstyle {
    toolVersion = '9.0'
    ignoreFailures = false
    maxWarnings = 0
}

task codequality(type: se.bjurr.violations.gradle.plugin.ViolationsTask) {
    codeClimateFile = file 'build/reports/codequality.json'
    violations = [
            ['CHECKSTYLE', buildDir.path, '.*/checkstyle/.*\\.xml$', 'Checkstyle']
    ]

    doLast {
        def file = new File('build/reports/codequality.json')
        def json = new JsonSlurper().parse(file)

        for (point in json) {
            point.location.path = point.location.path.replace("${rootProject.rootDir}/", '')
        }

        file.createNewFile()
        file.text = new JsonGenerator.Options()
            .disableUnicodeEscaping()
            .build()
            .toJson(json)
    }
}

checkstyleMain.finalizedBy codequality
checkstyleTest.finalizedBy codequality

doLastでViolationsプラグインでCode Quality形式に変換した後に、codequality.jsonを読み込んで、絶対パスを相対パスに書き換えた上で上書きしています。

修正したらタスクを実行して確認します。

$ gradle build --continue

見事に変換できました!

"location": {
  "path": "src/main/java/Sample.java",
  "lines": {
    "begin": 3
  }
}

なお、ファイルパスを変換後のファイルは改行が取り除かれ、一行になっています。そのままでは読みにくいので、整形しています。

マージリクエストで静的コード解析の結果を確認する

さて、今度こそ設定は終わったので、マージリクエストを作成して、Code Qualityを使ってみましょう。

その前に、src/main/java/Sample.javaファイルを修正して、コードの問題をすべて解消します。

class Sample {
}

タスクを実行して、問題がないことを確認してください。

$  gradle build --continue

BUILD SUCCESSFUL in 1s
4 actionable tasks: 3 executed, 1 up-to-date

問題がなければ、コミットしてプッシュしてください。

これで準備完了しました!マージリクエストを作成し、Code Qualityを堪能していきます。

まずはブランチを作成します。

$ git checkout -b branch1

src/main/java/Sample.javaファイルを次のように修正してください。(アクセス修飾子にpublicを追加しています)

public class Sample {
}

括弧の位置がおかしいエラーだと新鮮味がないので、Google Java Style Guideの、公開クラスにはJavadocコメントが必要というルールに違反してみました。

タスクを実行すると、Javadocコメントを書くように指摘されています。

$ gradle build --continue

> Task :checkstyleMain FAILED

[ant:checkstyle] [WARN] /home/masakura/tmp/project1/src/main/java/Sample.java:1:1: Missing a Javadoc comment. [MissingJavadocType]

FAILURE: Build failed with an exception.

...

コミットしてプッシュしてください。そして、GitLabプロジェクトの左側のメニューからマージリクエストを選んで、マージリクエストを作成してください。

マージリクエストのパイプラインが完了するまでしばらく待ちます。完了すると、マージリクエストのパイプラインの青い丸アイコンが緑の丸アイコンか(成功時)、赤いバツアイコン(失敗時)にかわります。青い丸アイコンのまま終わらないときは、ページをリロードしてください。

マージリクエストのパイプラインが完了(失敗した)

パイプラインが完了したら、コードの品質...の右の展開ボタンをクリックして、Code Qualityの結果を確認できます。

マージリクエストのCode Quality結果

もし、Ultimateプランを利用している場合は、マージリクエストを変更タブに切り替えることで、次のようにコード差分でも確認できるようになります。(GitLab.comの公開プロジェクトでも可)

コード差分でのCode Quality結果

最初の方で解説したとおり、インラインビューではバグがあります。表示されないなと思ったら、並列ビューに切り替えてください。

実際の開発では、このマージリクエストの結果を受けて、静的コード解析の指摘をすべて修正してからコードレビューを依頼することになります。コードレビューでは、静的コード解析ツールでは検出しづらい、不適切な名前やアルゴリズムの不備などのより重要な指摘に集中できます。

ちなみに、マージリクエストに表示されるコードの問題には、元のブランチにあるコードの問題は含まれません。マージリクエストの変更で新たに発生したコードの問題だけが表示されます。

すべての静的コード解析の指摘を取得するには、GitLabプロジェクト左側のメニューよりパイプラインを選んで、該当するパイプラインの右端にあるケバブメニューをクリックして、アーティファクトのダウンロードbuild:codequalityをダウンロードしてください。

Code Quality形式のJSONファイルのダウンロード

有償のPremiumプランを利用している場合は、パイプライン一覧で選択したパイプライン詳細ページのCode Qualityタブが利用できます。(GitLab.comの公開プロジェクトでも可)

パイプライン詳細でのCode Quality結果

最後に

軽量Code Qualityいかがだったでしょうか?

GitLab標準のそれと比べると設定が面倒ですが、CIジョブの実行に時間がかからないのは大きなメリットです。CIを活用しているチームにはこの軽量Code Qualityをおすすめします。

Code Qualityとは直接は関係ありませんが、静的コード解析ツールは利用している統合開発環境やエディターに必ず組み込んでください

統合開発環境にCheckstyleを組み込み

1,000行くらいのコードを書き終えて、GitLabにプッシュした後で「30箇所くらい括弧の位置を間違えてるから直してください」とCIから指摘されたらと、想像してみてください。修正だけならいいのですが、テストもやり直しです。とはいえ、やる気が出ないので修正後のテストをつい省いてしまってあとで痛い目を見ることになります。

統合開発環境やエディターに組み込んでおけば、コードを書いている最中に「ここ括弧の位置を間違えてるから直してください」と指摘を受けることができます。開発者はその場で間違いを修正できます。

改めて。静的コード解析ツールは利用している統合開発環境やエディターに必ず組み込んでください

それならCode Qualityは不要なんじゃと思うかもしれません。しかし、統合開発環境やエディターに組み込んだ静的コード解析ツールは開いているファイルのみを解析することが多いため、どうしても漏れが出ます。その保険としてパイプラインでCode Qualityを実行する必要があります。

まとめです。

  • 静的コード解析ツールを使いましょう!
  • 統合開発環境やエディターに静的コード解析ツールを組み込みましょう!
  • Code Qualityを活用しましょう!

おまけ

SpotBugsやその他の場合

SpotBugsでもCheckstyleのときと同じようにCode Qualityを利用できます。

しかし、Cyeckstyleより厄介です。問題のあるソースコードのファイルパスが、Javaパッケージのルートディレクトリからの相対パスになっているからです。(src/main/javatest/main/java基準となる)

"location": {
  "path": "Sample.java",
  "lines": {
    "begin": 3
  }
},

ファイルパスを変換する必要があるのはCheckstyleと同じですが、src/main/javaを追加する必要があります。ですが、test/main/javaや他の場合もあるので、一筋縄ではいきません。

つまり、静的コード解析ツールごとにこのあたりを調整する必要があります。そこがこの方法の欠点です。どなたかがViolationsでファイルパス変換機能が実装されるか、別の変換ツールの登場を待ちましょう!

消極的な対処方法として、ファイルパスを変換しないという手もあります。マージリクエストのコード差分で静的コード解析の結果が確認できなくなったり、問題のあるファイルへのリンクをクリックしても別のページが表示されたりしますが、気にしなければそれでいいのかもしれません。

無償のGitLabですべての静的コード解析結果にアクセスする

無償のGitLabでは、パイプライン詳細ページにあるCode Qualityタブがありません。マージリクエストの概要タブに表示される分だけになりますが、これはあくまでマージリクエストで新たに発生した問題だけが表示されます。

GitLabはGitLab CIの成果物にアクセスする機能があります。この成果物にCheckstyleが出力した人が読める形式のHTMLレポートを含めると、無償のGitLabでもマージリクエストやパイプラインから省略されていない静的コード解析の結果にアクセスできるようになります。

次のようにして、.gitlab-ci.ymlartifacts:pathsでHTML形式のレポートファイルを成果物に含めます。

image: gradle

build:
  script:
    - gradle build --continue
  artifacts:
    expose_as: codequality
    paths:
      - build/reports/checkstyle/main.html
    reports:
      codequality: build/reports/codequality.json

また、artifacts:expose_asを指定すると、マージリクエストにリンクが表示されるので、少し便利です。

Gitlab x icon svg