なぜサーバーサイドKotlinを導入するのか?

アプリボットでは現在、新規開発のプロダクトでサーバーサイドの言語としてKotlinを導入しています。

これまではJavaを使用してきたので、新しいチャレンジになります。

今回はなぜKotlinを導入するに至ったか、ご紹介します。

新しい言語へのチャレンジ

アプリボットではこれまで、サーバーサイドの言語としてJavaを使用して来ました。

Springをベースとした基盤も作り、資産も充実し安定した実績を残しています。

しかし、Javaは歴史の長い言語でもあるがゆえ、レガシーな部分もあります(安定していて素晴らしい言語であることは前提ですが)。

また、組織としても今ある資産に固執するだけでなく、技術の幅を広げたいという思いがありました。

そこで、横断組織であるA.R.T.を中心に、今後の新規開発でKotlinを使おうと踏み切りました。

現在はもともとあったJavaの資産をベースに、Kotlinでの基盤を作成しています。

モダンな書き方ができる

Kotlinは新しい言語なので、コードがシンプルで安全な、モダンな仕様になっています。

コードがシンプル

例えば、コードがシンプルな点として下記のような点があります。

  • デフォルトがpublic
  • 型推論
  • String Template
  • プロパティ

デフォルトがpublic

Javaではクラスやメソッドのアクセス修飾子を省略すると、デフォルトでパッケージプライベートという扱いになっていました。

Kotlinではそのデフォルトがpublicとなっています。

実用上パッケージプライベートを使うことはほぼないので、デフォルトpublicは無駄なコードも省けて、より直感的になったと思います。

public class Hoge {
    public void execute() {
    }
}
class Hoge {
    fun execute() {
    }
}

型推論

Kotlinでは標準機能として型推論が実装されています。

ただし、変数を定義する時に初期値を設定しない場合は、型の記述は必須になります。

val name = "Applibot"
val name: String
val name // 初期値がなく型推論ができないのでエラー

String Template

Javaでは文字列を + で結合していましたが、Kotlinでは ${} で変数名を囲むことで文字列の中に変数を埋め込めるようになっています。

String name = "Applibot";
String text = "私の所属会社は" + name + "です"
val name = "Applibot"
val text = "私の所属会社は${name}です"

だいぶ可読性が上がりましたね。

プロパティ

JavaではLombokの @Data@Getter @Setter のアノテーションを使ってBeanクラスのGetterやSetterの生成を省略していましたが、Kotlinではこれが標準機能として存在します。

まず、クラスにプロパティだけ定義しておけば、GetterとSetterは自動的に作られます。

class User {
    var id: Int = 0
    var name: String = ""
}

また、data を使用してデータクラスを作成すれば、コンストラクタに書いておくだけでGetterとSetterに加えてtoStringとHashCodeも併せて生成されます。

class User(var id: Int, var name: String)

上記の2つはいずれもプロパティをvarで定義していますが、valで定義した場合はSetterは生成されません。

class User2(val id: Int, var name: String)
val user = User2(1, "Applibot")
user.name = "SGE"
user.id = 10 // この行でコンパイルエラー

安全

KotlinはJavaに比べて主に下記の2つの点で明示的な書き方を求めており、より安全と言えるのではないかと思います。

  • Null安全
  • val、var

Null安全

Null可の型とNull不可の型が明示的に区別されています。

JavaではデフォルトがNull可で、Null不可にする場合はlombokの @NonNull のアノテーションを使用していました。

KotlinではデフォルトがNull不可、Null可にする場合は変数の型名の後に?を明示的に分ける必要があります。

public void execute(@NonNull Integer id, @Nullable String name) {}
fun execute(id: Int, name: String?) {}

Null可型の変数にNull不可型の値を入れることは可能ですが、Null不可型の変数にNull可型の値を入れることはできません。

そのため、下記のようなコードはコンパイルエラーになります。

fun execute(id: Int) {
    val user: User = getUser(id) // この行でコンパイルエラー
    println(user.name)
}
private fun getUser(id: Int): User? {
    if (id == 1) {
        return null
    } else {
        return User(id, "Applibot")
    }
}

また、executeメソッドで受け取る変数userをNull可にした場合、Nullチェックをせずに中のメソッドを呼ぼうとするとコンパイルエラーになります。

fun execute(id: Int) {
    val user: User? = getUser(id)
    println(user.name) // この行でコンパイルエラー
}

この場合は手前にif文でのNullチェックを入れるか、下記のようにuserに ? を付けて安全呼び出しをする必要があります。

println(user?.name)

安全呼び出しをすると、userがnullだった場合はnullを返却し、nullでなかった場合は関数が実行されnameの値が返却されます。

