C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/02/25 04:29
まず最初に謝ります。ごめんなさい、以前 業務エラーの表現方法の考察 で書いたことを取り消させてください。

僕はやはり、

「業務エラーは例外で表現するべき」

と考えます。
では、その理由について語っていきます。


【 目次 】
1. 例外の利点
1-1. 例外時の処理コードを隔離できる
1-2. バグを検出しやすい
1-3. 詳細な情報を保持している

2. 業務エラーとしての例外
2-1. 一つのメソッドは一つの機能を表す
2-2. コスト

3. 注意点
3-1. 適切な実装の上で必要となるのは判別情報のみ
3-2. 業務エラーはExceptionクラスから派生

4. まとめ

5. 参考文献


【 用語 】
この記事で使用する用語について、以下のように定義します。

・例外
System.Exceptionから派生する全ての例外クラスを指します。

・エラー
正常系ではないフローを指します。エラーは業務エラーとシステムエラーに大別できます。

・業務エラー
エンドユーザーの操作次第で発生しうるエラーを指します。
業務を遂行する上で間違った操作をエンドユーザーが行った場合に発生するエラーや、エンドユーザーが操作を行っている間に、他のエンドユーザーが何らかの操作を行ったことによって、現在の操作が有効でなくなった場合に発生するエラーなどです。

・システムエラー
システム異常やネットワーク異常、プログラムの欠陥など、エンドユーザーに非がないエラーを指します。


【 1. 例外の利点 】
業務エラーとかシステムエラーとかはひとまず置いといて、まずは「エラー」を例外で表す場合の利点を挙げてみます。
  • 例外時の処理コードを分離できる
  • バグを検出しやすい
  • 詳細な情報を保持している
こんな所でしょうか。
それぞれ見ていきましょう。


[ 1-1. 例外時の処理コードを隔離できる ]
構造化例外処理は、その名が表すように、「例外処理コードを分離・隔離」することによって「構造化」します。つまり、可読性、保守性に優れるということです。

エラーを戻り値で表す場合はどうでしょうか?
これは、if文やswitch文を用いて処理コードを分離することができます。
しかし、確実に構造化例外処理より劣ります。
構造化例外処理なら、例えば例外をスローする可能性のあるメソッドを呼び出す時、例外が発生したとしても呼び出し元で特に処理する必要がなければ、呼び出し元には例外に関するコードを一切記述する必要がありません。例外はそのまま上位へと伝わり、適切な箇所で処理が行われます。


[ 1-2. バグを検出しやすい ]
例外はハンドルされなければCLRに検出されます。また、ハンドルし損ねた例外を最上位 ( ASP.NETで言えばGlobal.asaxのエラーハンドラ ) で全てハンドルし、ログを残すといったことも可能です。
僕の場合、この最上位のハンドラで例外を検出したら、「予期しない例外が発生しました。」というようなメッセージをエンドユーザーに見せて、裏ではログを残すという手法をよく使います。

エラーが戻り値で帰ってきた場合、そのエラーを適切に処理しなくても、CLRには当然検出されません。すると、どこかで不整合が生じます。その結果アプリケーショ ンがおかしな挙動を起こしたとしても、何が原因なのかは例外に比べて判別がつきづらくなります。また、アプリケーションが ( 一見すると ) 正常な動作を続けてしまうかもしれません。その場合、バグを検出することは困難になります。


[ 1-3. 詳細な情報を保持している ]
デバッグの最中にハンドルされない例外が発生した場合、その例外の各プロパティを参照すれば色々な情報を得ることができます。具体的には、どんな例外なのか ( 型やMessageプロパティ ) 、どのアセンブリで発生したのか ( Sourceプロパティ ) 、どのメソッドで発生したのか ( TargetSiteプロパティ ) 、どういう経緯で発生したのか ( StackTraceプロパティ ) 、例外の元となった例外について ( InnerExceptionプロパティ ) 、といった情報となります。
先ほど書いたように、ハンドルされなかった例外が発生したら例外の情報をログに残すようにすれば、運用時に発覚したバグも調べやすくなります。

エラーを戻り値で表した場合、これらの情報を取得するのは言うまでもなく大変困難です。


【 2. 業務エラーとしての例外 】
ここまでで、例外の利点について見てきました。これらは全て、業務エラーを例外で表した場合にも適用される利点です。
ここからは、業務エラーとしての例外について別の視点から考察していきます。


[ 2-1. 一つのメソッドは一つの機能を表す ]
例えば、「DBへの反映を行うメソッド」があったとします。
このメソッドは「DBへの反映を行うメソッド」であり、「DBへ反映できるようなら反映するメソッド」ではありません。
このメソッドは、楽観同時実行制御を行います。つまり、他者が先にDBを更新していた場合、DBへの反映は行えず、業務エラーとなります。
業務エラーは、一般的に想定されるエラーと見なされます。しかし、想定されるエラーだからと言って戻り値で返してはいけません。想定されていようがいなかろうが、DBへの反映を行うメソッドでDBへの反映ができないのであれば、それは紛れもない「例外的事象」なのです。

