プログラマが知るべき97のこと/技術的例外とビジネス例外を明確に区別する


プログラムの実行時に起きる問題には、大きく分けて2つの原因があります。1つは技術的な原因です。これは、発生するとアプリケーションの実行そのものが続けられなくなるような問題のことです。もう1つの原因はビジネスロジックで、これは簡単に言えば、ユーザがアプリケーションの使い方を誤らせないために(わざと)発生させる問題です。「モダンな」プログラミング言語、LISP、Java、Smalltalk、C#などは、この2種類の問題の発生を通知するために「例外(Exception)」を使用します。しかし、2種類の問題は本質的に大きく異なるものなので、混同しないように常に注意する必要があります。両者を同じ例外階層構造を使って表現することは混乱の元になりますし、ましてや同じ例外クラスで表現するのはもってのほかです。

プログラムのコードに誤りがあると、解決が難しい「技術的な問題」が発生することがあります。たとえば、要素が17個しかない配列の83番目の要素にアクセスするようなコードがあると、プログラムは間違いなくまともに動かず、例外が発生するでしょう。そこまで極端でなくても、たとえばライブラリのコードを呼び出す時に引数が適切でなかったりすると、ライブラリ内部で同様のことが起きるでしょう。

この種の例外を処理するコードを自分の手で書くのは賢明とは言えません。発生した例外を自分で捕まえずにトップレベル、アーキテクチャレベルまで通過させ、トップレベルの例外処理メカニズムに後の処理を委ねるのです。トップレベルの例外処理メカニズムは処理を依頼されると、システムを安全な状態に戻すための対処をします。具体的には、トランザクションのロールパック、ログ作成、管理者への警告、ユーザへの通知(管理者への警告より表現は穏やかになる)などを行います。

ライブラリ内で問題が発生した場合も同様のことが言えます。たとえば、メソッド呼び出しのコードがライブラリの契約(Contract) に違反していた場合などです。契約の違反とは具体的には、渡す引数が正しくない、必要なオブジェクトが正しくセットアップできていない、といったことを意味します。これも「技術的な問題」の一種です。要素が17個しかない配列の83番目の要素にアクセスしようとする、というのと重大さの点でさほど変わりません。これは本来、呼び出し側のコードでチェックされるべきことなのですが、もし、チェックが行われないのだとすれば、それはクライアント側のプログラムのミスということになります。呼び出し側コードでの正しい対処は、自分で例外を発生させることです。

データベースサーバが応答しない等、実行環境の不備によってプログラムの実行が続けられない、という状況も考えられます。これは上記の問題とは異なりますが、やはり技術的な問題で、あることは確かです。本来は、インフラの側が何らかの対処(接続を修復する、何度かリトライをする、など)をするべきなのですが、それが失敗に終わったという状況です。先の2つとは原因が違いますが、解決のためにできることがほとんどないという点は同じです。発生した例外を捕まえずに通過させて状況を通知し、トップレベルの例外処理メカニズムに対処を委ねるしかないでしょう。

ここまで述べた技術的な問題とは違い、「ビジネスロジック」は、業務ロジックの判断によりプログラムの実行が中断されるような状況です。確かに普通の状況ではないし、望ましい状況でもないのですが、技術上の問題が起きた時ほど深刻ではありません。プログラム自体に問題がある、というわけではないからです。たとえば「ユーザが、預金額を超える額のお金を口座から引き出そうとした」というような問題を指します。これは先ほど述べた「契約(Contract)」の一部と言ってもいいでしょう。この状況で例外を発生させることは、モデルに含まれた代替パスの1つにすぎないのです。つまり、クライアントの方で、あらかじめこの状況に対処するコードを組み込んでおく必要があるということです。発生させる例外は、この状況にのみ対応する例外を新たに定義するか、あるいは、技術的な例外とは違う例外階層構造に属するものにすべきです。システム全体に影響を与えず、クライアントだけで問題に対処できるようにするのです。

「技術的例外」と「ビジネス例外」を同じ例外階層構造で扱ってしまうと、両者の区別は暖味になってしまいます。メソッドの契約に違反していてもそれがわからず、メソッドを呼び出す際にどういう事前条件を満たさなければならないのかもわかりません。例外が発生したときにクライアントのコードで処理するべきかも判断できません。2つの例外を明確に区別して扱うようにすれば、そういうことは起きません。技術的例外に関しては、アプリケーションのフレームワークに対応を任せることができ、ビジネス例外に関しては、クライアントにあらかじめ対処するコードを組み込んでおくことができます。