💬

関数型は読みやすいコードが書きにくい

2023/12/13に公開

発端

仕事でRxJavaなるものを初めて触ってみました。リアクティブプログラミングと呼ばれるパラダイムの一つで、関数型に影響を受けているようです。その時に「関数型って書くときは気持ちいいけど後から見たら読みにくいこともあるな」と思ったのでまとめます。

素人にはわかりにくいコード

実行に時間がかかる関数A()
  .subscribeOn(別のスレッド)
  .map(Aの実行結果を加工する)
  .observeOn(メインスレッド)
  .subscribe(最終的な結果で何かする)

さて、ここで「Aの実行結果を加工する」と「最終的な結果で何かする」はどのスレッドで実行されるでしょうか?答えは「Aの実行結果を加工する」は別のスレッドで、「最終的な結果で何かする」はメインスレッドです。
僕は今でもこんがらがってます。

混乱する理由

subscribeとsubscribeOnは同じsubscribeという単語を使っていながら、observeOnが呼ばれた場合、subscribeOnで指定されたスレッドはsubscribe時には使用されません。ここが混乱の元でした。なぜobserveという単語ではダメだったのかわかりません...
この理由を一般的に解釈すると「宣言的なコードは呼び出し側で処理をコントロールしないため、スレッドの切り替えのような命令的な処理が隠蔽されるからわかりにくい」ということになると思います。もちろんパラダイムやライブラリに精通していれば問題ないですが、素人に読みにくいコードという時点で可読性は下がっていると思われるので改善すべき点だと考えています。

改善方法

RxJavaの例では明確な改善方法があります。それはCoroutinesを使用することです。

val result = withContext(別のスレッド) {
  実行に時間がかかる関数A().map(Aの実行結果を加工する).await()
}
最終的な結果で何かする // メインスレッド

こんな感じでかけます。awaitをかけているので、多分ブロッキングになるはず...
間違っていたらご指摘ください。
コード量は増えましたが、スレッドの切り替えがブロックによって明確になりました。こちらの方が僕はわかりやすいと思っています。

宣言的パラダイムが常にベストではない

宣言的なコードは、ReactやJetpack ComposeのようにUIを記述する上では有効なことが慣習的に分かっています。しかし、ロジック面においては宣言的である部分と命令的である部分のバランスが重要で、どちらか一辺倒では良くないと考えています。明確な線引きは言語化できていませんが、少なくともスレッドの切り替えについては命令的な方がいいよなと思います。

Discussion