「じゃあメソッド名を変えれば良い」と思うかもしれません。例えば先ほど挙げた 「DBへ反映できるようなら反映するメソッド」 というメソッド名でしょうか?でもこれ、一つのメソッドで二つの機能を表してますね。可能であるかどうかを調べ ( 検証 ) 、可能であればHogeを反映 ( 実行 ) するわけです ( 内部のロジックがどうであれ使う側にしてみればそうなります ) 。構造化の観点からすると、一つのメソッドが複数の機能を表すのはよろしくありません。

では、実行用のメソッドの他に検証用のメソッドを用意すればいいかというと、一概にそうとは言えません。
基本的に検証用メソッドは、例外発生のコストによるパフォーマンスの低下が無視できない状況であったり、前もって検証を行いたい理由がある場合 ( これは業務エラーの表現方法に非依存 ) に用意します。

なお、検証メソッドは検証結果を戻り値で返します。検証メソッドは、言うまでもなく検証を行うためのメソッドなので、結果を戻り値で返すのは自然 ( 当然 ) なことです。


[ 2-2. コスト ]
例外にはコストがかかります。
でも、基本的には問題にならないはずです。
例えば、DBの更新を非接続型データアクセスにて行う場合楽観同時実行制御が行われます。この時、他者が既にDBの更新を行っていたために更新に失敗した場合、これは業務エラーとなります。でもこれ、そう頻繁に発生することではありません ( 頻繁に発生するのなら楽観同時実行制御では不適切です ) 。頻繁に発生しない業務エラーで多少レスポンスが悪くても、それは問題にはならないでしょう。

では頻繁に発生する業務エラーがあったとします。これも問題にはなりません。2-1にて前述したように、「例外発生のコストによるパフォーマンスの低下が無視できない状況」では、検証用のメソッドを用意するからです。
例えば、エンドユーザーからの入力値の妥当性検証は必ず行います。エンドユーザーからの入力値が不正な値であることは ( 故意であろうがなかろうが ) よくあることです。入力値の妥当性検証は、パフォーマンスの向上以外にも、可読性の大幅な向上にも繋がります。

ちなみに、入力値の妥当性を検証した後の処理では、入力値は必ず妥当であるはずですので、もし検証した後の処理で入力値が妥当でないことが検出された 場合、それは業務エラーではなくシステムエラーとなります。これを表す例外クラスは、大抵の場合 ArgumentException クラス、またはその派生クラスとなります。


【 3. 注意点 】
ここまでで、業務エラーを例外で表すことの素晴らしさを語ってきました。
次に、業務エラーを例外で表す際の注意点をいくつか挙げます。


[ 3-1. 適切な実装の上で必要となるのは判別情報のみ ]
判別情報、つまり例外の場合は型です。各業務エラーに応じた適切な例外クラスを用意し、それぞれをハンドルできるようにします。
Exception クラスの持つ各プロパティ ( Exception.Message プロパティや Exception.StackTrace プロパティ ) は、業務エラーが適切に処理されている限り利用することはありません。例えば、 Exception.Message プロパティをそのままエンドユーザーに表示したりしてはいけません。例外クラスの持つ各プロパティを利用するのは、例外の処理漏れ ( ハンドルされない例外の発生 ) があった場合となります ( デバッガやログファイルを通して参照します ) 。

なお、これらは業務エラー・システムエラー問わず言える事です。 ( システムエラーの場合、SQLException.Numberプロパティなど、型以外に判別情報を提供している場合もありますが。。。 )


[ 3-2. 業務エラーはExceptionクラスから派生 ]
Microsoftは以前、アプリケーション固有の例外はApplicationExceptionから派生させることを推奨していました。しかし、現在はこれを推奨していません。変わりにExceptionから派生させることを推奨しています。

なぜでしょうか?これは、Microsoftに原因があります。
Microsoftはこのようなルールを推奨しようとしていました。

しかし、.NET Framework クラスライブラリ ( 以降、FCL ) 自身がこのルールを守れなかったのです。実は、FCLに用意されている例外の中には、ApplicationExceptionから派生しているクラスや、Exceptionクラスから直接派生しているクラスが紛れ込んでいます。FCLがルールを守れていない以上、このルールはもう台無しです。ApplicationExceptionから派生させる意味はなくなります。


【 4. まとめ 】
  • 業務エラーは例外としてスローし、上位で適切な処置を行う必要があります。
  • 業務エラーを例外で表すことにより、可読性・保守性の向上に繋がります。
  • 業務エラーを例外で表しても、適切な設計を行えばパフォーマンスに問題はありません。
  • 業務エラーを表す例外に対して適切な処理を行っている限り、型以外の例外情報は不要です。
  • 業務エラーを表す例外がハンドルされなかった場合、例外情報がとても重宝します。
  • 業務エラーを表す例外はException クラスから派生させます。


【 5. 参考文献 】

プログラミング Microsoft .NET Framework 第2版
この記事を書くに当たって、この書籍の例外に関するページが大変参考になりました。業務エラー・システムエラーという観点では特に書かれていませんが、是非一読してみることをお勧めします。


スポンサーサイト



タグ: .NET C# 例外処理