🗂

Kotlinを扱う際の心構え

2024/06/22に公開

※大事なことなので2回書きました

様々な分離がしやすい

様々な分離をすることで、どこでデータが変更されているかがわかりやすくなり、デバッグしやすくなります。

イミュータブルとミュータブル、再代入可能の明示化

変数宣言に関しては、varとvalで簡潔に再代入可能かを明示化することができます。
Javaではfinal修飾子を付与しないといけません。
ミュータブルなデータ型に関してはMutableナントカとMutableというPrefixがついていることが多いです。

副作用のある処理とそうでない処理の分離

イミュータブルとミュータブルが明示的となっているため、メソッドシグネチャを見ただけで、副作用がありそうかどうかの判断がしやすくなります。
Javaでは、getterのようなメソッドでも()がついていて、メソッド形式でしたが、Kotlinではプロパティアクセスが使えるため、こういった点でも副作用があるかどうかの判断がしやすくなります。
Kotlinの参考になったScalaでも、副作用を伴わない処理ではメソッドではカッコ()をつけない慣習があります
RubyでRubocopを使っていると、副作用があるメソッドをhoge()と呼ぶようにするとhogeへ置き換えられてしまうのは、気に食わないですが、設定でなんとかなるんでしょうか…。
話は戻り、Kotlinの話ですが、クエリ実行する箇所だけメソッドチェーンにしないORMというのも出てきました。ScalaのSlickとScalikeJDBC、KotlinのKomapperがそうです。
クエリ実行は副作用を伴ったり、重い処理になったりするため、分離したくなるところでもあります。

データと処理の分離

data classがあり、データを書く場所なのか、処理を書く場所なのかが明示的になります。
また、Javaと違ってクラスを書かなくても関数を書けるため、これもデータと処理の分離に寄与しています。

KotlinはIDEの力を使って設計していく言語

普段、Rubyを書いている人へ、Kotlinを書かせてみて驚いたことがありますが、コンパイルが通らない箇所がかなり多い状態で、「今の段階で、方針がズレていないかレビューをしてください」と言われたことです。
IDEを使わずに、vimかemacsかサクラエディターかなにかでコーディングをしていたのか、今となっては分からないですが、コンパイルが通っていないということは、それぞれ期待しているインターフェイスを想定できていない又は想定が間違っているということなので、設計ができていないということになります。
設計をする即ち実装が完了するといったことが可能なのが、IDEと静的型付け言語を使って実装するということだと思います。
逆にRubyを書いている人というのは、おそらく記憶力やドキュメントや他のコード等を参考に、REPL等で実行しながら、オブジェクトやメソッドを探し当てていくような作業でプログラミングをしているのだと思います。
動的型付け言語は、私には※型を心のなかに記憶できるほど頭が良いわけではないというのもあり使いこなせませんでしたが、Ruby等の動的型付け言語では、設計に凝って独自の機能を作ることを避けてドキュメント通りの使い方をするか(「設定より規約」もこれに属すと思われる)、ちゃんとドキュメント化するのが鉄則なのかなあ?という気がしています。

Ruby参考記事:
https://x.com/yukihiro_matz/status/1066980158429552640
https://qiita.com/yatemmma/items/3597a0e20556fb846d3f

インターフェイスが厳格なため、開発者は、インターフェイス通りに実装することだけに集中できる

Javaもそうですが、インターフェイス通りに実装されていれば、処理の呼び出しができることが保証されます。
他サービスとの連携がある場合でも、Javaの世界の外からくる値が、インターフェイス通りの値でなければ、それ以上、処理ができないようになっています。このため、開発者は、定義した型を前提に実装することに集中できます。

Dockerを使う意義は他の言語に比べて小さい

どうも、Rubyを使う人は、RubyのライブラリがOSにインストールされているものに依存していることが多いためか、Dockerを使って開発したがる人が多いような気がします(github-actions上でのJavaのビルドやテストもDockerを使いたがったりなど)。
なかには、Dockerは本番環境でもある程度動くことをある程度保証してくれる魔法の道具とでも考えている人もいるように感じます。そもそもDockerは、virtualizationの技術です。
Kotlinの基盤技術であるJavaは、「Write once, run anywhere」と言われるように、コンパイル後のバイナリが1つあれば、JVM(Java virtual machine)という仕組みによって、同じバイナリを様々な環境で動かすことができます。
ちなみに、テストでのDockerとの連携としては、Testcontainersなど、Java/Kotlin/ScalaのテストからRDBMS等で使うDockerを起動することができるライブラリもあります。これにより、Dockerを意識せずに開発することもできます。

Anyや!!は型チェック無視ではない

Kotlinは、実行時にもその型であることが保証されます。
TypeScriptのanyは、実質、型チェックを無視するものでしたが、KotlinのAnyは、JavaのObjectと同じようにすべての型の親玉です。
また、!!は、nullチェックを無視するものではなく、nullの場合は例外を投げるものです。

Javaのライブラリを使う上での注意点

プログラミングパラダイムの違い

Javaは、ここ最近のイミュータブルなデータを扱う流れ以前からある言語なためか、ミュータブルにデータを扱うことが前提のコードがあったりします。
Spring FrameworkでField InjectionよりConstructor Injectionが推奨されるようになりましたが、昔はField Injectionでみんなやっていたような気がします。
ミュータブルな操作を強いられるライブラリを使う場合、設計思想が特定の場所だけ違うということになります。

Nullableの扱い

Javaのメソッドは、Kotlinだと、Nullableと同等の扱いになります。
そのため、データのマッピングが必要な場合、Kotlinで書かれたものか、Kotlinのラッパーライブラリを使うことが望ましいです。

Kotlinで書かれたものより、簡潔さがなくなることがある。

Kotlinにはinfix記法や、関数型リテラルをレシーバー付きで宣言(Hoge.() -> Unitというような記法)など、完結に書く機能があるが、それがJavaのライブラリでは提供されていない。
そのため、Kotlinのライブラリよりは劣るという見方をすることもできます。

KSPとKAPTが混在するプロジェクトの場合、干渉することがある。

コンパイル前の処理としてkaptkotlinとkspkotlinが動作しますが、それぞれが干渉する事例がちょくちょくあるみたいです。
トラブル防止のために、kspを使うのか、kaptを使うのか、設計を統一したほうが良いと思います。

GitHubで編集を提案

Discussion