ChatWork Creator's Note

ビジネスチャット「チャットワーク」のエンジニアとデザイナーのブログです。

チャットワーク と Kotlin

f:id:cw-miyashita:20171201004201p:plain こんにちは。 @ryugoo_ です。 Android エンジニアをしています。今回は弊社 Android アプリ開発における Kotlin への取り組みについてお話しさせていただきます。

はじめに

チャットワークの Android アプリは 2014 年末に Titanium Mobile から Java ネイティブアプリに移行しました。早 3 年が経過しようとしていますが、最近は Kotlin 化を進めています。 Kotlin とは何かということは、様々なところで言及されているのでここでは詳しく述べませんが、 Android Studio のベースである IntelliJ の開発元である JetBrains が主体となって開発を行っている JVM 言語です。

Kotlin は 2017 年の Google I/O でサプライズ的に Android アプリ開発のファーストクラス言語に昇格しました。それ以前も Kotlin を使って Android アプリを作ることはできましたし、多くのアプリで Kotlin が採用されていましたが、非公式であるところが導入の障壁となっていました。弊社でも以前から導入を検討していましたが、突然開発に使えなくなる可能性が否定できなかったり、エンジニア採用面の不安からも導入を躊躇していました。

しかし、公式にサポートされたことでこれらの問題は解消され、発表翌日から Kotlin の導入をスタートしました!

既存のコードベースに Kotlin を入れる

約 3 年間積み上げてきたコードベースに対して、 Kotlin をどこから入れるかを最初に悩みました。この時は幸い、新しく作ろうとしている DialogFragment があったため、これを Kotlin で書くことにしました。

しかし、私を含めて Android 開発チームは 3 名で、そのうち、 Kotlin を書いたことがあるのは私 1 人……。Kotlin を導入することはチームの合意がありましたが、 Kotlin 自体をチーム全体で書けるようにする必要があります。そこで、最初に提出した Pull Request では、 Kotlin のイディオムについての説明コメントを丁寧に入れていくことにしました。

f:id:cw-miyashita:20171130224641p:plain

多少正しくなくても、 Java における ○○ が Kotlin では ×× なんだと分かるように説明をしていき、チャット上でクイズを出しながら理解ができたかを確認していきました。これを繰り返しながら、少しずつ Kotlin の導入を進めていきました。

Kotlin 導入のメリット

この記事を書いている時点で Java : Kotlin の比率は約 55 : 45 です。半分近くを Kotlin に置き換えることができました。既存のコードを Kotlin 化するメリットは大きいと私は思っています。その理由は、

  1. Java と比較して 1 ファイルあたりコードの行数が 50% 〜 80% になる
  2. Nullable / Non-null をハッキリと意識するようになるので、 ( ´∀`)<ぬるぽ と出会う確率が低くなる
  3. 言語機能 / 標準ライブラリが充実しているので、サードパーティのライブラリに頼らずにコードを書ける

というものがあります。 Kotlin でコードを書くとハッキリとコードの行数が減るのですが、無理矢理に減るのではなく、プログラムとしてスッキリと読みやすく、意図を追いやすい形のまま減ってくれます。

Java

public static User newInstance(@NonNull Cursor cursor) {
    User user = new User();
    user.setUserId(cursor.getLong(0));
    user.serName(cursor.getString(1));
    return user;
}

Kotlin

fun newInstance(cursor: Cursor) = User().apply {
    userId = cursor.getLong(0)
    name = cursor.getString(1)
}

※ 実際に使われているコードではありません

2 つめの Nullable / Non-null をハッキリ意識するようになることに関しては、Kotlin 自体の言語機能でこれらが明確になることと、 Android SDKAPI を Kotlin 側から呼び出すときに Platform Types を意識するようになるからです。 Platform Types は T! として表現され、 T または T? のどちらかである型です。

Android SDKJava で書かれていて、ほとんどの場合は返り値が Platform Types になります。 Annotations Support Library の @Nullable または @NonNull が付いていればそれぞれ T? / T として扱うことができますが、それ以外は T! となります。この場合は、

  1. JavaDoc を見て null が返ることが明示されている、または不明な場合は T? にする
  2. Android SDK のソースを見て、 null が返らないことが明確な場合は T にする

という基準を設けて型を決定しています。迷ったら T? です。

Android SDK のコードを見るときは、プロジェクトが使用中の targetSdkVersion に対応する Sources が提供されていれば Android Studio でコードジャンプしてソースを読めますが、それ以外の場合は Google Chrome の Android SDK Search 拡張を使うとコードを追いやすくなります。

Google Chrome の Omnibox に ad<tab> を打つことで Android SDK の検索を行え、該当のドキュメントにジャンプできます。ジャンプ先のドキュメントにはソースへのリンクが付くようになるので、そこでソースを読むことができます。

f:id:cw-miyashita:20171130224811p:plain f:id:cw-miyashita:20171130224833p:plain

例えば LayoutInflater#from明確に Non-null であると分かるため、この場合は T として扱っています。

3 つめに Kotlin は実用的な言語機能 / 標準ライブラリが充実していて、サードパーティのライブラリに頼らなくても表現豊かにコードを書くことができます。

例えば、Android Studio 3.0 / Android Gradle Plugin 3.0 からは Java 8 のシンタックスを使って Android アプリを書けるようになりました (それ以前では Retrolambda を使うことで Java 8 のシンタックスを使えました) が、 Java 8 の標準ライブラリの全てを使うことはできません。

Stream API や Optional API といった強力な標準ライブラリがそのままでは使えないので、 Lightweight Stream API のようなバックポートライブラリでカバーする必要がありました。 Kotlin では標準ライブラリとしてこうしたものをカバーする API が用意されているので、大変便利です。

Java

List<String> texts = Arrays.asList("1", "2", "3");
long count = Stream.of(texts)
    .map(Integer::valueOf)
    .filter(num -> num % 2 == 0)
    .count(); // 1

Kotlin

val texts = arrayOf("1", "2", "3")
val count = texts
    .map { it.toInt() }
    .count { it % 2 == 0 }

これからの取り組み

チャットワークの Android アプリでは可能な限り Kotlin に置き換えを進めつつ、新規に追加するコードは Kotlin を第 1 言語にします。発表されたばかりの Kotlin 1.2 も既に対応が完了していて、次回のリリース分から適用される予定です!今後も Kotlin の進化を追いつつ、すぐに対応できる体制を整えて、楽しく開発を行っていきたいですね:)

それでは、ハッピー Kotlin !