The note to squeeze Wix
バックエンドで投げた例外の理由は、画面に届かない
会員のキャンセル画面が、これで止まっていた。
ご注文を読み込めませんでした。
[詳細] getMyOrders: Error: Unable to handle the requestこの Unable to handle the request は、バックエンドが投げた例外の文面ではない。投げた内容が何であれ、フロントエンドにはこの形で届く。原因は置き換えられて消えている。
このときの実際の原因は、キャンセル記録のコレクションがまだ作られていなかったことだった。ダッシュボードの CMS で不在を確認して初めて分かった。画面からは、そこへ辿り着く手がかりが1つも出ていない。
投げるのをやめて、返す
対処は単純で、理由を届けたい失敗は例外にしない。結果オブジェクトを返す。
{ ok: boolean, rows: object[], cancelDisabled: boolean, notice: string, detail: string }notice は利用者に見せる文言、detail は原因を書く場所である。段階ごとに detail を埋めるので、会員の取得で落ちたのか、注文の取得で落ちたのか、記録の取得で落ちたのかが画面で分かる。
例外そのものは捨てない。バックエンドで console.error に元の内容を残す。届かないのはフロントエンドまでの経路であって、ログには出る。
読めないものと、止めるべきものを分ける
この画面には、もう1つ設計の問題があった。注文と、キャンセル記録を並列に読んでいて、記録側の例外が一覧全体を道連れにしていた。
注文ごと消えると、利用者には注文が無かったことに見える。実際には注文はあり、読めなかったのは「どれが取り消し済みか」の情報だけである。だから一覧は出して、取り消し操作だけを止めることにした。何が取り消し済みか分からないまま操作させると、二重返金や、取り消したのに集計に載るといった、取り返しのつかない不整合になる。
そのために、行の状態とシステムの状態を別々に運ぶようにした。行の「キャンセルできるか」は締切の事実のまま残す。システム都合の停止は、結果オブジェクトの別のキーで運ぶ。画面はこの2つを別の文言で出し分ける。
行の状態 | 表示 |
対象日が判定できない | お手数ですがお問い合わせください。 |
締切を過ぎている | キャンセル受付は終了しました。 |
締切前だが、サーバが受付を停止している | ただいまキャンセルを受け付けられません。 |
キャンセルできる | 締切時刻の案内とボタン |
3行目と2行目を同じ文言にしてしまうと、締切前なのに終了したと言われた利用者が問い合わせてくる。文言を分けるのは表示上の親切ではなく、状態を混ぜないための線引きである。
戻り値の形を変えたら、呼び出し側を追う
この変更のあと、別のバックエンド関数がこの関数の結果を Array.isArray() で見ていて、黙って空になっていた。配列を返していたものがオブジェクトになったので、判定が成立しなくなったからである。
例外を返り値へ変える改修は、呼び出し側の想定を必ず変える。投げなくなったぶん、失敗が失敗として扱われない経路が増える。戻り値の形を変えたら、その関数を呼んでいる場所を全部見に行く必要がある。
診断用の webMethod を1つ置く
もう1つの対処は、段階ごとの成否を配列で返す診断用の関数を用意することだった。疑わしい手順をそれぞれ try と catch で囲み、{ label, ok, detail } を積んで、投げずに返す。
これは調査のための道具なので、原因が確定したら消す。公開前のチェックリストに「診断関数の削除」を入れておかないと、そのまま本番に残る。