MicroAd Developers Blog

マイクロアドのエンジニアブログです。インフラ、開発、分析について発信していきます。

プロダクトにKotlinを導入して1年弱経ったので振り返る

アプリケーションエンジニアの宮田です。

自分の所属しているチームでは2019年10月から、既存のJava8で書かれたSpringBootアプリケーションへKotlinの導入を始めました。
今回のブログでは、Kotlinを導入して良かったこと、導入に当たって直面した課題、1年弱やった感想についてまとめます。

Kotlinを導入して良かったこと

安心感を持ってコードを書ける

個人的には「特に意識しなくてもnull安全・immutableなコードが簡潔に書けるようサポートが行き届いていて、その中でコードを書ける」というのがKotlinを導入して最も良かったことです。

やろうと思えばJavaでもKotlinと同じくらい堅牢なコードは書けますが、そのために必要とされる知識や努力は大きく、コードも煩雑になります。
また、「コード全体が基本的に安全な方向に作られている」という安心感はJavaに無いものでした。

方々で「Kotlinは何も考えず入れるだけでよくなる」と言われているのを観測していますが、全てが揃っているからこそだと思います。

シンプルかつクリーンに書ける

KotlinJavaに比べシンタックスがシンプルで、更に様々な処理がシンプルに書けるような記法が用意されています。
この記法を覚える難易度そのものは高いですが、使いこなせば使いこなすほどコードがシンプルに書けます。

また、スコープ関数もKotlinの強力な機能の一つで、可読性を損なわず、かつ余計な一時変数によってスコープを汚すことなくコードを書くことができます。

記法のシンプルさは単純に生産性に繋がるだけでなく、そのシンプルさから来る可読性の高さは保守性にも大きく貢献していました。

導入に当たって直面した課題

このように良さが多々あるKotlinですが、既存のJavaプロジェクトに導入するということも有り、導入に当たって幾つかの課題に直面しました。

導入順序としては、「新規部分はKotlinを使い、既存部分も徐々にKotlin化していく」という形を取ったため、解決策もそれを前提としている点はご注意下さい。

Java向けのリフレクションを用いるライブラリと相性が悪い

Javaでは「インスタンス生成 -> setter経由でフィールド初期化」という順序でのオブジェクト初期化が一般的で、リフレクションを用いるようなライブラリもそれを前提にしていることが殆どです。
一方、Kotlinでは「コンストラクタやファクトリーメソッドで全フィールドを初期化」という形が一般的で、そのままの状態ではJava向けのリフレクションを用いるライブラリを利用できません。

プロジェクトでは特にModelMapperBeanPropertyRowMapperを用いている部分をどうするかが問題となりました。

簡単に検討した選択肢としては以下が有りました。

  1. 必要な部分だけJavaコードを残す
  2. kotlin-noargプラグインを用い、既存ライブラリが機能するようにする
  3. ライブラリを探す
  4. 手書きにする

1と2はそれなりに不都合無くできるものの、実行時エラーの危険性が残ることから望ましくなく、3はよいライブラリが見つからず、4はJavaから乗り換える利点が減ることから避けたいという話になりました。

解決手段

諸々の検討の末、マッピングツールを自作することで解決しました。
Kotlinのリフレクションによる関数呼び出しは非常に洗練されており、細かい所を含めても3日程度で実装を完了できました。
以下は実際にBeanPropertyRowMapperを置き換えるために作成したツールの技術ブログです。

このツールは、高速化や機能改善を行った上でKRowMapperというOSSとして公開しており、実際にプロジェクトのKotlin向けRowMapperは全てこれによって置き換えが完了しています。

また、ModelMapperに関しても置き換え用ライブラリを作成しましたが、チーム内で「別にマッピングツール無くても良いのでは?」という雰囲気が出てきたため、まだプロダクションコードへの導入は行っていません。

問題として残った部分

複雑な継承関係を持つオブジェクトに関してはModelMapperへの依存が大きく、一気に置き換え切ることもできなかったため、レスポンスオブジェクトに関してはJavaのまま残っているコードが有ります。

補足: POSTされたデータの受け取りに関して

