はじめに
開発現場でこんな経験はありませんか?
- 突発対応でチケットが進まない - エラー調査や問い合わせ対応に追われて、開発が止まる
- 調査に時間がかかる、原因が分からない - ログを見ても何が起きたか分からず、原因特定に半日
- 問い合わせが多くて疲弊する - 単に「エラーが出ました」と言われても、ユーザーは自己解決できず問い合わせに
- エラーにならず、不正データが出来て問い合わせに
実は、これ全部、例外(エラー)処理を丁寧に実装しなかったことに起因してるかもしれません。
実例:エラー画面が載ったPDF
ある時、PDF生成処理(画面を開く→PDFとして印刷する)で画面を開く際サーバーエラーが発生し、エラーが出た画面をそのままPDFに変換して公開してしまった事例がありました。
結果:
- 問題のあるPDFを一通り洗い出す必要が発生
- 全て再生成する時間がかかる突発対応
- チームメンバーの開発作業がストップ
正しく例外処理をすると何が変わるか
例外処理を丁寧に実装すると、ステークホルダー全員にメリットがあります。
開発者にとってのメリット
-
問い合わせ対応時間が減る
- エラーの原因が分かりやすいので、調査時間が激減
- ユーザーや事業部が自己解決できるため、問い合わせ自体が減少
-
案件に集中できる
- 開発作業が中断されず、計画通りに進行
- 新たな案件の提案等に時間を使える
-
精神的な負担が軽減
- 「また問い合わせが来た...」というストレスから解放
- 問い合わせ自体が無くなれば「俺ばっかりこの対応してるな...」が無くなる
事業部にとってのメリット
-
自分で確認・対応できる
- エラーメッセージから何をすべきか分かり、開発者に頼らず速やかに解決
-
案件が早く進む
- 開発者が案件に集中できるため、やりたいことの実現が加速
- 施策が計画通りに進む
-
ユーザー(社外のステークホルダー)からの問い合わせが減る
- エラーメッセージから何をすべきか分かり、弊社のサポートや担当者に頼らず解決
ユーザーにとってのメリット
-
自己解決できる
- エラーメッセージから何をすべきか分かり、サポートに頼らず自力で復帰、対応
-
サービスを安心して使える
- 重要な処理(決済処理、作品の登録更新等)で「よくわからないエラー」による不安がなく、サービスへの信頼を維持
仕様段階でできること
デザイナー・ディレクターができること
エラー時のユーザー体験も仕様の一部として扱う
現状の課題
- 仕様策定時に例外処理を検討していない
- 開発者の裁量に全て任せている
目指す姿
- 仕様検討時に例外処理も一緒に考える
- 全員が例外処理を当たり前のように気にする
仕様検討で考慮すべきこと
❶ どういうエラーが出ると思う?
- 入力値が不正だったら?
- ファイルサイズが大きすぎたら?
- 何かの上限(登録数、限度額等)に達していたら?
- その他エラーが起きたら?(ネットワークエラー等どうしてもたまに起きてしまうエラー)
❷ エラーが出たらどうする?
- ユーザーにどう伝える?(画面デザイン、文言)
- ユーザーは何をすればいい?(リトライ?問い合わせ?)
- サポートや事業部はどう対応する?
重要なポイント
開発者だけでなく全員が例外処理を当たり前のように気にすることで、サービス品質が上がる。
例外処理は「開発の工程」ではなく「サービス設計の一部」として捉えることが重要だ。
開発段階でできること
開発者ができること
可能な限り発生し得る全てのエラーを「想定」する
現状の課題
- 何かしらエラーが発生したら共通のエラー画面が返るから良いだろうと考えている
- そもそもエラーが出ることを考慮していない
目指す姿
- 起こり得る全てのエラーそれぞれに適切な対応を考える
- 適切な対応を実装または許容するかを理由を付けて判断する
開発者向け:実践テクニック
テクニック① 外部通信を信用しない
クライアント側の心構え
- データベース、AWS、外部API、ファイルI/Oは失敗する前提で書く
- レスポンスのエラーコードを必ず確認する
- タイムアウトやネットワークエラーにも備える
- リトライして良い物はリトライする
⚠️ よくある誤解
「今まで動いてたから大丈夫」「社内APIだから信頼できる」
→ どんなシステムでも障害は起きます
コード例:外部API呼び出し(PHP)
❌ 良くない例
function badExample($url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response);
return $data->value;
}
問題点:異常レスポンスが返り、valueという項目が無いとサーバーエラーになる
✅ 改善案
function betterExample($url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = @curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
if ($response === false) {
throw new ApiException("API通信に失敗: {$error}");
}
if ($httpCode == 503) {
throw new RetryableApiException("一時的なエラー");
}
if ($httpCode >= 400) {
throw new ApiException("APIエラー (HTTP {$httpCode})");
}
$data = json_decode($response);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new ApiException("レスポンスが不正です");
}
return $data->value;
}
コード例:外部API呼び出し(JavaScript)
❌ 良くない例
async function badExample(url) {
const res = await fetch(url);
const data = await res.json();
return data.value;
}
問題点:異常レスポンスが返り、valueという項目が無いとエラーになる
※特に、その処理だけでなく、ブラウザで動く他の処理も巻き込まれてしまう
✅ 改善案
async function betterExample(url) {
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
return data.value;
} catch (err) {
throw new ApiError("API通信に失敗しました", err);
}
}
テクニック② エラーの返し方を工夫する
サーバー側の責務
-
リトライ可能なエラーかどうかを判定できるように返す
- 一時的な障害(503)
-
クライアント起因のエラーは適切に修正できるように返す
- 入力値不正(400, 422)
-
再現不可能なサーバーエラーは開発者に分かりやすく通知
- 予期しない障害(500)
コード例:エラーレスポンス(PHP)
❌ 良くない例
function badExample() {
// 何らかのエラーが発生したとする
throw new Exception('エラー');
return response()->json([
'status' => 'success'
], 200);
}
問題点:
- 呼び元でエラーを受け取った時、理由が何であれ同じ処理をするしかなくなってしまう
- フレームワークのデフォルトエラー画面(HTML)が返ってしまう(この処理はJSON文字列を返すべきなのに)
✅ 改善案
適切なHTTPステータスコードとエラーメッセージを返す実装が必要です。詳細な実装例は長くなるため、重要なポイントは以下の通りです:
- エラーの種類ごとに適切なHTTPステータスコードを返す
- エラーメッセージはユーザーが理解できる内容にする
- エラーコードを設定して、プログラムで判定できるようにする
- 開発者向けの詳細情報とユーザー向けのメッセージを分ける
テクニック③ 最後の砦の正しい使い方
基底例外でのみcatchする「雑なtry-catch」
→ これは最終手段であって常套手段ではない
❌ 何でもcatch
- すべてのエラーを同じように扱うと、適切な対応ができない
- 開発者への通知が増えすぎて麻痺する
✅ 具体的にcatch
- 想定できる例外は個別に処理
- 予期しない例外だけ最後の砦で受け止める
⚠️ ただし、最後の砦がないのは論外
握りつぶしてはならないエラーも構わず握りつぶして false や空文字を返すのは絶対にNG
コード例:try-catchの使い方(PHP)
❌ 良くない例
function badExample() {
$api = new ExternalApi();
try {
$result = $api->call();
} catch (Exception $e) {
// 全部500エラー
\Sentry::captureException($e); // 開発者に通知
return error500();
}
}
問題点:通知しなくて良い物も全部開発者に通知されてしまう
✅ 改善案
function betterExample() {
$api = new ExternalApiImproved();
try {
$result = $api->call();
} catch (ApiTimeoutException $e) {
// タイムアウト → 503エラー(リトライ可能)
return error503();
} catch (InvalidInputException $e) {
// 入力値不正 → 400エラー(クライアント起因)
return error400($e);
} catch (Exception $e) {
// 予期しないエラー → 500エラー
\Sentry::captureException($e); // 開発者に通知
return error500();
}
}
明日からできること
デザイナー・ディレクターが明日からできること
① 仕様書のフォーマットに例外処理に関する項目を増やす
仕様検討時に「どんなエラーが起きそう?」「起きたらどうする?」を確認する
② エラーが出た時のユーザー(社内、社外ともに)への見せ方をデザインに組み込む
- バリデーションエラーや、サーバーエラーが出た時の、共通のメッセージの表示の仕方を決める
- その表示の仕方で本当にユーザーは自力で復帰できるのか?等を考える
エンジニアが明日からできること
① エラーを洗い出す
- 今作っている機能で起きそうなエラーをリストアップ
- 外部通信、入力値、データ状態など、あらゆる失敗パターンを想定
② 適切に実装する
- 洗い出したエラーに対して、適切な処理を実装
- コメントを残すことで、後から見ても意図が分かるように
③ テストする
- 想定した全ての例外処理を正しく動くかテストする
- 必要に応じてモックを作成し、テストしやすい設計にする
まとめ
例外処理を丁寧に実装すると...
✅ ユーザーが自己解決でき、問い合わせが減り、信頼も維持される
✅ 突発対応が減りor早く解消出来、案件に集中できる
✅ 案件が早く進み、計画通りに事業を進められる
全員で例外処理を気にする文化を作る
仕様段階から開発、テストまで、例外処理を「当たり前」にすることでサービス品質が向上する
おまけ:システム開発における三方良し
近江商人の経営哲学「三方良し」は、システム開発にも当てはまります。
三方良しとは
ビジネスの本質は三者の利益のバランス
- 売り手良し - 売り手が利益を得る
- 買い手良し - 買い手が満足する
- 世間良し - 社会にも貢献する
三者すべてが利益を得てこそ、持続可能なビジネスが成り立つ
システム開発における三方良し
-
開発者良し(ディレクター、デザイナー含む)
- 突発対応が減り、案件に集中できる
-
事業部良し(開発部以外の人と言える)
- 自己解決でき、開発が早く進む
-
ユーザー良し(要は社外のステークホルダー)
- 自己解決でき、サービスへの信頼が低下しない
- 問い合わせも減る
例外処理は、三方良しを実現するための一つの要素です。
例外処理を正しく実装、テストすることは、結果として持続可能な開発、ビジネスに繋がります。