Javaの場合は同じようなコードを書いてもコンパイル時には気付かず、実行時にidに1が入ってきた時にNullPointerExceptionが発生します。

public void execute(Integer id) {
    User user = getUser(id);
    System.out.println(user.getName()); // この行でNullPointerExceptionが発生
}
private User getUser(Integer id) {
    if (id.equals(1)) {
        return null;
    } else {
        return new User(id, "Applibot");
    }
}

lombokを使うことで @NonNull のアノテーションを付ければ同じことは実現できますが、デフォルトがNull可となっているため書き忘れることも起こりがちです。

そのため、言語使用として明示的にNull可、Null不可の型を分けていて、デフォルトNull不可としているKotlinはNullPointerExceptionが発生しにくいと言えます。

安全呼び出しを使う場合も、デフォルトでエラーになる以上基本的にはnullを返却して大丈夫だと意図して書くはずなので、安全性は保たれると思われます。

(nullチェックを書くのが面倒臭いから一旦安全呼び出しにしとこう・・・みたいなことはやらないように注意しましょう)

val、var

KotlinではImmutableな変数とMutableな変数を明示的に分けることが必須になっています。

それぞれImmutableの場合は val Mutableの場合は var を変数名の前に付ける必要があります。

val immutableName = "Applibot"
immutableName = "SGE" // 値を変更しようとするとエラーになる
var mutableName = "Applibot"
mutableName = "SGE" // 値を変更できる

Javaの場合は final を修飾子として付ければImmutableにすることはできましたが、何も付けなければデフォルトでMutableとなるため、全ての変数でfinalの付ける、付けないを明確に考えていた方は少ないのではないかと思います。

Kotlinではそこを必ず明確にしなくてはならないのと、言語としてvalを使うことを推奨していることもあり、誤った値の変更によるバグの発生は減らせます。

Javaからの移行コストが低い

KotlinはもともとJetBrains社が過去のJavaの資産を活かしつつモダンな開発をすることを考えて作られており、Javaとの完全互換になっているJVM言語です。

そのためJavaとの相互運用が可能で、Javaのプロジェクトで一部のコードだけKotlinで書くといったことも可能です。

また、Javaで作られているライブラリ、フレームワークを使うこともできます。

import org.springframework.util.StringUtils
fun execute(text: String) : String? {
    if (StringUtils.isEmpty(text)) { // SpringのStringUtilsを使用
        return null
    }
    return text
}

上記のコードでは、Javaで実装されているSpringのStringUtilsをインポートして呼び出しています。

また、Javaで作られたクラスを継承したり、インタフェースを実装することもできます。

@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
                .mvcMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
    }
}

上記はSpring Securityで用意されているインタフェースを実装している例。

現状使用しているライブラリ、フレームワークは基本的にはJavaのままで使用できます。

そのため、Kotlinへの移行もそれらの部分は除き、まずはもともと自分で実装している層の部分だけ置き換えて行くこともできます。

(アプリボットでもまずはそこから始める予定です)

また、フレームワークをそのまま使えることでアーキテクチャもJavaとほぼ同じ形で実装できるため、Javaエンジニアであれば学習コストも低く、スムーズに対応できると思います。

将来性がある

Androidの公式の開発言語として採用された

昨年の5月に行われたGoogle I/0 2017で、GoogleがAndroid開発の公式言語に加えると発表がありました。

これまでAndroidアプリもJavaで開発されることが基本でしたが、GoogleがKotlinを公式に認めたことは、非常に大きな出来事として話題になりました。

Spring5.0のKotlinサポート

Springが5系から、Kotlinのサポートをしています。

4系までもKotlinで使用することは可能ですが、5系からはコアレベルでの対応も入り、よりKotlinで使いやすくなります。

JavaのフレームワークとしてメジャーなSpringがKotlinに対応し始めたことも、JavaからKotlinへの移行のしやすさを後押ししています。

最後に

ここまで書いてきたように、KotlinはJavaを使っている組織で新しくチャレンジするには進めやすい言語であると考えています。

ただ、まだサードパーティのライブラリを使用する際に不具合があったり、基盤レベルの抽象的な実装をしている部分では、Kotlinで同じように実装しようとした際になかなか情報が出てこないこともあります。

Androidアプリの開発ではよく使われるようになってきましたが、サーバーサイド開発の言語としてまだ日本語での情報が少ない部分もあります。

しかし、新しい技術にチャレンジする時にこういった難しさは付き物です。

それでも今後のアプリボットの技術を成長させ、より大きな効果をもたらしてくれると信じているので、引き続きKotlinの導入に取り組んでいきます。

また、そこで得たノウハウもこのてっくぼっとで公開していこうと思っているので、是非ご覧ください。