対象読者
- 「このアプリケーション、Kotlinのバージョン古くなってきてるし、最新まで上げようぜ!」って仕事をやることになった、ちょっと前の私。
- およびそのような人(いるのか)。
前提条件
- 2017年頃ファーストリリース、現在も稼働中のバックエンドアプリケーション
- ただしアクティブなメンテナとなるチームはおらず、今回たまたま私が改修するタイミングがあった
- Java
- 1.8系
- Kotlin
- 1.2.10
- 2017年12月頃にリリースされた模様 github.com
- 1.2.10
- フレームワーク
- Spring Boot(1.5.8.RELEASE)*1
- プロジェクト管理
- その他
- JUnit5
- mockk
- 1.7.15
- mockito-kotlin
- 1.6.0
結果
- Kotlin
- 1.2.10 -> 1.7.10
- mockk
- 1.7.15 -> 1.12.0
- mockito-kotlin
- 1.6.0 -> 2.2.0 *2
- その他一部アプリケーションコードの修正(後述)
バージョンアップのモチベーション
Kotlin 1.2系が、最新のIntelliJ IDEAではサポートされていない為です。
執筆時点で最新バージョンの2022.2.1のIntelliJ IDEAでKotlin 1.2を利用しているアプリケーションを起動しようとしても、そもそも起動することができません。 (正確にはビルドすらできません。Kotlin: Language version 1.2 is no longer supported; please, use version 1.3 or greater.というメッセージの通り、最低でも1.3以上にする必要があります)
このことは開発生産性の観点で大きなマイナスなので、バージョンアップすることにしました。
方針
- 時間をあまりかけたくないので、変更は必要最小限に
- 極力バージョンアップ前に利用していたライブラリをそのまま利用する(ライブラリの追加、削除よりも、既存ライブラリの新しいバージョンを利用する)
- 極力アプリケーションコードの修正を加えない
Kotlinのバージョンを上げる
最新のバージョンを確認する
- GithubのReleasesページを確認 github.com
とりあえず上げてみる
- アプリケーションコード上で修正が必要な箇所はビルド時点では特になし*3
- Kotlinで書かれたコードをJava8上で動かす場合には、kotlin-stdlib-jre8 ではなく、kotlin-stdlib-jdk8を利用する必要がある点に注意が必要
単体テストを実行できるようにする
この時点では単体テストが実行できなくなっていた
- mockkが依存するCoroutine周りのクラスパスが変わっていた
- Kotlin 1.3以降でCoroutine周りの機能がデフォルトに変わったため
- ということでmockkを最新までバージョンアップ github.com
- mockkが依存するCoroutine周りのクラスパスが変わっていた
今度は一部のテストケースで「Caused by: java.lang.NoSuchFieldError: ASM_API」というエラーメッセージが出てテストに失敗するようになる
- これはmockito-kotlinが依存しているbytebuddyのバージョンが古いことによって起こっているらしい
- mockito-kotlinのバージョンも最新にすることで解決
アプリケーションが問題なく稼働することを確認する
- JAX-RSのParamConverterを利用する処理の周りがうまく動かないことを確認
- これはKotlinをバージョンアップしたことで、Null Safetyがより厳しく判定されるようになったため
例えば以下のようなParamConverterを実装したクラスがあるとします。(LocalDateTimeはJava標準のクラスとする)
class LocalDateConverter: ParamConverter<LocalDateTime> { override fun fromString(value: String): LocalDateTime { try { return LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) } catch (e: Exception) { throw IllegalRequestException("Invalid date string. $value") } } override fun toString(value: LocalDateTime) = value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) }
ParamConverterはJAX-RSにおいて、String型のメッセージパラメータと特定の型Tで相互に変換する責務を担います。
参考:https://spring.pleiades.io/specifications/platform/8/apidocs/javax/ws/rs/ext/paramconverter
上記クラスで実装している fromString
関数は、文字列から特定の型Tへの変換処理を担っています。
Kotlin1.2においては、この fromString
関数の引数としてnullが渡ってきても問題なく動いていました。しかし、Kotlin 1.7までバージョンアップされる過程で、Null Safetyが厳しく検査されるようになり、nullが渡ってきた場合に想定通りに動かなくなってしまったようです。
その為、ワークアラウンドとして以下のように、nullの値が渡ってきても問題ないようにアプリケーションコードを修正しました。
override fun fromString(value: String?): LocalDateTime? { try { return value ?.takeIf { value.isNotEmpty() } ?.let { LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) } } catch (e: Exception) { throw IllegalRequestException("Invalid date string. $value") } }
ということで
こまめなバージョンアップって大事ですね。
*1:こいつもついでにバージョンアップしたかったが今回は時間の都合で割愛
*2:mockito-kotlinは2系にバージョンアップされる過程でGroup IDが微妙に変わっているので注意が必要です。com.nhaarman -> com.nhaarman.mockitokotlin2
*3:Deprecatedな関数はいくつかあります。String.toUpperCase等。https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/to-upper-case.html