2022年版 VSCodeでJavaを気持ちよく開発する環境構築

# 背景 (自分語りなのでとばしてもいいです)

筆者は普段はPython、C++、Rust、Goを主に書いていて機械学習、数理最適化関連のアプリケーションやWebAPIの開発をしています。機械学習はPython、数理最適化はC++ or Rust、WebAPIはPython or Goというような使い分けをしています。今の会社のプロダクトの内、あまり機械学習と関係ないのにPythonで書かれているものがあり、しかもMyPyが導入されていなく、型hintも適当であまりにも可読性が低いものがあって改修しようと考えています。その際、せっかくなので今後外注もしやすいようにJavaで書き直すことも検討し、気持ちよく開発できる環境をいろいろと試行錯誤しました。

# なぜJavaなのか？ (自分語りPart 2)

本当はRustで書きたいし、社内の自分の部門はこの用途でRustメインで書こうと決めましたが、外部に今後の保守を外注することも考えるとJavaになりました。Kotlinも考えましたが、外注先がJavaしか書けない場合もありますし、何よりVSCodeにはMicrosoft社、あるいはJetBrains社によるKotlinの公式の拡張が無く、個人の方が開発している[^vscode-kotlin]という状況です。このKotlinの拡張の出来自体はすばらしいのですが、開発者の方は初めはKotlinを使用しようと思ってこの拡張を作ったが、結局Kotlinは使用しなくなったのでメンテするのが大変で誰かに手伝ってほしいという状態で今後の不安が残ります。Kotlinを作っているJetbrains社自体がIntellijやFleetなどのIDEを開発していて競合のVSCode向けに作る気は全く無いと思うのであまり期待はできません。したがって、Microsoftが公式で拡張を開発しているJavaの方がVSCodeで書く上ではよさそうです。Javaの方も近年ではScalaやKotlinの様々ないいところを取り入れて、唯一Javaの言語仕様でサポートされていないNonNull/Nullableの対処のみ何とかすればKotlinじゃなくてもいいと考えました。

[^vscode-kotlin]: https://github.com/fwcd/vscode-kotlin


# 考慮した結果の環境

- Builder: Gradle or Maven 
- NonNull: `org.springframework.lang.NonNullApi` 
- Linter: Checkstyle (Google Style) + ~~SonarLint~~
- Formatter: google-java-format (+ spotless)
- Test Framework: JUnit5 + JaCoCo
- 利用するVSCodeの拡張
    - `vscjava.vscode-java-pack`
    - `vscjava.vscode-gradle`
    - `nicolasvuillamy.vscode-groovy-lint`
    - `shengchen.vscode-checkstyle`
    - ~~`sonarsource.sonarlint-vscode`~~
    - `ilkka.google-java-format`
    - `ryanluker.vscode-coverage-gutters`

以下説明をしていきます。

## NonNull/Nullableの対応

JavaにはAnnotationを利用して引数や戻り値がNonNullなのかNullableなのかをチェックする仕組みがあります。このNull関連のAnnotationがまたたくさんあり、どれがいいのかを議論した記事などもたくさんありますがここでは触れません。私が欲しいのはKotlinのようにデフォルトでNonNullを強制し、必要な場合だけNullableを許可するような機能です。この条件で探したところSpring Frameworkの中にある`NonNullApi`というannotationが見つかりました。詳細は以下の記事を参照してください。

https://www.baeldung.com/spring-null-safety-annotations

使う際にはpackageルートに`package-info.java`というファイルを作成し、中に
```java
@org.springframework.lang.NonNullApi
package sample;
```
の様に書くだけです。これでこのパッケージ全体でNonNullがデフォルトの挙動になり、必要な場合に`@NullAble`を付けるか`Optional`でラッピングするなどの対応をすればよくなります。

Intellijではこのannotationもデフォルトでチェックしてくれますが、VSCodeではそれができなく、SonarLintを試したらやってくれたので`sonarsource.sonarlint-vscode`が必須の拡張になります。

### 追記[2022/11/03]

VSCodeのJava拡張(`redhat.java`)のv1.11.0からAnnotationを利用したNullチェックが利用可能になりました。これによりSonarLintが不要になりました。ただし`org.springframework.lang.NonNullApi`だとちゃんと動いてくれなく、同様の機能を有する`org.eclipse.jdt.annotation.NonNullByDefault`にすると意図した挙動になります。また、この機能を正しく利用するには`.vscode/settings.json`の`java.settings.url`で設定ファイルを例えば`.vscode/java_settings.prefs`のように指定し、その中に以下のように記載します。

