DMM.com #1 Advent Calendar 2017 21日目の記事です。
昨日は @szyn さんの Table definition as Codeを実現するflagonを作ったお話でした。
デザインとプログラミングを学んできた上で、最近思っていることをポエム的に記事にしてみました。記事中に出現するコードは雰囲気です。
(便宜上Web開発をベースにしていますが、ネイティブアプリでもGUIを含む以上は同じことが言えると思います。)
従来UIを考えるのはデザイナーとされてきましたが、Webのアプリケーション化が進んだことやJavaScriptの発展によって、見た目ではなく機能にもっと着目してUIの構築をしていく必要性があると考えます。ここで言う構築とは実装だけではなく、どのようにUIが生み出され、動作し、変化(拡張)されていくかを考えるということです。
GUIを機能単位で見る
GUIには必ず機能が存在しています。この「機能」の認識がデザイナーが勘違いしやすいところで、何かインタラクティブなものがないと機能的ではないと判断しがちですが、ただ文字を表示するだけでも立派な「機能」です。言い換えれば、どんなものでも「表示するという機能」だけは標準で備えているのがGUIと言えるかもしれません。
また勘違いしやすい点として、ここで言う機能と見た目は無関係であるということです。つまりHTMLにおけるsectionのような区切りと機能の単位は必ずしも一致しません。
「header」という「機能」なんて存在しないってことです。あるのはheaderの中にある「トップページにリンクされているロゴを表示する機能」や「ユーザーメニューを開閉できるメニューナビゲーション機能」ということです。
この視点をキチンと持つことができれば、オブジェクト指向をGUIの世界へうまく適用させることは難しくないと考えています。
<!-- ページ内には複数の「機能」が存在している -->
<search-box/>
<activity-timeline/>
<general-notificate/>
<yourteam-list/>
<!-- 機能とマークアップは別物 -->
<header> <!-- ← マークアップ -->
<logo/> <!-- ← 機能 -->
<user-menu/> <!-- ← 機能 -->
</header> <!-- ← マークアップ -->
// それぞれはオブジェクトとして定義ができる(例:class糖衣構文を使ってみる)
class SeachBox { ... }
class ActivityTimeline { ... }
class GeneralNotificate { ... }
class YourteamList { ... }
class Logo { ... }
class UserMenu { ... }
よくUIのモジュール化に関して、機能の再利用と見た目の再利用を混同している場面を目にします。本来ここは分けて考えないとお互いの足を引っ張り合ってしまうので注意したいところです。
Atomic Designは見た目の再利用を構造化するためのものなので、各機能を定義した上でその見た目を形作るための構造をどう効率化しようかという点で持ち出した方がいいかもしれません。
複数の機能(モジュール)が連結して完成する
モジュールは原則として1つの機能しか持たないクラスである
前述のとおり、特定の機能をもったモジュールが1つのクラスとして定義ができます。それらを組み合わせることで、WebページやWebアプリケーションとして完成します。
これは単一責任の原則(SRP)を当てはめているベストプラクティスなので、Modulabilityを向上させる鍵です。ここから先は一般的なプログラミングにおけるオブジェクト指向に沿ったデザインパターンをそのまま当てはめることができるはずです。
モジュールは独立して動作し、独立してテストができる
モジュールである以上、Webのどこに配置されても彼自身が独立して動作ができます。トップページに置かれても、ヘッダーに置かれても、同じようにふるまいます。何か別のモジュールやファイルに依存してはいけません。
そのモジュールを利用するための唯一の方法は、インターフェースを知っていることのみです。以下のような実は○○だった、ということが従来の開発ではよくありました。
- 実はライブラリ本体を事前に読み込む必要があった
- 実は別のUIでも使っているCSSファイルを別途読み込む必要があった
- 実は別のUIが**な状態になっている必要があった
このようなことはなく、ただそれをレンダリングするだけで説明通りに動くことが望ましいです。
そして、ここで言う独立とは、UI同士が直接的な依存関係を持っていないことを指しています。具体例をあげると、「Aモジュールのボタンを押下すると、BモジュールのDOMを直接書き換える」といった処理です。これは依存関係があります。
ただし、AモジュールとBモジュールが内部的に抱える処理の中で共通したライブラリやメソッドを利用していることは問題がありません。なぜなら、モジュールを使用する上で内部的な実装は関心ごとではないからです。つまりどうでもいいのです。
独立できているということはつまり、同じインプットを与えたら常に同じアウトプットがあることを証明するためのテストが問題なく実施できることを意味します。
モジュール同士はお互いに協調して存在する
モジュール一つではWebページとして成り立ちません。彼ら自身は独立していますが、お互いに1つのWebを作るという目的のために協調性が求められます。
AモジュールはBモジュールが存在していなくても仕様どおりの動作はするけども、ページ上での目的を達成するためにお互いに協力関係にある、といった具合です。「クリックすることで切り替わるタブUI」と、タブの中に表示させる「**リスト」は直接依存していないが、ページを成り立たせるために協力しています。
直接依存を避けつつも相互に協調させるため、実際にはSTOREやEmitterなどを利用して各モジュールを仲介する実装が理想的だと思います。
CSSのカプセル化が不可欠
オブジェクト指向において、常にグローバルスコープであるCSSは目の上のたんこぶです。GUIである以上、ビジュアル的な正当性も気にしなければいけません。
なるべく機械的な仕組みを使って汚染がないように心がけましょう。
この辺はいま色々とアーキテクチャが存在していると思います。
イベントとインターフェース
GUIである以上、イベント駆動になります。インタラクティブなGUIをつくる以上、イベントとインターフェースは切り離せないと思います。
GUIをモジュール化したときに、それを動作させるイベントは大きく以下の2種類に分かれると思います。
1: render
constructor
mount
などモジュールの利用時にフックされるイベント
GUIがモジュール化している限りは絶対的に存在するイベントです。lifecycle methodとして定義されています。一般的なクラスなどで使うものと大差ありません。
// newする際のconstructor実行
const TopLogo = new Logo('top');
GUIを使用するときにプロパティを指定したり、何か必要な値を渡したりしますが、それはここのイベントのインターフェースに対して渡しているイメージです。
<!-- プロパティの指定も役割は同様 -->
<yourteam-list listData="data" />
2: click
onmouse
blur
などユーザーの動きに合わせて発火するイベント
GUIのインターフェースというとこっちが想像しやすいはずです。
例えばボタンをクリックすることで、イベントが実行されて所定の動作が発火します。言っていることは当たり前のことですが、これはユーザーに対してclickイベントによるインターフェースが提供されているからです。ユーザーはクリックするということさえ知っていれば、処理が実行できます。
onmouseでのインターフェースが提供されていなければ、いくらユーザーがonmouseイベントを実行しても何も起こりません。
普通に実装しているだけで既に出来ていることですが、オブジェクト指向においてはここの理解はとても重要だと思います。
この事実は、どのHTMLタグに対して、どのデバイス(ブラウザ)で、どのようなアクションをすると、何のイベントが発火するか。という情報が非常に重要だというのがわかるかと思います。
React(JSX)のSyntheticEventで挙げられているようなものや、Intersection Observer APIなどで検知させるような仕組みもこっちに当てはまります。
最後に
GUIが最も難しいのは、オブジェクトのインターフェースがやりとりする相手が常に不定形な「なにか」であるということです。それは子供かもしれないし老人かもしれないし、クローラーBOTかもしれないしSeleniumのような自動ツールかもしれないです。
自分で書いたプログラムならインプットを絞れますが、GUIでは一番最初のインプットはどうアクセスされるかがわかりません。(まぁそれが面白いところでもあるんですが)
何も考えずにUIをコンポーネントというあやふやな単位に切り分けよーというだけだと自爆すると思うので、よーく考えるようにしましょう〜。
なんだかとても真面目な記事になってしまったので、最後に知り合いの猫ちゃんの写真を載せます。
かわえぇな…クソ…明日は @masami256 さんです!
DMM.com #1 Advent Calendar 2017
DMM.com #2 Advent Calendar 2017