<-- mermaid -->

私がKotlinを好きになった理由

自己紹介

初めまして、NewsPicksエンジニアの米澤翔です。

2022年の6月に入社し、そこから初めてKotlinを触り始めました。

私は昔軽くJavaを触ったことがあったり、C#をメインにコードを書いていたりしたのですが...

Kotlinはまさに「ちょうど良いパートナー」でした。

kotlinlang.org

今回はそんなKotlinの魅力について、特にWebエンジニアの視点から語らせてください。

Kotlinの魅力

使い手の心理的安全性が高く保たれ、書いたり読んだりする負担が小さい

それでいてJavaの便利なライブラリやフレームワークを利用できる

これにつきると思っています。

null安全がデフォルトとなっている

一番でかいメリット。

例えばJavaで「安全な」コードを書こうとしたら、こんなコードになるのではないでしょうか?

public void save(FreeTrialCouponCode freeTrialCouponCode) {
    if (freeTrialCouponCode == null) {
        throw new IllegalArgumentException("freeTrialCouponCode should not be null");
    } 
    freeTrialCouponCodeDao.save(freeTrialCouponCode);
}

ですが、Kotlinはデフォルトでnullを許容していないので、心配性なコードを書く必要はありません。

fun save(val freeTrialCouponCode: FreeTrialCouponCode) {
    freeTrialCouponCodeDao.save(freeTrialCouponCode);
}

「もしかしたらこの変数、nullなのかも?誤って参照して、null pointerで落ちたらどうしよう」

そんな心配がなくなりますね。

もちろんkotlinでもnullableな変数も使えます。

ですが「null安全がデフォルトの文法におけるnullableな変数」は物理的にnullハンドリングを強制させられるので、null pointer起因のバグをかなり見逃しにくくなります。

Javaの資産が活かせる

KotlinはJVM系の言語です。なのでSpringBootなどのJava製フレームワークの恩恵を受けることができます。

spring.io

「うちもSpringBoot採用したんだけど、.javaファイルなんだよなぁ...」

ご安心ください。.java.kt という、拡張子が異なるソースが共存して同じJVM上で動きます。

.javaファイルと.ktファイルが共存している

初めて見ると非常に奇妙な光景ですが、ちゃんと動きます。

昔に作られたJavaの資産を活かしつつ、新たな実装箇所や既存機能の一部をKotlinで作る・リプレースすることが可能です。

もちろん、KotlinからJavaのコードを実行できますし、なんなら JavaからKotlinのコードを呼ぶことも可能です。

配列操作が簡単で直感的

tech.uzabase.com

弊社エンジニア武藤が執筆した過去の記事と内容が重複しますが、配列操作が簡単に書けます。

例えばJavaで配列の操作を書く場合、こんなコードになるでしょうか。

List<Integer> doubledEvenNumbers = numbers.stream().filter(n -> n % 2 == 0).map(n -> n * 2).collect(Collectors.toList());

Java使いの皆様には悪いのですが、ちょっと野暮ったいように感じます。 (stream とか collect とか、いる?)

一方、Kotlinではこう書けます。

val doubledEvenNumbers = numbers.filter { it % 2 == 0 }.map { it * 2 }

以前私はC#を使っていたのですが、LINQ を彷彿とさせるイケてる文法ですし、明らかに記述量が減っています。

そしてKotlin はイテレータを it で書けるので、Java では

map(n -> n * 2)

と書いていたところをKotlinでは

map { it * 2 }

と、より短く書けます。地味に嬉しい。

文末のセミコロン不要

Javaはこう

freeTrialCouponCodeDao.save(freeTrialCouponCode);

Kotlinはこう

freeTrialCouponCodeDao.save(freeTrialCouponCode)

ちょっとシンプルになって気持ちがいい(体感)

コンストラクタインジェクションが簡単に書ける

テスタビリティを上げるために、Dependency Injectionを使う方も多いのではないでしょうか?

例えばSpringBootなどではコンストラクタインジェクションが推奨されています。

Javaで書くとこんな感じでしょうか。

@RestController
public class ImageController {
    private final BookImageService bookImageService;

    @Autowired
    public ImageController(BookImageService bookImageService) {
        this.bookImageService = bookImageService
    }
}

一方Kotlinでは、フィールドの宣言とコンストラクタが同時に書けちゃいます。

@RestController
class ImageController(
    private val bookImageService: BookImageService
) {
}

楽!

テストフレームワーク「kotest」がイケてる

直感的にして簡潔に記述できるテストフレームワーク「kotest」の存在もありがたいです。

kotest.io

テストコードは例えばこんな感じ

package com.newspicks.util

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