「DBからの取得が問題になるんだからPOSTされたデータの受け取りにも問題が出るんじゃないか」という疑問が有るかもしれませんが、これに関してはプロジェクトでJSONのパースに用いているJacksonKotlinを強力にサポートしているため、特に問題にはなりませんでした。

lombokで生成したgetter/setterにアクセスできない

Kotlin導入前のJavaプロジェクトでは、省力化のため、lombokを導入してPOJOgetter/setterをやっていました。
一方、コンパイル順序の問題から、Kotlinからはlombokで生成するgetter/setterにアクセスできずコンパイルが通らないという問題が出ました。

選択肢としては以下の3つを検討しました。

  1. ビルド設定を見直す
  2. 全てKotlin化する
  3. 必要な部分だけdelombokしてgetter/setterを書く

解決手段

最終的に、問題になる部分が少なかったことから、基本的に全てKotlin化して、本当にどうしようもない部分はdelombokしてgetter/setterを書く形での解決としました。
導入当初はそれなりに大きな問題でしたが、DBからの取得をKotlinで書くことができるようになり、Kotlin化も進んだことによって問題が小さくなる形で消えていきました。

Javaとの繋ぎ込みでバグを作りこんでしまう

冒頭で書いた通り、KotlinJavaに比べ簡単なシンタックスで安全なコードが書けるようになっています。
一方、これに慣れた状態でJavaを書くことで、時折バグを作りこんでしまうことが有りました。

解決手段はKotlin化を進める位しか無いというのも厄介な部分です。

以下2つは特に頻出した間違いです。

nullabilityを勘違いする

どうしてもJavaを残さざるを得なかった部分を触っていた時、nullabilityを勘違いしてエラーを作りこんでしました。
このパターンは、コード内で複雑な扱いをしていたり、特にフロントからのPOST構造を勘違いした時に発生していました。

比較方法を勘違いする

プロジェクトでは、Javaコードでの比較で==, equals, Objects.equalsの3つを状況によって使い分けていました。
一方、Kotlinでは、==で大体の比較を書くことができます。

このため、Java==だけで比較を書いてしまいかけるということが何度か有りました。

1年弱やった感想

1年弱に渡りKotlinへの置き換えを進めてきましたが、やはりKotlinのシンタックスの恩恵は非常に大きく、バグを減らしながら質の高いコードを書くことができると感じます。
繰り返しになりますが、「ただ書くだけで堅牢に書いたJavaと同じくらい安全で、かつシンプルに書くことができる」というのは非常に生産性を高めることができます。

導入に当たって困る場面も思っていたほどは有りませんでした。

まず、業務としてKotlinを経験した人間無しでの導入開始でしたが、「書けない」ということで困ることは殆ど有りませんでした。
ただ、背景としてJava8Optionalstream APIlombokに関してはプロジェクトに導入していたので、それらの経験は理解の助けになっていたとは思います。

ライブラリの不足や互換性で困ることも殆ど有りませんでした。
やはりJavaのライブラリをそのまま使えるというのは大きいですし、残るほんの少しの部分も自作で何とかなったり、サポートが追いついてきたりしています。

時間が取れるのであれば当然フルKotlin化も合わせて行った方がよいですが、順次の導入でも問題無く移行を行っていくことができました。

一方で、設計面の問題はKotlin化によっても解決できませんでした。
というよりも、設計面で問題が有るコードはそもそもリファクタリングが難しく、Kotlin化以前に手を入れることが難しかったです。
一方、Kotlin化によって既存のコードの危険な部分が洗い出された側面も有り、直接的な解決には至らずとも解決の助けにはなったと感じています。

何にせよ、Kotlinに対してJavaが1/4強という所まで来ることができたので、これを0に近付けていけるようこれからも頑張っていきます。
f:id:miyata_kiori:20200911183515p:plain

終わりに

先月にはKotlin 1.4がリリースされ、Spring Bootを筆頭に様々なライブラリでのKotlinサポートが進むなど、Kotlinを導入するに当たっての障壁は段々下がってきていると感じます。
1年弱やった感想としても、「導入が簡単で、ただ書くだけで安全かつシンプルなコードができる」と、メリットが非常に大きいと感じます。

弊チームでの導入事例が少しでも参考になれば幸いです、ご覧いただきありがとうございました。