Poll: do you use finite state machines for UI design/development?
— David K. 🎹 (@DavidKPiano) June 30, 2017
オブジェクトベースのUIモデリングが注目されることにより、UIからオブジェクトを抽出してオブジェクト間の関連や階層など静的な構造を考えることが広まってきました。
特にUIクラス図などは、UIに現れる要素(オブジェクト)と関係を視覚化することでユビキタス言語のように職種を越えた共通言語として利用されています。
参考: UIデザイナーのスキルと、OOUI観点の構造設計 | Goodpatch Blog
一方で、UIモデリングやデータモデリングと比べて見落とされがちな重要なシステム変数として「時間」があります。「どのタイミングで」「何を起点に」というイベントや時間のパラメータはUIの挙動と密接に関係しているにも関わらず、エンジニアだけで設計/実装していることも多いのではないでしょうか。
モデルを考えるときは静的な関係構造だけでなく、動的な振る舞いや状態変化についても同様に注意深く考える必要があります。UIはユーザーのアクションや処理の進行を契機に内部状態を変更し、画面遷移やインタラクションの形で表層に現れます。
実際のところ、動的な振る舞いや状態変化も構造としてモデリングすることができます。難しいものを考えるときはまず構造を可視化してみましょう。目に見える形にすることで自分以外のチームメンバーから議論やフィードバックを得ることができるようになります。何より、思考を外部化することによる自己フィードバックも得られます。今回は状態遷移の可視化の道具として、XStateというJavaScriptのライブラリを紹介します。
XState
XState - JavaScript State Machines and Statecharts
davidkpiano/xstate: State machines and statecharts for the modern web.
ステートマシンや状態遷移を扱えるJavaScriptのライブラリは昔から多々ありますが、このXStateは機能や表現力、プロダクションコードへの組み込みやすさ、ドキュメントの充実や更新の活発さなどどれも備わっていて今後の発展も注目のライブラリです。
XStateは以下のような状態の表現をサポートします。
- Actions (アクション) - onEntry や onExit などのアクションに伴うイベントと状態遷移
- Guards (条件分岐) - 成功/失敗や試行回数のような条件による分岐
- Hierarchy (階層) - 状態の階層構造の導入 ⇒ 状態のモジュール化
- Orthogonality (直交性) - 並列な状態遷移の組み合わせ
- History (履歴) - 状態の履歴保持
状態遷移をJSON形式で定義し、それをそのまま実行可能な状態機械にインスタンス化してコードから呼び出すことができます。以下はAPI呼び出しの振る舞いについて記述したサンプルです。
import { Machine, assign, spawn } from 'xstate';
const fetchMachine = Machine({
id: 'fetch',
context: { attempts: 0 },
initial: 'idle',
states: {
idle: {
on: {
FETCH: {
target: 'pending',
cond: function canFetch() {
return true
},
}
},
},
pending: {
entry: assign({
attempts: ctx => ctx.attempts + 1
}),
after: {
TIMEOUT: 'rejected'
},
on: {
RESOLVE: 'fulfilled',
REJECT: 'rejected'
}
},
fulfilled: {
initial: 'first',
states: {
first: {
on: {
NEXT: 'second'
}
},
second: {
on: {
NEXT: 'third'
}
},
third: {
type: 'final'
}
}
},
rejected: {
entry: assign({
ref: () => spawn(Machine({ initial: 'foo', states: {foo: {}}}))
}),
initial: 'can retry',
states: {
'can retry': {
on: {
'': {
target: 'failure',
cond: 'maxAttempts'
}
}
},
failure: {
on: {
RETRY: undefined,
},
type: 'final'
}
},
on: {
RETRY: 'pending'
}
}
}
}, {
guards: {
maxAttempts: ctx => ctx.attempts >= 5
},
delays: {
TIMEOUT: 2000
}
});
const currentState = 'idle';
const nextState = fetchMachine.transition(currentState, 'FETCH').value;
XState自体は他のフレームワークに依存しないシンプルなAPIが特徴です。その為、React + Hooks や Vue のような独自のエコシスステムを持つフレームワークとも相性良く組み合わせることができます。詳しくは以下のページを参照してください。
XState Visualizer
で、XStateは状態遷移の設計だけでなく実際に動くコードでもあるのですが、今回押したいのは XState Visualizer というセットで提供されている可視化ツールです。
上述したようなJSON形式の状態遷移をリアルタイムに可視化し、さらにマウスクリックでインタラクティブにイベント発火させ実際の遷移を目で見て確認することができます。これはいわば振る舞いにフォーカスしたプロトタイピングで、表層のデザインから切り離したまま状態遷移の単体で議論やフィードバックを得ることができるようになります。
宣言的な記述とインタラクティブなダイアグラム化により、設計から自己フィードバックを得ることができるのはとても重要なことで、ビジュアルも含めた具体的なUIデザインまで作る前に素早く間違いに気づくことができます。
以下はAPI呼び出しのサンプルの実行例です。状態遷移を直接「触れる」ことによって、設計時に想定していないパスに到達してしまう or 到達できないパターンがあることに気がつくことができることでしょう。
VisualizerはGitHub認証を備えていてサイト上での編集をGistに保存することができます。公開情報以外はなかなか保存が難しいかもしれませんが、Visualizerはオープンソース化もされていてローカル環境で実行することも可能です。READMEの手順どおりに簡単にインストールできるのでぜひセットアップしてみてください。
まとめ
視覚表現や実装の詳細に囚われることなく、実行可能なモデルで仕様を素早く検証しましょう。目に見える形になれば、言葉だけで説明するより生産的な議論をすることができます。アプリケーションの動的な側面についても、職種を越えて共通認識を作り上げていきましょう。