Webアプリケーションの構造化
前のページで述べたように、すべてのモジュールはその中核となる型のまわりに組み立てられるべきです。ブログ投稿のWebアプリケーションを作っているとすると、私なら次のようなモジュール構成で作り始めると思います。
Main
Page.Home
Page.Search
Page.Author
Model
型を中心にして、それぞれのページに対応するモジュールがあります。これらのモジュールはElmアーキテクチャに従っており、Model
とinit
、update
、view
、それから必要に応じて作られた補助関数からなります。ここで、モジュールがどんどん長くなり続けるのに任せていますが、そのまま必要な型と関数を追加し続けます。もし自分がたくさんの補助関数を持つカスタム型を作ったことに気付いたら、そのとき初めてそれを別のモジュールへと切り出しても構わないといえるでしょう。
いくつか例を見ていく前に、ある重要な方針について強調しておきたいと思います。
予め計画を練ってはならない
ここで私はPage.Home
モジュールやPage.Search
モジュール、Page.Author
モジュールの未来に対してどんな推測もしていないことに注目してください。複数の箇所から使うことができるようなモジュールを定義しようとはしていませんし、どんな関数も共有しようとはしていません。これは意図的なものです!
以前の私のプロジェクトでは、すべてをどのようにひとつに組み合わせるかという壮大な計画を練っていました。「このページは編集と投稿の閲覧で、どちらも投稿に関係しているから、Post
モジュールが必要だな!」しかし私がアプリケーションを書いたとき、閲覧のページだけが公開日を持つということに気付きました。そして、編集中の記事内容は別の方法で管理して、タブを閉じたときにもデータが失われないように一時的に保存するようにする必要がありました。その結果、ページを開いたときにサーバー上のデータをページに表示する処理を、閲覧ページと編集ページでは少し異なるやり方にしなければなりませんでした。私はついに互いに絡み合う要素すべてをPost
に制御させようとめちゃくちゃにしてしまい、ページは両方ともひどいものになってしまいました。
単にページから作り始めれば、それがまったく同じではなく似ているのだということがわかりやすくなります。これはユーザインターフェイスではよくあることなのです! 投稿の編集と閲覧というのは、どちらも異なる構造、補助関数、JSONデコーダを持ち、結果的にそれぞれ異なる構造を持つEditablePost
型とViewablePost
型になるというのは、うまくいっているように見えます。これらの型をそれぞれの独自のモジュールへと分割したほうがいいほど複雑になっているかもしれませんし、そこまでではないかもしれません! 私はただコードを書いて、何が起こるのかを見るだけです。
これがうまくいくのは、コンパイラが大規模なリファクタリングを本当に容易にしてくれるからです。もし20ファイルにも渡るような大規模な失敗をしたのにあとで気付いたとしても、私はただそれを修正するだけでいいのです。
例
次のオープンソースプロジェクトのディレクトリ構造が参考になります。
カルチャーショック
JavaScriptからやってきた人たちは、JavaScript特有の習慣や思い込み、不安も持ち込んでしまいがちです。それらは状況によっては正しく重要ですが、Elmに引っ越してきたときにかなり重大なトラブルを引き起こすこともあります。
防衛本能
The Life of a Fileで私は、その人がElmへと入ってきたときに迷子にさせてしまうような、JavaScriptの神話的知識について指摘しました。
『ファイルは短くしよう』JavaScriptでは、とてもやっかいなバグを引き起こす、隠れた状態変化が起きやすいです。しかしElmでは、そのようなことは起こりえません!ファイルは2000行の長さになってもいいですし、それでもそのようなバグは起きないのです。『最初から正しいアーキテクチャにしよう』JavaScriptではリファクタリングはとてつもなく危険で、最初からすべて書き直したほうが手っ取り早いでしょう。でもElmでは、リファクタリングは簡単で安全です!20の異なるファイルでも自信を持って変更することができます。これらの『防衛本能』は問題からあなたを守ってくれると思うかもしれませんが、そんな問題はそもそもElmには存在しないのです。これらの神話を頭の片隅に置いたままにしても、防衛本能に振り回されるよりはいいでしょう。でも、ファイルが400行、600行、800行と増えていくのを見た時に、このJavaScriptの神話はとても落ち着かない気持ちにさせてくるのがわかりました。行数の限界をもっと押し上げておくのをお勧めします! どこまで行けるのか試してみてください。コメントヘッダを使ってみたり、補助関数を作ってみましょう。でもそれらをすべてひとつのファイルに入れたままにしておいてください。これは自分自身で経験する価値があります!
MVC
Elmアーキテクチャを見た人のなかには、
Model
とUpdate
、そしてView
というようにモジュールを分割すればいいんだなという直感を抱く人もいます。やめておきましょう!これはコードをわかりにくくし、どの関数をどのモジュールに置けばいいのかという議論が生じてしまいます。
Post.estimatedReadTime
がupdate
関数とview
関数の両方で使われていたときは、何が起こるのでしょうか?これはまったく正当な疑問ですが、viewのほうに所属しているか、それともupdateのほうに所属しているのか、明らかではありません。Utils
モジュールが必要なのでしょうか? もしかしてそれは本当はコントローラか何かではないのでしょうか? それぞれの関数を配置するのは存在論の問題になってしまい、あなたの同僚みんなが異なった見解をもつことになりますので、結果的に目的のコードを見つけ出すのは難しくなります。そもそもestimatedReadTime
って本当は何なのでしょうか? その本質とは何なのでしょうか? 見積(Estimation)でしょうか? Richard1が考えているのはその本質といえるのでしょうか? 時間(Time)のほうでしょうか?もし型を中心にしてモジュールを組み立てれば、このような問題にぶつかることはほとんどなくなります。
Model
とupdate
、view
を含むPage.Home
モジュールがあります。そして補助関数を書きます。やがてPost
型を追加します。estimatedReadTime
関数を追加します。そのうち、Post
型についての補助関数がたくさんできるかもしれませんし、それらを別のモジュールへと切り出す価値があるかもしれません。この話の中で、モジュールの範囲について考え、考えぬく時間を大幅に減らすことができることでしょう。このようにして書かれたコードは、とても読みやすくなることもわかっています。コンポーネント
Reactから来た人たちは、すべてがコンポーネントであると考えています。Elmでは、積極的にコンポーネントを作ろうとするのは災厄の種でしかありません。根本的な問題は、コンポーネントがオブジェクトであるということです。
- components = local state + methods
- local state + methods = objects
Elmを使い始めておいて、「オブジェクトでアプリケーションを作るにはどうすればいいの?」と考えるのは変です。Elmにオブジェクトはありませんから! Elmコミュニティの人たちは、代わりにカスタム型と関数を使うことをお勧めしています。
コンポーネントという用語を考えると、アプリケーションの視覚的デザインに基いてモジュールを作ってしまいがちになります。「サイドバーがあるな、じゃあ
Sidebar
モジュールを作る必要があるな」もっと簡単な方法は、単にviewSidebar
関数を作って必要に応じて何かの引数を渡すというものです。それはおそらくどんな状態も持たないでしょう。ひとつかふたつフィールドがありますか? ではそれはすでに定義されているModel
の中に入れてしまいましょう。もしカスタム型とそれに関連する補助関数がたくさん増えてきたら、そのときそれは別のモジュールへと切り出すといいでしょう!重要なのは、
viewSidebar
関数を書くということは、対応するupdate
とModel
を一緒に作る必要があるということを意味しないということです。そのような直感には従わないでください。必要な補助関数だけを書けばいいのです。
1. 訳注: Richard (Richard Feldman)はこのガイドの原著者Evanの同僚です。Evanと同じ NoRedInk に勤務しており、Elmの発展にも多大な貢献をしています。 ↩