ほとんどのエラーハンドリングは、すでにシステムを知っているエンジニアのために書かれている。
それは逆さまである。
ユーザーは、StripeのWebhookがタイムアウトしたこと、Supabaseのポリシーが行を拒否したこと、モデルプロバイダーが429を返したことなど気にしない。ユーザーが気にするのは次の3つだけだ。
- 何が起きたのか
- 自分の作業は安全か
- 次に何ができるのか
インターフェースがこれらの問いに答えられなければ、エラーメッセージは役に立っていない。単に実装の詳細を漏らしているだけである。
例外ではなく、ユーザーの目的から始める
エラーメッセージの初稿は、たいていコードの経路のように聞こえる。
チェックアウトセッションの作成に失敗しました。
それは事実かもしれないが、役に立たない。より良いバージョンはユーザーの意図から始める。
チェックアウトを開けませんでした。プロジェクトの詳細は保存されています。もう一度試すか、通話を予約して手動で完了させてください。
このメッセージは4つの役割を果たしている。
- 失敗したアクションを明示する
- データが保存されたかどうかを確認する
- 次のステップを提示する
- ユーザーを責めない
内部エラーは、プロバイダー、ステータスコード、リクエストID、スタックトレースとともに、依然としてログに記録できる。ユーザーはそのすべてを必要としない。
ユーザー向けの文言とエンジニアリングのテレメトリを分離する
プロダクトの表面と可観測性の表面は、同じペイロードを持つべきではない。
ユーザーには明確な復旧経路が表示される。システムはスタックトレース、リクエストID、プロバイダー応答、運用者向けのアラートルーティングを保持する。
本番環境では、同じ障害から2つの出力が欲しい。
- 画面上の人間が読めるメッセージ
- ログ、分析、アラートにおける機械可読なイベント
ユーザー向けの文言は落ち着いていて具体的であるべきだ。テレメトリは必要なら密度が高くて見苦しくても構わない。この2つを混ぜると、役に立たないログか、敵対的なインターフェースのどちらかになる。
優れたエラー状態は5つの問いに答える
エラー状態をレビューするとき、私はこのチェックリストを適用する。
「いいえ」なら、その状態は完成していない。
例えば、問い合わせフォームの失敗で「500 Internal Server Error」と表示すべきではない。次のような文言にすべきだ。
メッセージを送信できませんでした。ブラウザはこのページに留まっているため、何も失われていません。もう一度試すか、プロジェクトの詳細を直接メールでお送りください。
そしてサーバーログには実際の原因(バリデーション失敗、Resendのタイムアウト、Supabaseの挿入失敗、Webhookの拒否)を記録すべきである。
システムが失敗する前にフォールバックを設計する
チームは通常、最初の本番インシデントの後にフォールバック状態を追加する。障害がすでに公開されているため、これはコストが高い。
重要なフローについては、機能を構築している間にフォールバックを定義するのが好きだ。
| フロー | ユーザーフォールバック | 運用者シグナル |
|---|---|---|
| チェックアウト | ルートを保存し、予約リンクを提供 | セッションメタデータ付きの決済プロバイダーエラー |
| 問い合わせフォーム | メッセージを画面に保持し、直接メールを表示 | ソースとペイロード形状付きのリード取得エラー |
| AI生成 | プロンプトを保持し、再試行を提供 | プロバイダー、モデル、レイテンシ、トークンメタデータ |
| ファイルアップロード | ファイル制限と再試行パスを表示 | ストレージエラー、サイズ、MIMEタイプ、組織ID |
フォールバックは凝っている必要はない。勢いを維持する必要がある。
すべてのエラーを同じように見せない
汎用的なメッセージは、プロダクトを不注意に見せる。
- 問題が発生しました。
- 後でもう一度お試しください。
- 予期しないエラーが発生しました。
最終的なキャッチオールとして許容できる場合もあるが、製品内の唯一のエラー表現であるべきではない。
異なる障害には異なる復旧経路が必要である。
- バリデーションエラー:正確なフィールドと期待される形式を表示する
- 権限エラー:必要なロールやアカウントを説明する
- レート制限:いつ再試行するか、またはより軽いアクションを提供する
- 依存関係の失敗:ユーザーの作業を保持し、代替経路を表示する
- 破壊的アクションの失敗:何が変更されなかったかを明確に示す
目標はシステムを完璧に見せることではない。目標は、システムが完璧でないときにユーザーが方向感覚を持てるようにすることである。
運用者には別のインターフェースが必要
ユーザーを尊重する文言は、運用者が実際の証拠を依然として得られる場合にのみ機能する。
つまり、以下をログに記録することである。
- ルートとアクション
- リクエストIDまたはトレースID
- 利用可能な場合はユーザー/組織ID
- プロバイダーとステータスコード
- 安全なペイロード形状
- タイミング
- 再試行回数
また、秘密情報、生のトークン、支払いカードの詳細、プライベート文書、顧客データを含む可能性のある完全なプロンプトをログに記録しないことも意味する。
優れたエラーハンドリングは、よりソフトなログ記録ではない。より鋭い分離である。
私がリリースしようとするパターン
すべての重要なアクションに対して、私は次の形を望む。
- 早期にバリデーションし、フィールドレベルのガイダンスを表示する。
- サーバーアクション/APIルートを構造化されたエラーハンドリングでラップする。
- 安定したユーザーメッセージと安定したマシンコードを返す。
- 運用者にとって安全な完全なコンテキストをログに記録する。
- コンバージョンに影響する場合は、障害をプロダクトイベントとして追跡する。
- 可能な限りユーザー入力を保持する。
これは華やかな作業ではないが、プレミアムな感触の一部である。作業を保存し、次に何をすべきかを伝えるサイトは、赤いボックスを点滅させて最初からやり直させるサイトよりも信頼できると感じられる。
