webエンジニアになりたての頃、例外についてどう考えたら良いか分からなかったので
そういう人のための記事。
サービスの機能要件、非機能要件に応じて対応を変える必要があるため、
一概に「これが正解」とは言えないが、良い実装に至るための考え方を紹介する。
この記事では、「APIからデータを取得し、Webページを表示するシステム」を想定して記載する。
ユーザーへの表示の仕方
やってはいけないパターン
まず大前提として、APIリクエストに失敗した場合に
Webページがフレームワークのエラー画面を表示するような状態は言語道断である。
理由は2つある。
- ユーザーにとって、「なぜエラーになったか」「どうしたらサービスを使うことができるか」が分からないため不親切なサービスになる
- 悪意ある人が見た場合、「例外処理が考慮されていない=脆弱性が潜んでいる可能性が高い」と考えて攻撃対象になりやすい
ではどうすれば良いか?
大抵の場合は、以下のどれかが良いと考えられる。
- データがないと表示できないモジュールのみトルツメ
- モジュール単位で「データが取得できませんでした」のエラーを表示する
- 画面が表示できないような致命的なエラーの場合は、独自に作成したエラー画面を表示する
エラー画面の場合、ユーザーには次に取るべきアクションをメッセージで教えてあげると親切である。
APIリクエストが失敗するケース一覧
大きく分けて以下の3パターンが考慮されていることが大切で、
発生する可能性によって準正常系とするのか異常系とするのか判断すると良い。
- 定義されたエラー系のステータスコードが返却される(4XX,5XX)
- 想定より時間がかかってAPIから返事が返ってこない
- APIサーバーがダウンしていて応答がない
エラー系のステータスコードが返却される場合
まずはAPIの仕様書を見て、返ってくる可能性のあるステータスコードを理解しておく必要がある。
よく404の例外をどうするか迷うところだが、筆者は以下のように判断している。
- 存在しないリソースを問い合わせているのがそもそもおかしい。なので404を返すのは基本的にあり得ない→異常系として処理
- APIに問い合わせた結果、リソースが存在しないということは割とありえる→準正常系として処理
取り扱うAPIがどういうポリシーで実装されているかと、
そのAPIを利用する側でどういう想定をしているのか、というところが判断のポイントだ。
大抵のライブラリはAPIから4XX,5XXが返却された場合、例外を投げるような仕様になっているため
テキトーに利用せず、キャッチした後どうするかを考えなければいけない。
想定より時間がかかってAPIから返事が返ってこない
まず、リクエストする側で「APIのレスポンスを待つまでは〇〇ms」と
タイムアウトの時間を決めておくのが良い。
この場合は以下3点のどれかに改善余地があり、発生しない方が良いことなので異常系(例外)として扱う。
- タイムアウト時間の設定が厳しすぎる
- APIが遅すぎる
- 稀に重いデータがあるなど、その振れ幅を考慮しきれていない
APIサーバーがダウンしていて応答がない
この世に「絶対に障害が発生しないシステム」は存在しないため、
このケースも考慮しておかなければならない。
ただし、これについても発生しない方が良いことなので異常系として扱う。
エラーログに何を出力するか決めておく
本番環境などでエラーが発生した場合、必ずその原因を調査しなければならない。
エラーログをいい加減に出力しておくと「後で調査できない」という痛い目を見ることになる。
エラーログに出力する例
- 発生時刻(これはログ出力の仕組みで出しておく)
- 接続先API
- APIリクエストした時のURLやリクエストボディ
- 同じ状況を後から再現して確かめられるように
- ただしユーザー名やメールアドレスなど機密情報は出力しないように
- 何がダメだったのか
- 取得しようとしたデータのIDなど
- 1リクエストごとに発行される、ログをトレースするためのID
- 例外が発生した時のスタックトレース
独自例外を定義する
さらに、独自で例外クラスを定義しておくという方法も組み合わせると良い。
メリット
- ログに出力される例外クラス名で、どこで何が発生したのかが推測しやすくなる
- 例外クラスのフィールドにデータのIDなどを入れておけば、発生した例外とそのデータを一緒に管理できる
- 自分が例外としたい単位で処理できる
- タイムアウトした場合と404の場合は投げられる例外が違うが、ロジック的に同じように扱いたいなど
- データ取得先が何なのかを利用側が意識する必要がなくなり、取得先が変わっても利用側に影響がない
- とにかくデータ取得ができなかったことを「FailedFetchUserException」と定義しておけば、接続先がAPIからDBに変わっても利用側に影響がないように修正することができる
まとめ
- 考慮すべきパターンは3つ(4XX,5XX系、タイムアウト、疎通できない)
- 基本的には発生してはいけないかどうかで異常系 or 準正常系を判断すると良い
- ログはしっかり出力しておくこと
- 独自例外を定義するとコントロールしやすい