```
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.Nonnull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=jorg.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
```

この部分についてはドキュメントには書かれておらず、以下のスレッドから推測した挙動です。
https://github.com/redhat-developer/vscode-java/issues/1693

## Formatter

Formatterが結構曲者でした。google-java-formatを利用したいのですが、VSCodeの公式サイトはじめ、9割くらいの記事はsettings.jsonに

```
"java.format.settings.url": "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml"
```

を追加するという書き方になっていますが、このファイルはもう何年も更新されていなく、後述のCheckStyleのGoogle Styleでlintすると大量に警告が出てきます。実はgoogle-java-formatの挙動はエディタの標準機能では設定できない項目も多く、単純に設定をするのではなくてGoogleが公式にIntellijやEclipse向けに拡張を開発しています。そして残念ながらVSCode向けにはありません。これはgoogle-java-formatがJavaで開発されており、IntellijやEclipseにはそのまま組み込めるけどElectronを利用してTypescriptで書かれているVSCodeにはそのまま組み込めないためだと思います。

幸いなことに3rd partyの拡張が公開されていて、これは単純に外部コマンドでgoogle-java-formatをたたいているだけです。

https://github.com/ilkka/vscode-google-java-format

どうもMacはhomebrewで `google-java-format` というコマンドが導入できるそうですが、WindowsやLinuxの場合はjavaコマンドでjarファイルを実行するスクリプトを書く必要があるようです。どうせwrapperスクリプトを書くならもういっそのこと実行ファイル化しようということでGraalVM Native Imageで事前にgoogle-java-formatを実行ファイルにし、それをセットして利用しています。こうすることでJVMの実行時の1秒弱の時間も節約できてきびきび動きます。

```
"google-java-format.executable-path": "/usr/local/bin/google-java-format-1.15.0-linux-x86_64",
```

GraalVMで変換するときにまたいろいろとオプションを追加しないといけなかったのですが、今回はその話は省略します。以下のレポジトリにWindowsとLinux向けのビルドスクリプトを上げてあります。Releaseページには実際に作った1.15.0の実行ファイルもおいてあります。

https://github.com/lucidfrontier45/google-java-format-native

`.gradle`ファイルのフォーマットは`nicolasvuillamy.vscode-groovy-lint`を利用しています。

## Linter

Checkstyleを利用します。VSCodeからは`shengchen.vscode-checkstyle`という拡張で利用できます。私は`google_checks-10.3.1.xml`というファイルをGoogleStyleの[公式レポジトリ](https://github.com/checkstyle/checkstyle/tree/master/src/main/resources)からダウンロードしておきJavadocが無い時に出る警告だけコメントアウトしています。

~~また、前述した通り`@NonNullApi`をチェックするために`sonarsource.sonarlint-vscode`もインストールします。~~

## Test Framework

JUnitとTestNGが二大巨頭のようですが、詳しくないのでとりあえずJUnit5を選択しました。TestNGの方がいいという場合はコメントなどで教えてください。そしてカバレッジを取得するためにJaCoCoを利用します。Gradleの設定は基本的に[公式ページ](https://docs.gradle.org/current/userguide/jacoco_plugin.html)に書いてある通りです。

```gradle
test {
    finalizedBy jacocoTestReport // report is always generated after tests run
}

jacoco {
    toolVersion = "0.8.7"
}

jacocoTestReport {
    dependsOn test // tests are required to run before generating the report
    reports {
        html.required = true
        csv.required = false
        xml.required = true
    }
}
```

そしてこのカバレッジをVSCodeで表示しているソースコード上に重ねるのに`ryanluker.vscode-coverage-gutters`を使用します。Gradle上でJaCoCoを実行した際にxmlはデフォルトで`jacocoTestReport.xml`という名前で出力されますが、これはCoverage Guttersがデフォルトで監視しているファイルには含まれていないのでsettings.json内に追加してあげます。

```json
    "coverage-gutters.coverageFileNames": [
        "cov.xml",
        "coverage.xml",
        "jacoco.xml",
        "coverage.cobertura.xml",
        "jacocoTestReport.xml"
    ]
```

# まとめ

上記の設定はまとめてGitHubにTemplate Projectとして公開しています。
https://github.com/lucidfrontier45/JavaTemplate

他にもおすすめの設定等ありましたら是非教えてください。