目的
- 「通信」の状態をUIで漏れなく表現する資料を作ること。
対象読者
- (WEB)APIとUIが関係するの仕様書を記述する人
- (WEB)APIとUIの接続部分を実装を行う人
動機
- 通信の結果の「失敗」と、通信の「中断」、処理の「失敗」が仕様書に明確に分離して定義されていない事がある
- 上記の理由により実装者の感覚で実装されることがある
- その結果、UIがユーザーに対して不親切な状態になりうる可能性が高くなる
これらを未然に防ぐために、通信に関する状態を正しく理解し、仕様書の段階で実装にブレが生じない定義を行いたい。
用語定義
用語 | 意味 |
---|---|
通信 | HTTP通信など |
通信とUIの状態の分類
理解を開始するためにはまずは「分ける」ところから始めます。
通信の状態の分類
通信の状態は次の4つの状態があります。
通信状態 | 意味 |
---|---|
待機 (Standby) | 通信の開始前の状態。通信終了から次の通信開始までの状態。 |
実行中 (In Progress) | 通信中の状態。 |
完了 (Complete) | サーバーからの応答を正常に受け付けた状態 |
中断 (Abort) | タイムアウト。ユーザーによるキャンセル |
※ 「中断」はクライアントに対してサーバーからの応答がない状態です。
通信の状態を伝えるために必要なUIの状態の分類
すくなくとも3種類のUIを利用することで通信の状態を伝えられます。
UIの状態 | 意味 |
---|---|
待機 | 初期表示と同義。ユーザーからのイベントを受け付ける表示状態 |
実行中 | 通信が処理中の表示状態 |
通知 | 通信が完了、もしくは中断した場合にユーザーに通知する表示状態 |
通信の状態を伝えるUIの具体例
UIの状態 - 待機
- Buttonなどのユーザーイベントを受け付けられるUIが表示されている
UIの状態 - 実行中
待機表示の無効化
- 処理中はボタンを
disabled
にする
テキストの変更
- 「処理中...」などを表示する
アニメーションによる読み込み表示
- Loader
- Spiner
- Skeleton
- Youtubeの初期ロード画面とか
UIの状態 - 通知
- Dialog
- Snackbar
- Toast
- Blank(空の表示)
「通信が発生するUIの仕様書として必要な定義」の具体例
Buttonをクリックしたときに、通信が発生し、通信中はLoaderが表示され、通信が完了した場合はToastを表示、中断した場合はDialogを表示する
を仕様書で表現するための例を示します。
1. 通信状態に対応したUIの表示状態マトリックス
UI | 通信:待機 | 通信:実行中 | 通信:完了 | 通信:中断 |
---|---|---|---|---|
Button | 表示 | 表示 | 表示 | 表示 |
Loader | 非表示 | 表示 | 非表示 | 非表示 |
Toast | 非表示 | 非表示 | 表示 | 非表示 |
Dialog | 非表示 | 非表示 | 非表示 | 表示 |
:::message この表の意図 通信に関連するUIの表示状態を通信のシーケンスに対応して書くことで、表示するUIと表示しないUIの抜け漏れを防ぐ。 :::
2. 表示されるUIの通信状態別仕様
:::message この項目の意図 表示状態のマトリックスは登場自分物の整理に利用するが、本節は登場人物がどのような振る舞いをするのかを詳細に記述する。 :::
Buttonの仕様
通信の状態 | UIの状態 |
---|---|
待機 | クリックできる状態 |
実行中 | クリックできない状態 |
完了 | クリックできない状態。Toastを閉じることによってクリック可能となる。 |
中断 | クリックできない状態。Dialogを閉じることによってクリック可能となる。 |
Loaderの仕様
- 通信の実行中にのみ表示され、ユーザーからのアクションを防ぐために表示全体を覆う。
- ユーザーがクリックしても反応しない
- 通信が完了したときに非表示にされる
Toastの仕様
- 通信が完了した結果が成功、失敗に関わらず、Reuqestが完了した場合に表示される
- 本UIを閉じることにより、
Button
のdisabled
が解除される。
Dialogの仕様
- 通信が中断された場合に表示される。
- エラーコードを表示し、ユーザーからのフィードバック導線として機能する
- 本UIを閉じることにより、
Button
のdisabled
が解除される。
フローチャート
前節までに示した内容をフローチャートに書き起こすと次のようになります。
「通信が発生するUIの仕様書として必要な定義」は何が記述されている必要があるか
- 通信で利用するUI対する言及がされていること。
- 通信で利用しないUIに対して言及がされていないこと (極めて重要)
- 利用するUIに対する通信のサイクルにおける状態遷移すべてが記述されていること
- 通信処理の「中断」と「完了」が明示的に分類されていること
- 通信の完了の結果の、「失敗」が通信の「中断」と処理の失敗と混同しないこと。
- 通信の完了を通知する(or しない)方法が記されていること。
- 通信の状態を「待機」「実行中」「完了」「中断」などのようにユビキタス言語化し文章にブレがないこと。
このチェック項目は仕様書のレビュー時にでも利用してください。
:::message 仕様書を書くときは「明文化」することが重要です。通信の状態の用語を正確に使うことにより、読者が複数の意味で受け取ることができないように防ぐべきです。 :::
実際に起こり得るパターン
自分が実際に経験したパターンを紹介します。随時追加していきます。
エラーを握りつぶして値を返すパターン
単純な実装例は次の通り(JavaScript)。
try {
const res = await fetch("...");
if (res.ok) { return true; }
return false;
} catch (error) {
return false;
}
問題となるのは、通信の「完了」と「中断」、処理全体の「失敗」が混ざり合っている点です。
try {
const res = await fetch("...");
if (res.ok) { return true; } // 通信の「完了」の結果が成功
return false; // 通信の「完了」の結果が失敗
} catch (error) {
return false; // 通信の「中断」か、処理の失敗どっちか
}
これがなぜ"問題"なのかというと、この通信の処理全体をどのUIに対してマッピングするのか、正しくできない点にあります。例えば、「ユーザーに対して、HTTP通信の結果が500を返しているので、サーバー側のエラーです」と判別する方法が握りつぶされています。
上記のコードを書き換え、意図が分解できる状態にするには次のように書き換えます。
// ※ 関数に切り出したり細かいことは本当はあるけどエッセンスだけ伝われば良い
try {
const res = await fetch("...");
if (isSuccessResponse(res)) {
// この処理が期待する「成功」のレスポンスに対する処理
} else {
// この処理が期待する「失敗」のレスポンスに対する処理
}
} catch (error) {
if (error instanceof TimeoutError) {
// 通信処理がタイムアウト(中断)した場合のエラー
}
throw new Error(error); // 通信以外の処理に失敗したことをThrowする
}
処理の流れを明示的に分類し、「わかった上で」エラーを握りつぶすのであればこういった処理は必要ありません。しかしながら、わかっていない状態でなんとなくエラーを握りつぶしていると仕様変更を受けたときに痛い目を見ます。
:::message このエラーを握りつぶすパターンはBFFをする場合にありがちなパターンです。フロントエンド用のAPIを集約するサーバーの中で、特定のマイクロサービスが死んだ場合に代替値を利用することがあります。これを行うと、「ユーザーに対してサーバーが死んでいるから〜」と判別する方法を失います。 :::
more ...
他に思いつくパターンがあれば追記します。コメントでも歓迎しております!
最後に
通信に関連したUIの仕様はある程度規格化することができます。 通信の状態とそれに対応するUIを用意しておけば仕様書を管理するコストが一気に減ることになり、またユーザビリティの向上にも繋がることと思います。