想定外を想定せよ! 〜プロのエンジニアに必要な危機回避能力〜
こんにちは!アドリブエンジニアです。
皆さんは普段コードを書くとき、どれくらい「想定外の事態(例外)」を意識できているでしょうか?
コードレビューをしていると、プログラマーによってかなり意識の差が出るのが、この「例外処理(エラーハンドリング)」の領域です。ちゃんと「もしここで変なデータが来たらどうなるか?」を想定して書いている人と、正常に動くケース(正常系)だけを考えてサラッと書いてしまう人にくっきりと分かれます。
今回は、エンジニアが陥りがちな例外処理の落とし穴や、最もやってはいけない「悪手」、そして現場で本当に使える「想定外に強いコードの書き方」について、具体的にお話ししてみたいと思います。
そもそも「適切な例外処理」を書かないとどうなるのか?
結論から言うと、「本番環境でバグが起きたときに、何が原因なのか追跡できなくなる」という地獄が待っています。
例外処理(ここで言う例外は単なるプログラミング言語の Exception クラスに限らず、「想定外の状態全般」を指します)を考えずに、「結果だけ」を求めてコードを書いているとどうなるでしょうか。
「とりあえずうまく動いたからオッケー!」と実装を終えてリリースしたものの、後から想定外のデータが流れてきたときに処理が失敗します。しかも、ログを見てもどこで何が起きたのかさっぱり分からない……なんてことに陥るのです。
結局、後からデバッグする羽目になり、莫大な時間とコストを溶かすことになります。 だからこそ、コードを書いている「その瞬間」に、「もしここに想定外のデータが入ってきたらどう振る舞うべきか?」を想像し、先回りして対策を仕込んでおくことが不可欠なのです。
日付パースのサンプルから考える「丁寧な例外処理」
「そうは言っても、具体的にどう書けばいいの?」という方のために、簡単なサンプルで考えてみましょう。
たとえば、ユーザーが画面から入力した文字列(日付)を受け取って、プログラム内で扱うために DateTime(日付型)に変換する処理を作るとします。
文字列は 2026/08/07 のような形式で渡されてくるとしましょう。
1. 異常系を一切考えない「楽観的すぎるコード」
正常系しか考えないプログラマーは、こんなステップで処理を書きます。
文字列をスラッシュ / で分割(Split)する
分割された3つの要素(年・月・日)をそれぞれ整数(int)に変換(Parse)する
それを使って DateTime を生成して返却する
この書き方は、「渡される文字列には必ずスラッシュが2つ含まれていて、分割すれば絶対に3つの要素になり、それらはすべて数値である」という大前提に依存しています。
もしユーザーが誤って 20260807 と入力したら? スラッシュが1つしかなかったら? 配列の要素数は「2つ」になり、3つ目の要素にアクセスした瞬間にプログラムは死んでしまいます。
2. 想定外を考慮した「強いコード」
一方、例外を考慮できるエンジニアは、次のようにチェックを挟みます。
スラッシュで分割する
【チェック】要素数がちゃんと「3つ」あるか確認する
なければ、「日付文字列のフォーマットが不正です(要素数が不十分)」という意図のわかる例外を明示的にスローする
【チェック】分割された各文字列が、本当に数値に変換できるかチェックする(TryParse などを使用)
変換できなければ、同様に適切な例外を投げる
安全に変換できた値を使って DateTime を生成する
C#などの高級言語を使っていれば、自前でチェックを書かなくても言語側が IndexOutOfRangeException(配列外参照)などのエラーを出してはくれます。
ですが、「日付を分解したら要素が3つありませんでした」という独自の明示的な例外(ユーザー定義例外やログメッセージ)を出しておけば、エラーログを見た瞬間に「あ、入力フォーマットのパースでコケたんだな」と一発で原因が判明しますよね。
ちなみに……(C/C++の世界の恐ろしさ)
最もやってはいけない「握り潰し(Null返し)」
私がレビューする時に「これだけは絶対にやめて」と指摘する最悪のパターンがあります。
それが、「想定外が起こったらNullを返す(=例外の握り潰し)」です。
// 悪い例:異常が発生したのに、そっとNullを返して終わらせる
if (items.Length != 3)
{
return null; // ← これが最大の悪手!!
}
「とりあえずエラーで落ちないように、Nullを返して関数を抜け出しておこう」
……一見、アプリがクラッシュしないので「安全な対策」のように思えるかもしれません。しかしこれは、発生した異常事態を闇に葬り去る行為です。
本来、データが存在しない、あるいはフォーマットが壊れているという「異常」が起きているのに、それを呼び出し元に隠蔽して処理を続行させてしまう。その結果、呼び出し元の別の場所で後から NullReferenceException(いわゆるヌルポ)が発生し、「一体どこでNullが紛れ込んだんだ!?」と原因特定が泥沼化します。
運良く表面上は動いてしまったとしても、データが正しく反映されないまま本番環境にリリースされてしまい、後から巨大なデータ破損に繋がる……なんていう恐ろしい事態を引き起こすのです。
「落ちないようにする」のではなく、「変なことが起きたら、正しく・わかりやすく声を上げる(例外を投げる)」。これが鉄則です。
現場で多発する「通信」と「マスターデータ」の落とし穴
実際の開発現場(特にゲーム開発やWebシステム)において、特に例外処理の考慮漏れが起きやすいポイントが2つあります。
1. マスターデータの参照失敗
ゲーム開発などでよくあるのが、企画者さんが設定してくれたマスターデータ(CSVやJSONなど)を参照する処理です。
「ID:1のデータを引こうとしたら、データが存在しなかった」というケース。こちらは基本的には例外にしたいのですが、開発中は、データがまだ用意されていない、だけどデザイナーが動作確認するために先に進めたい、といった事もあるので、うまくバランスを取る必要があります。1つのデータがないことがボトルネックになってしまうのも良くないので、悩みどころです。
落としどころとしては、クライアントではnullを返して、進めるところまで進める、サーバーではしっかりと例外として扱う、かなと思います。
2. ネットワーク通信の不安定さ
ネットワークが絡む処理は、まさに「想定外の宝庫」です。 テスト環境(社内の快適なWi-Fi環境)では何の問題もなく動いていても、実際のユーザーは移動中の電車や、電波の弱い場所でアプリを使います。
通信タイムアウトが起きる
レスポンスデータが途中で切れる
サーバーから予期せぬエラーコードが返ってくる
こういった通信トラブルが起きたときに、画面がクルクル回ったままフリーズ(固まって)してはいけません。「通信に失敗しました。もう一度お試しください」といったダイアログを出し、安全にリトライできるようにエラーハンドリングしておく必要があります。
テスターさん任せにしない!エンジニアから仕掛ける異常系テスト
ここまで「エンジニアが気をつけるべきこと」をお話ししてきましたが、実はQA(テスト)の工程でも例外処理の検証は漏れがちです。
デバッガーさんやテスターさんの作成するテスト仕様書を見てみると、意外と「正常系(正しく操作したときの挙動)」しか書かれていないことがよくあります。異常系のテスト(連打した、途中で回線を切った、異物データを淹れた等)は、テスターさん個人のスキルに委ねられてしまっている現場も少なくありません。
だからこそ、開発したエンジニア自身が、 「この機能、実は通信中にタスクキルされたり、データが欠損してたりすると怪しいかも……」 と感じたポイントがあれば、積極的にテスターさんに伝えるべきです。
「ここ、連打したり通信を途中で切ったりしたときの挙動を重点的に見てもらえますか?」と一言声をかけるだけで、本番障害のリスクを大幅に減らすことができます。
まとめ:「まあ大丈夫だろう」という楽観バイアスを捨てよう
人間にはどうしても「ここは大丈夫だろう」「まあ、そんな奇妙な操作は誰もしないだろう」と思ってしまう心理的バイアス(正常性バイアス)があります。
しかし、開発チームがテストする回数なんて、本番で何万人・何百万人のユーザーがプレイする回数に比べたら、1万分の1にも満ちません。本番環境では、僕たちの想像をはるかに超える「想定外」が必ず起こります。
コードを書くときは、一歩立ち止まって、自分の知識と過去の失敗経験のアンテナをフル動員してみてください。
「もし、ここがNullで返ってきたら?」
「もし、配列の中身が足りなかったら?」
「もし、この瞬間にボタンを連打されたら?」
そうやって「最悪のシナリオ」を想像し、丁寧に例外処理を仕込んでおくこと。これこそが、プロのエンジニアとしての誠実さであり、プロジェクトと自分自身を守る最大の武器になります。
今回の記事が、皆さんの日々のコードレビューや実装において、少しでも参考になれば幸いです。
最後までお読みいただき、ありがとうございました!
