データ指向プログラミングを読んだ。
オブジェクト指向(?)に課題を感じていたと言うよりは良いコードを書くこととそれを周知していくことの難しさを感じていて、もっとシンプルで安全なアーキテクチャは無いかなと思って、関数型っぽいけど既存の言語でも実装できます!という売り文句を見て読んでみた。
概要
カプセル化をやめてデータを独立させれば、関数はより自由に引数をとることができてロジックを集約、拡張することが簡単になるし、ロジックとデータが分かれることでそれぞれを理解するのが簡単になるよね。データをイミュータブルにして変更を管理するクラスを1つにすれば、安全だしデッドロックの問題を回避することができるよね(?)みたいな話だった。
全体感
RubyやJavaなどでこの考え方を使ってWebアプリ開発をやろうとはあまり思えなくて、Reactを使ったフロントエンド開発やClojureなど特定の言語ではうまくいくこともあるだろうなという感じだった。
というのもクエリの結果がインスタンスではなく連想配列とかで返されるCakePHP2を触ったことがあるが、この連想配列がイミュータブルになって変更を管理するクラスが1つになったと仮定してもあんまり良い方向には進まなさそうかなと。
どこでデータが書き換えられているのかを探し当てるのが本当に大変だし、テンプレートファイルがグローバルなヘルパ関数だらけになるしであんまり良い思い出がない…
逆に限定的な用途であれば使えるし、そこで守るべき原則を学べたのは良かった。
例えば検索のクエリをElasticsearchに投げるとき、FormObjectでリクエストを整形し、QueryBuilderを使ってクエリを作成し、gemで発行するようにしているが、この間を流れるデータはインスタンスではなくFreezeしたハッシュやStructを使っている。これによって各オブジェクトが必要な引数を明らかにしつつ、様々な形式の引数を受け取ることができる柔軟な形になっていると思う。
また、SPAでないReactのような特定の用途に絞られている状況では全容が把握しやすく、データがイミュータブルで安全であるという利点を活かすことができると感じた。ただ、昨今のTypeScriptによる型とconstとletの使い分けなんかで十分なんじゃないかな…
コードを書くことに慣れていない人(新入社員とか)に読ませられるかと言われるとかなり怪しくて、リーダブルコードとかCleanCode、ちょうぜつソフトウェア設計入門とかを理解した上で読んでほしい。
これ読んで、そうか!Rubyでもデータ指向プログラミングできるんだ!と思ってSinatraとかで書き始める、みたいなことは起きてほしくない。
内容に関しては413ページからの付録A"データ指向プログラミングの原則"がよくまとまっているので、まずそこを読んで概要を掴むことをおすすめする。
以下は内容に関する個別の感想。
内容に対して
データをコードから独立させることで、全てのフィールドがPublicにし、特定のデータ構造さえ合致していれば関数が使い回せるダックタイピングのような柔軟性を手に入れました!安全性はトレードオフだよね!みたいな感じのことが書かれておりめちゃめちゃびっくりした。大規模なシステム開発ではデータを適切にカプセル化することによって影響範囲を境界内にとどめて分業開発を可能にしてきたのではなかったのか…。データがイミュータブルであれば安全というのは、あくまで状態を持たないことによって参照透過性が担保されるよねという話であって、データを生成するロジックそのものが安全であることを保証してはいないよね、と思うのだが。
確かに入力と出力が単純な形になってテストを書くのは簡単になるのでそこで固めていきましょうね、みたいな話なんだろうか。
前述のデメリットが大きすぎると感じられたのだが、メリットとして上げられていることに納得できる点もあった。
- データ構造とロジックの構造を分けることで複雑さが軽減される
- 引数をJSONSchemaで定義することによって単体テストを自動化しうる
- 検証対象のロジックに必要なデータのみを用意すれば良いのでデバッグが簡単
1に関して、確かにデータ上の関係性はないが、ロジック上の関わりはあるみたいなことはあってそこを分離するのは良い発想だと思った。しかし、本文中でも釈明があったがデータとロジックを分ける、ということは管理に必要なファイルは元のプロジェクトのファイル数をNとするとN~2Nになるということであり、またデータとロジックの間にある依存関係を意図的に無視してもいる。
そのため実際には確認しにいかなければならないファイル数は増えるし、そもそもデータはどの場所からでも改ざんしうるということなので、時折発生する調査タスクなんかを鬱陶しく感じている身からすると絶対にやめてくれと言いたくなる。
2に関してはJSONSchemaは表現力もあるし契約プログラミングっぽくて良いかなと思ったけど、実務上では引数のチェックって正規表現や数値の範囲だけでは収まらず、ユニーク制約をDBに問い合わせたりするわけで、それって関数内でクエリ発行するの?それとも全データを渡してincludes
とかでチェックするの?と。
てかそもそも1本のデータツリーだけを使う例しか出ていないが、あるロジックで元の引数のデータから関連的に遠いところのデータが必要になったとき、Reactのprops drillingみたいなことが起きるんじゃないの?と思う。
また、Stateを管理する場所をStateクラス1つにすることで、コードから状態管理を追い出すのは良いのだが、逆に言えばデータの変更を検知するタイミングがcommit時にしかないので、そこにValidationとCallbackが集中するだろうなというのも辛そう。
データはイミュータブルだから読み取りで差分がおきるようなこともないね!みたいな話もしていたが、全データをメモリ上に載せる気なのか、そういうスナップショット的なものをサポートするようなNoSQL系のDBを使う気なのかも分からなかった。
逆にClojureや他の関数型プログラミング言語がこれらの問題をどういう形で解決しているのか、というのはかなり興味が湧いたので機会があればそれらを読んでみようと思う。