Runner in the High

技術のことをかくこころみ

Elmのモジュール分割における区分軸の観点

Elmアプリケーションにおいてモジュールを作る際に、モジュールへ機能をどう凝集させるかに関して。

機能による区分

かつてElmにおいて一般的だった分割方針。グレーの部分がひとつのモジュールを表している。

ModelとUpdate, Viewなどをすべて別のモジュールへ分割し、Userなどのネームスペース配下に配置する形で、Railsなどのフルスタックフレームワークディレクトリ分けと非常に近い。

f:id:IzumiSy:20200517012328p:plain
機能による区分

この分割方針の大きな欠点は、モジュールを不必要な単位で分けすぎていることで結局すべてのデータをモジュールから公開しなければならなくなっている点にある。

ViewモジュールやUpdateモジュールは、実装のためにModelモジュールの内部実装を知る必要がある。その結果、公開された内部実装へ知る必要のない他のモジュールが依存する可能性がでてしまう。

Elmにおいてモジュールを作る理由というのは公開したいものと公開したくないものを分けることであり、他モジュールからの依存をコントロールし変更に強い「インターフェースに対する依存」を生み出すこと。Opaque Typeのような手法も、まさにこれが理由になる。

モジュールの分け方が不適切な単位で小さすぎると、実装のためにモジュール内部をすべてexposingしないといけなくなってしまいモジュールの機能が存在意義を失ってしまう。このような「機能による区分」が持つ問題への反省を踏まえて、現在のElmにおいては以下の「ドメインによる区分」の方針でモジュール分割が行われる。

ドメインによる区分

「機能による区分」で行われていた分割とは異なり、モジュールはUser, Article, Commentのようなアプリケーションにおけるドメインの単位のみで作られる。

最も卑近な例では、リチャード・フェルドマンによるrtfeldman/elm-spa-exampleがモジュール分割にこの区分を採用している。

f:id:IzumiSy:20200517012408p:plain
ドメインによる区分

このモジュール分割が行われるのは、例えば「画面」がそうである。Elmアプリケーションにおいて、User画面、Article画面、Comment画面はそれぞれがひとつづつモジュール化される。

画面をモジュール化する理由は、画面を利用するMainなどのモジュールは各画面の実装詳細を知る必要がないからだ。これらのモジュールを利用するMainモジュールは、各画面モジュールが提供する関数を用いてモデルの更新をしたりHTMLを作ればよいだけであり、画面モジュール内部の実装詳細を知る必要はない。以上の理由から、画面単位のモジュールが作られる。

このような単位でモジュールを作ることで、「機能による区分」では過剰に公開されていたModelやUpdateの詳細が外部から適切にカプセル化され、意図しない依存が生まれにくい状態になっている。

余談: 共通化によって生まれる孤児モジュール

プログラミングをしている人間であればおなじみのDRY(共通化)は一般的には「機能による区分」で行われる事が多く、上記の「ドメインによる区分」の軸とはモジュールの設計の観点で直行することになる。

例えば「すべての画面モジュールにおいて"View"や"Update"の軸で共通化した実装を用意したい」などのケースがこれに当たる。

このような区分軸の直行が起こるケースでは、各ドメインを横断して仕様の分析を行い、その共通化が単なる「コード上の共通化なのかそれとも「概念としての抽象化」なのかを見極める必要が出てくる。

しかしながら、実際の事実としてもっとも起きがちなのは、このようなタイミングでcommonやutil, helperなどの「どこにも属さない概念」爆誕することである。彼らはコードとしては共通化されたが、概念上の所属の観点からは考えることを放棄された孤児モジュールである。

彼ら孤児モジュールは、ある意味「所属待ち」を行っている状態でもあるため、継続的にリファクタリングなどのタイミングでどこかのドメインに所属させたり、新しい抽象概念に昇格させたりしていくことが必要である。孤児モジュールがコードベースにおけるマジョリティーになってしまうと、アプリケーションにおけるドメインの概念が揺らいでしまう。