はじめに
みなさんこんにちは!NewsPicks Growthチームの アダチ (@dikxs118)です。
NewsPicksではサーバーサイドの開発にKotlinの導入を推進しており、新規で書くコードは全てKotlin、既存のコードでもJavaで書いてあるものを随時Kotlinに書き換えています。
Kotlinを使うからには、「日本一使いこなせている!」と言えるまでに高められるよう、日々勉強会や、外部カンファレンスへの登壇を積極的に行なっています。
その成果もあって、社内での知見も溜まってきておりKotlinをはじめたての方でも迷うことなく書ける環境が整ってきました。 Kotlin化に対するPRレビューもチームを横断して盛んに行われており、今回はそのレビューの中で得た知見を共有しようと思います。
本題
さて、私は Java -> Kotlin という移行勢なのですが、久方ぶりにJavaを書くと
- Stream API じゃなくてKotlinの標準関数で書きてえ・・
- Kotlinだったら全体的にもっと良い感じに書ける・・
- とにかくKotlinで書きてえ・・
という気持ちになることが多いです。
新規クラスであればKotlinにすれば良いですし、小さなクラスであれば簡単にリファクタできるのですぐKotlinにしてしまうのですが、 肥大なServiceクラスや歴史のある大規模なクラスをKotlin化するのは労力がかかります。
あるタスクで、上記のような大規模なクラスに新規メソッドを追加、もしくは既存メソッドの改修をすることになったとしましょう。
本来のタスクの工数を大幅に超過してまで、大規模なクラスをKotlin化してその動作を保証することは望まれませんし、 Kotlin化は手段に過ぎないので、気持ち的にも中々辛いものがあります。
だけど、、
それでも、、
Kotlinで書きたいんじゃ!
そういったケースではどうするのが良いでしょうか??
NewsPicksではそのようなケースでは、対象メソッドのみを別のKotlinクラスに書き出し、残りのメソッドをJavaクラスへ委譲
または継承
することで開発者の体験を高めています。
今回はその委譲
について詳しく解説していきます。
Javaクラスの一部をKotlinで書く
ここからは、どのように委譲するか、一見勘違いしてしまうハマりポイントがあるので技術的な解説をしていきます。 前提知識としては、委譲
とDI
です。フレームワークは Spring Boot
を利用していますが、他のフレームワークを使っている方や独自のDIコンテナを使っている方は適宜読み換えてください。
今回の使うコードは下記です。
public interface SampleServiceInterface { String sampleMethodA(); String sampleMethodB(); }
@Service public class SampleServiceImplJava implements SampleServiceInterface { @Override public String sampleMethodA() { return "A"; } @Override public String sampleMethodB() { return "B"; } }
このクラスに新たなsampleMethodC
を作り、それをKotlinで実装できるようにしましょう。
先に実装後のコードを記載します。
public interface SampleServiceInterface { String sampleMethodA(); String sampleMethodB(); String sampleMethodC(); }
@Service public class SampleServiceImplJava implements SampleServiceInterface { @Override public String sampleMethodA() { return "A"; } @Override public String sampleMethodB() { return "B"; } @Override @Deprecated() public String sampleMethodC() { throw new UnsupportedOperationException("Not Implemented"); } }
@Service @Primary class SampleServiceImplKt(private val b: SampleServiceInterface) : SampleServiceInterface by b { override fun sampleMethodC(): String { return "C" } }
ポイントは下記2つです。
SampleServiceImplKt
を@Primary
で優先的にDIして、SampleServiceImplJava
に委譲するSampleServiceImplJava
側にはNot Implemented
ということを書いておく(他のファイルで実装してある旨を書いても良いですね)
一つ目のポイントについて@Primary
で優先的にDIしてJavaクラスに委譲するところまでは特に問題ないと思います。
ただ、その先が少しハマりポイントになっていて、SampleServiceImplJava
に委譲すると言いながら、SampleServiceInterface
に委譲しているのでは?SampleServiceInterface
は優先的にSampleServiceImplKt
が解決されるのでは??と思う方もいるかもしれません。
しかしこのクラスが SampleServiceInterface
を解決する段階では、SampleServiceImplKt
はまだ完成されていないので、自動的にSampleServiceImplJava
として解決されて正しく委譲されます。
もちろんSampleServiceImplJava2
みたいな他にも Interfaceを実装しているクラスがある場合は Spring BootはどのクラスをDIするべきか解決できないので@Qualifier("sampleServiceImplJava2") private val b: SampleServiceInterface
といった方法を一例に解決するクラスを明示的に指定してあげてください。
この方法では呼び出し元に一切影響することなく、Kotlinを導入できるのでそういった面でも体験が良いです。
おわりに
今回は、委譲
を使ってJavaのコードの一部をKotlinで書く方法についてまとめさせていただきました!
NewsPicksでは 継承
を使って今回と同様の課題を解決している場面もありますが、その際はコーディングルールなどを定めて実装者が迷わないようにする工夫をしています。その辺りもまたの機会にまとめられたらと思います。
それでは。