アプリケーションエンジニアの宮田です。
自分の所属しているチームでは2019年10月から、既存のJava8
で書かれたSpringBoot
アプリケーションへKotlin
の導入を始めました。
今回のブログでは、Kotlin
を導入して良かったこと、導入に当たって直面した課題、1年弱やった感想についてまとめます。
Kotlinを導入して良かったこと
安心感を持ってコードを書ける
個人的には「特に意識しなくてもnull
安全・immutable
なコードが簡潔に書けるようサポートが行き届いていて、その中でコードを書ける」というのがKotlin
を導入して最も良かったことです。
やろうと思えばJava
でもKotlin
と同じくらい堅牢なコードは書けますが、そのために必要とされる知識や努力は大きく、コードも煩雑になります。
また、「コード全体が基本的に安全な方向に作られている」という安心感はJava
に無いものでした。
方々で「Kotlin
は何も考えず入れるだけでよくなる」と言われているのを観測していますが、全てが揃っているからこそだと思います。
シンプルかつクリーンに書ける
Kotlin
はJava
に比べシンタックスがシンプルで、更に様々な処理がシンプルに書けるような記法が用意されています。
この記法を覚える難易度そのものは高いですが、使いこなせば使いこなすほどコードがシンプルに書けます。
また、スコープ関数もKotlin
の強力な機能の一つで、可読性を損なわず、かつ余計な一時変数によってスコープを汚すことなくコードを書くことができます。
記法のシンプルさは単純に生産性に繋がるだけでなく、そのシンプルさから来る可読性の高さは保守性にも大きく貢献していました。
導入に当たって直面した課題
このように良さが多々あるKotlin
ですが、既存のJava
プロジェクトに導入するということも有り、導入に当たって幾つかの課題に直面しました。
導入順序としては、「新規部分はKotlin
を使い、既存部分も徐々にKotlin
化していく」という形を取ったため、解決策もそれを前提としている点はご注意下さい。
Java向けのリフレクションを用いるライブラリと相性が悪い
Java
では「インスタンス生成 -> setter
経由でフィールド初期化」という順序でのオブジェクト初期化が一般的で、リフレクションを用いるようなライブラリもそれを前提にしていることが殆どです。
一方、Kotlin
では「コンストラクタやファクトリーメソッドで全フィールドを初期化」という形が一般的で、そのままの状態ではJava
向けのリフレクションを用いるライブラリを利用できません。
プロジェクトでは特にModelMapper
とBeanPropertyRowMapper
を用いている部分をどうするかが問題となりました。
簡単に検討した選択肢としては以下が有りました。
- 必要な部分だけ
Java
コードを残す kotlin-noarg
プラグインを用い、既存ライブラリが機能するようにする- ライブラリを探す
- 手書きにする
1と2はそれなりに不都合無くできるものの、実行時エラーの危険性が残ることから望ましくなく、3はよいライブラリが見つからず、4はJava
から乗り換える利点が減ることから避けたいという話になりました。
解決手段
諸々の検討の末、マッピングツールを自作することで解決しました。
Kotlin
のリフレクションによる関数呼び出しは非常に洗練されており、細かい所を含めても3日程度で実装を完了できました。
以下は実際にBeanPropertyRowMapper
を置き換えるために作成したツールの技術ブログです。
このツールは、高速化や機能改善を行った上でKRowMapper
というOSSとして公開しており、実際にプロジェクトのKotlin
向けRowMapper
は全てこれによって置き換えが完了しています。
また、ModelMapper
に関しても置き換え用ライブラリを作成しましたが、チーム内で「別にマッピングツール無くても良いのでは?」という雰囲気が出てきたため、まだプロダクションコードへの導入は行っていません。
問題として残った部分
複雑な継承関係を持つオブジェクトに関してはModelMapper
への依存が大きく、一気に置き換え切ることもできなかったため、レスポンスオブジェクトに関してはJava
のまま残っているコードが有ります。
補足: POSTされたデータの受け取りに関して
「DBからの取得が問題になるんだからPOST
されたデータの受け取りにも問題が出るんじゃないか」という疑問が有るかもしれませんが、これに関してはプロジェクトでJSON
のパースに用いているJackson
がKotlin
を強力にサポートしているため、特に問題にはなりませんでした。
lombokで生成したgetter/setterにアクセスできない
Kotlin
導入前のJava
プロジェクトでは、省力化のため、lombok
を導入してPOJO
のgetter
/setter
をやっていました。
一方、コンパイル順序の問題から、Kotlin
からはlombok
で生成するgetter
/setter
にアクセスできずコンパイルが通らないという問題が出ました。
選択肢としては以下の3つを検討しました。
- ビルド設定を見直す
- 全て
Kotlin
化する - 必要な部分だけ
delombok
してgetter
/setter
を書く
解決手段
最終的に、問題になる部分が少なかったことから、基本的に全てKotlin
化して、本当にどうしようもない部分はdelombok
してgetter
/setter
を書く形での解決としました。
導入当初はそれなりに大きな問題でしたが、DB
からの取得をKotlin
で書くことができるようになり、Kotlin
化も進んだことによって問題が小さくなる形で消えていきました。
Javaとの繋ぎ込みでバグを作りこんでしまう
冒頭で書いた通り、Kotlin
はJava
に比べ簡単なシンタックスで安全なコードが書けるようになっています。
一方、これに慣れた状態でJava
を書くことで、時折バグを作りこんでしまうことが有りました。
解決手段はKotlin
化を進める位しか無いというのも厄介な部分です。
以下2つは特に頻出した間違いです。
nullabilityを勘違いする
どうしてもJava
を残さざるを得なかった部分を触っていた時、nullability
を勘違いしてエラーを作りこんでしました。
このパターンは、コード内で複雑な扱いをしていたり、特にフロントからのPOST
構造を勘違いした時に発生していました。
比較方法を勘違いする
プロジェクトでは、Java
コードでの比較で==
, equals
, Objects.equals
の3つを状況によって使い分けていました。
一方、Kotlin
では、==
で大体の比較を書くことができます。
このため、Java
で==
だけで比較を書いてしまいかけるということが何度か有りました。
1年弱やった感想
1年弱に渡りKotlin
への置き換えを進めてきましたが、やはりKotlin
のシンタックスの恩恵は非常に大きく、バグを減らしながら質の高いコードを書くことができると感じます。
繰り返しになりますが、「ただ書くだけで堅牢に書いたJava
と同じくらい安全で、かつシンプルに書くことができる」というのは非常に生産性を高めることができます。
導入に当たって困る場面も思っていたほどは有りませんでした。
まず、業務としてKotlin
を経験した人間無しでの導入開始でしたが、「書けない」ということで困ることは殆ど有りませんでした。
ただ、背景としてJava8
のOptional
やstream API
、lombok
に関してはプロジェクトに導入していたので、それらの経験は理解の助けになっていたとは思います。
ライブラリの不足や互換性で困ることも殆ど有りませんでした。
やはりJava
のライブラリをそのまま使えるというのは大きいですし、残るほんの少しの部分も自作で何とかなったり、サポートが追いついてきたりしています。
時間が取れるのであれば当然フルKotlin
化も合わせて行った方がよいですが、順次の導入でも問題無く移行を行っていくことができました。
一方で、設計面の問題はKotlin
化によっても解決できませんでした。
というよりも、設計面で問題が有るコードはそもそもリファクタリングが難しく、Kotlin
化以前に手を入れることが難しかったです。
一方、Kotlin
化によって既存のコードの危険な部分が洗い出された側面も有り、直接的な解決には至らずとも解決の助けにはなったと感じています。
何にせよ、Kotlin
に対してJava
が1/4強という所まで来ることができたので、これを0に近付けていけるようこれからも頑張っていきます。
終わりに
先月にはKotlin 1.4
がリリースされ、Spring Boot
を筆頭に様々なライブラリでのKotlin
サポートが進むなど、Kotlin
を導入するに当たっての障壁は段々下がってきていると感じます。
1年弱やった感想としても、「導入が簡単で、ただ書くだけで安全かつシンプルなコードができる」と、メリットが非常に大きいと感じます。
弊チームでの導入事例が少しでも参考になれば幸いです、ご覧いただきありがとうございました。