「例外」という言語機構の誤解
深夜3時、Slackに鳴り響くエラー通知。眠い目をこすりながらダッシュボードを開くと、そこには「ユーザーの入力ミス」や「一時的なネットワークの瞬断」といった、本来なら無視していいはずのログが山のように積み上がっている。多くのエンジニアが一度は経験するこの「アラート疲れ」こそ、現代のシステム開発における最大の生産性阻害要因の一つであると私は断言する。なぜ我々は、本来対応不要な事象にまで過剰に反応してしまうのか。その根源は、プログラミング言語が提供する「例外(Exception)」という機構を、システム運用の「障害(Incident)」と同一視してしまうという、極めて初歩的かつ致命的な設計ミスにある。
ソース元記事でも指摘されている通り、例外はあくまで「通常の制御フローから脱出するための言語機構」に過ぎない。しかし、現場では「try-catchで囲った場所でSentryに通知を送る」という安直な実装が蔓延している。これが引き起こすのは、伝播経路の数だけ重複して飛んでくる通知の嵐だ。1つの失敗が、スタックトレースを遡るたびに何度も通知される。これでは、本当に人間が介入して復旧作業を行わなければならない「真の障害」が、ノイズの中に埋もれてしまう。いわゆる「オオカミ少年アラート」の状態だ。通知が多すぎることは、通知が届かないことと等価である。我々エンジニアは、例外を「エラー」という一括りの概念で捉えるのを今すぐやめ、それが「仕様通りの準正常系なのか」「外部要因の異常系なのか」「コードのバグなのか」を厳密に切り分ける必要がある。
以下の表は、システム開発において混同されがちな概念を整理したものである。この区別を曖昧にしたままコードを書くことは、自ら障害対応の難易度を上げていることに他ならない。
| 用語 | 定義の所在 | 本質的な意味 |
|---|---|---|
| 準正常系 | 要件定義 | 入力不正や権限不足など、仕様が想定した拒否 |
| 異常系 | 要件定義 | DB停止や外部サービス失敗など、コード外の要因 |
| バグ | ソフトウェアテスト | コードが仕様と異なる実装になっている状態 |
| 障害 | システム運用 | 機能が提供できず、人による復旧作業が必要な事象 |
結局のところ、通知の判断基準は「例外が発生したかどうか」ではなく、「復旧作業が必要かどうか」の一点に尽きる。自力で回復可能なリトライ処理を通知対象に含めるのは、単なるノイズの生成だ。我々が目指すべきは、コードの実行中に発生する例外を、その性質に応じて「制御フローの一部として処理する」か「システム運用の障害として通知する」か、明確に分岐させる設計である。
設計の処方箋:通知を「1箇所」に絞る技術
では、具体的にどう実装を改善すべきか。まず徹底すべきは「処置できない例外は捕捉しない」という原則だ。多くの開発者が、とりあえずtry-catchで囲んでログを吐くという「安心感のための実装」を行っているが、これは技術的負債の典型例である。処置ができないのであれば、その例外は呼び出し元へ伝播させ、最終的なエントリーポイントや、Expressのようなフレームワークが提供するグローバルなエラーハンドラで一括して処理すべきだ。これにより、通知はシステム全体で「1箇所」に集約され、重複通知という悪夢から解放される。
特に注意が必要なのが、非同期処理の扱いだ。Node.jsやTypeScriptの環境において、awaitを忘れた非同期関数は、メインの制御フローから切り離された「迷子」になる。この迷子の中で発生した例外は、グローバルなエラーハンドラには決して届かない。結果として、障害が発生しているにもかかわらず、システムは「正常」と誤認し続ける。これを防ぐには、切り離した非同期処理に対して、その場でcatchを記述し、明示的に通知を行う必要がある。あるいは、完走を保証するキューイングシステムへ処理を委譲する設計が求められる。サーバーレス環境であれば、応答を返した瞬間に実行基盤が回収されるため、awaitを怠ることは即座にデータ不整合や処理中断という障害に直結する。この「非同期の境界」を意識できるかどうかが、シニアエンジニアとジュニアエンジニアを分かつ境界線であると私は考える。
また、バグに起因する例外(TypeErrorやReferenceErrorなど)は、予測不可能な場所で発生する。これらを個別の関数内で捕捉しようとするのは無駄な努力だ。これらは「想定外の事態」として、システムの外側で捕捉し、即座に開発者へ通知されるべきである。一方で、在庫不足のような「ビジネスロジック上の失敗」は、例外ではなく戻り値で表現すべきだ。例外を制御フローの手段として使うことは、コードの可読性を著しく低下させ、instanceofによる型判定という泥沼のコードを量産する原因となる。失敗を「値」として扱う関数型プログラミングの思想を取り入れることで、エラー処理はより堅牢で、かつ意図が明確なものへと進化するはずだ。
最後に、読者であるあなたに問いたい。あなたのプロジェクトで鳴り響くそのアラートは、本当に「今すぐ人間が対応すべきもの」だろうか?もしそうでないなら、それはシステムがあなたに「設計の不備」を訴えているサインではないか。明日から、すべてのcatchブロックをレビューし、「ここで本当に復旧作業が発生するのか?」と自問自答してほしい。不要なcatchを削除し、通知の経路を整理する。その小さな積み重ねこそが、深夜の障害対応からエンジニアを解放し、より本質的な開発に集中できる環境を作る唯一の道である。あなたは、自分の書いたコードが発する「悲鳴」を、正しく聞き分ける準備ができているだろうか。


コメント