class DateTest : FunSpec({
    context("StringのtoZonedDateTimeへの変換テスト") {
        test("正常にパースできる場合、意図したzonedDateTimeの値となる") {
            val zonedDateTime = "2023-12-03T10:15:30+01:00".toZonedDateTime(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
            zonedDateTime.year shouldBe 2023
            ...
        }

        test("文字列が不正でparseできない場合、例外が発生する") {
            val invalidTimeString = "何かしら不正な値"
            val exception = shouldThrow<DateTimeParseException> {
                invalidTimeString.toZonedDateTime(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
            }
            exception.message shouldBe "..."
        }
    }
})

ネスト形式で記述できるのでテストのスコープがわかりやすいですし、日本語で説明を書いても違和感ないですね。

型を後ろに書く

アラビア語圏の方、すみません。

コードを読むとき、私たちは「左から右」に読みますよね?

試しに↓を読んでみましょう。

public void verifyName(NameVerification model) {
    ...
    NameVerificationStatus status = (user.isNameVerified()) ? NameVerificationStatus.UPDATE : NameVerificationStatus.NEW;
    saveVerificationStatusHistory(user.getId(), NameVerificationStatus.CONFIRMED, null);
    ...
}

public Optional<StreamFile> getCertificate(Integer userId) {
    return certificateRepository.get(userId);
}

左から読んでる途中でこう思いませんでしたか?

「型名よりも変数名・メソッド名の方に興味あるから、さっさとしろ」

往々にして、私たちがまず気にしているのは「型の名前」じゃなくて「変数の名前・メソッドの名前」ではないでしょうか。

であるならば、左から読んだ時に変数名・メソッド名がまず先に表示される方が読みやすいと思うのは、私だけでしょうか。

fun verifyName(model: NameVerification?) {
    ....
    val status = if (user.isNameVerified) NameVerificationStatus.UPDATE else NameVerificationStatus.NEW
    saveVerificationStatusHistory(user.id, NameVerificationStatus.CONFIRMED, null)
    ....
}

fun getCertificate(userId: Int?): StreamFile? {
    return certificateRepository[userId]
}

これは私の想像ですが、同じような文法の Golangrustを設計したエンジニアも、同じことを考えていたのではないでしょうか?

メソッドがデフォルトでpublicであり、voidを書く必要がない

「メソッドを追加する場合、それはpublicであることが多い」

ある程度オブジェクト思考の言語でコードを書いたことのある方なら、ご納得いただけるでしょう。

public class FreeTrialCouponCodeRepository {
    ...
    public void save(FreeTrialCouponCode freeTrialCouponCode) {
        freeTrialCouponCodeDao.save(freeTrialCouponCode);
    }

    public void saveAll(List<FreeTrialCouponCode> freeTrialCouponCodeList) {
        freeTrialCouponCodeList.forEach(this::save);
    }

    public void update(FreeTrialCouponCode freeTrialCouponCode) {
        freeTrialCouponCodeDao.merge(freeTrialCouponCode);
    }
}

しからば人は思うはず。

「メソッド読むたび、高確率でpublicという単語を毎回読まされるの、面倒だなぁ」

...私だけでしょうか。

kotlinならpublicなメソッドに、public の記述は不要なんです。

なんなら void も不要です。

class FreeTrialCouponCodeRepository {
   ...
    fun save(val freeTrialCouponCode: FreeTrialCouponCode) {
        freeTrialCouponCodeDao.save(freeTrialCouponCode);
    }

    fun saveAll(val freeTrialCouponCodeList: List<FreeTrialCouponCode>) {
        freeTrialCouponCodeList.forEach(this::save);
    }

   fun update(val freeTrialCouponCode: FreeTrialCouponCode) {
        freeTrialCouponCodeDao.merge(freeTrialCouponCode);
    }
}

左から読み始めた時、メソッド名に目が辿り着くまでの時間が圧倒的に短いですね。

publicvoid という単語を書かなくなった分、一行の圧迫感も減りました。

IntelliJで開発ができる

最強のJVM開発IDE IntelliJ で開発できます。

IntelliJ IDEA - Java と Kotlin の最先端 IDE

改善の余地があるコードは指摘してくれて、ショートカット一つでリファクタリング完了です。 各種プラグインも充実。

しかも、.java から .kt ファイルへのコンバート機能まで付いています。

.javaファイルを右クリックして表示されるメニューから、.ktファイルへの変換が可能

それでいて Community Edition なら無料。 こんな贅沢があって良いのだろうか。

最後に

さんざん褒めちぎりましたが、もちろんデメリットもあります。

例えばGo言語と比べると、シンプルさやDocker Imageのサイズでひけを取るでしょう。

rustには処理速度で敵わないでしょう。

また、Java本体の方の機能が充実してくれば、kotlin自体が廃れる可能性もあります。

ですが、歴史と実績のあるJavaライブラリ・フレームワークの恩恵を受けつつ、それをシンプルかつ安全に使えるのは、 Kotlinならではの魅力ではないでしょうか?

もしKotlinに興味を持たれた方は、アンドロイドアプリやSpringBootのAPIをkotlinで書くところから始めてみると、その恩恵を感じやすいかと思います。

Kotlinの魅力、ぜひ味わってみてください!

Page top