エラーハンドリング完全ガイド:設計・実装・運用における実践的ベストプラクティス
エラーハンドリングとは何か — 概念と目的
エラーハンドリング(エラー処理)とは、ソフトウェアが異常事態や予期しない入力、外部システムの障害などに遭遇したときにそれを検出、伝播、対処、そして必要なら復旧する一連の仕組みと方針を指します。単に「例外を投げる/捕まえる」だけでなく、ログ記録、ユーザーへの通知、リトライ、フォールバック、監視やアラートの設計まで含めた広い設計課題です。
なぜ重要か
- 信頼性向上:適切なエラーハンドリングはシステムの予測可能性と可用性を高める。
- セキュリティ:エラー情報の扱いを誤ると機密情報漏洩や攻撃につながる。
- 運用性(Observability):正しいログとメトリクスは運用・障害対応を容易にする。
- ユーザー体験:適切なエラーメッセージとフォールバックで、ユーザーの混乱を最小化できる。
エラーの分類
- 回復不能(Fatal) vs 回復可能(Recoverable): サービス停止が避けられないか、代替パス・リトライで対応できるか。
- 同期エラー vs 非同期エラー: 同期関数内の例外、非同期コールバックやキュー処理での失敗。
- 短期的(Transient) vs 永続的(Permanent): たとえば一時的なネットワーク障害はリトライで回復し得る。
- ユーザー入力エラー vs システム内部エラー: UX上の扱い(ユーザーに再入力させる等)が変わる。
言語・環境ごとの典型的手法
言語やランタイムによって推奨されるエラーハンドリングの形は異なります。
- 例外ベース(Java、C#、JavaScript): try/catch/finally構造で例外を捕捉し、必要に応じてログや再送を行う。
- 戻り値エラー(Go): error型を返し呼び出し側で明示的に処理する。例外的な制御フローを避ける設計。
- Result/Option型(Rust、最近の関数型言語): コンパイル時にエラー処理を強制することで安全性を高める。
実践テクニックとパターン
- Try/Catch/Finally: リソース解放や後片付けをFinallyで確実に行う。
- エラーのラップ(Wrap): 下位レイヤの詳細を保持しつつ上位に意味のある文脈を付与する(例: "DBクエリ失敗: ユーザーID=1234")。
- リトライ+指数バックオフ: 一時的エラーに対する一般的対策。ただし無限リトライは避ける。
- サーキットブレーカー(Circuit Breaker): ある外部依存が多数失敗したら一定期間呼び出しを停止し、システム全体の崩壊を防ぐ。
- バルクヘッド(Bulkhead): リソースを隔離することで、部分的な障害が全体に波及するのを防ぐ。
- フォールバック(Fallback): メイン処理が失敗したときに代替処理(キャッシュ返却、デグレード機能)を行う。
ログ・監視・可観測性(Observability)
エラーが発生したときに「何が」「いつ」「どこで」「どの程度」の情報が得られるかが重要です。ログは人が調査するため、トレースは分散システムの経路解析、メトリクスはアラートの基準、イベントはインシデントのトリガーとして使います。
- 構造化ログ(JSONなど): 検索と集計が容易になる。
- トレース(分散トレーシング): リクエストを跨ぐ障害の因果関係を特定する(OpenTelemetry 等)。
- エラーレートやエラー数をメトリクス化し閾値でアラートを出す。
ユーザーへの見せ方(UX)
ユーザーに対するエラー表示は、技術情報をそのまま見せるのではなく、次に何をすればよいかを示すべきです。
- 内部情報は隠す(スタックトレースやDBエラーなどの内部情報は漏らさない)。
- 再試行ボタンや問い合わせ先、障害発生中の代替案を示す。
- トランジェントな問題の場合は「あとで再試行してください」などの文言でユーザー期待値を管理する。
セキュリティと情報漏洩防止
エラーメッセージやログにパスワード、APIキー、個人情報を含めないことは必須です。攻撃者はエラーメッセージから内部構造や脆弱性を見つけることがあるため、公開向けメッセージと内部ログを分離する運用が重要です。
テスト戦略
- ユニットテストで成功ケースだけでなく失敗ケースも網羅する(異常系テスト)。
- 統合テストで外部依存の異常(タイムアウト、HTTP 5xx等)をシミュレートする。
- カオスエンジニアリング(障害注入)でシステムの耐性を評価する。
運用でよくある落とし穴
- エラーを黙殺(catchして何もしない)して問題を隠す。
- 情報量が多すぎるログでノイズが増え重要なシグナルを見逃す。
- リトライを無制限に行い、かえって外部サービスに負荷をかける。
- 例外をただ上位に投げ続けるだけでコンテキストが失われる。
実例コード(簡潔な比較)
言語ごとの典型例を示します(理解の補助)。
JavaScript(例外):
try {
const res = await fetch(url);
if (!res.ok) throw new Error('ネットワーク応答が不正');
// ...
} catch (err) {
console.error('API呼び出し失敗', err);
// UI用に汎用メッセージを返す
}Go(戻り値エラー):
res, err := http.Get(url)
if err != nil {
// ネットワークエラー
log.Println("取得失敗:", err)
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("HTTP %d", res.StatusCode)
}Rust(Result):
fn fetch() -> Result運用上のチェックリスト(実務で使える)
- エラー分類が設計書/運用手順に明記されているか。
- 重要なエラーは必ず構造化ログとトレースを出しているか。
- 公開向けメッセージと内部ログの分離はできているか。
- 一時的エラーに対するリトライ戦略(最大回数・バックオフ・ジッター)を定義しているか。
- サーキットブレーカーやフォールバックは必要な箇所に導入されているか。
- 障害注入や異常系テストで問題が検出されているか。
まとめ
エラーハンドリングは単なる「例外を捕まえる」処理以上のもので、設計・実装・運用が一体となる領域です。適切に設計されたエラーハンドリングはシステムの信頼性、セキュリティ、運用効率、そしてユーザー体験を大きく改善します。言語特性に合わせつつ、ログ・トレース・メトリクスを組み合わせ、リトライやサーキットブレーカーといったパターンを取り入れることで、より堅牢なシステムを作れます。常に「何が起きるか」を想定し、失敗を前提とした設計を行うことが重要です。
参考文献
- MDN — JavaScript: 制御フローとエラーハンドリング
- Python 公式ドキュメント — エラーと例外
- Go 言語ドキュメント — エラー処理に関するFAQ
- The Rust Programming Language — Resultによる回復可能なエラー処理
- OWASP — Error Handling Cheat Sheet
- Microsoft Docs — Circuit Breaker パターン
- Microsoft Docs — Retry パターン
- OpenTelemetry — Observability 標準とツール
- Principles of Chaos — Chaos Engineering の原則


