2007年4月27日金曜日

.NET 例外処理

私がNETを始めたのはVS2003です。
その頃に購入した本では例外処理は以下のように書かれていました。
・独自例外はApplicationExceptionから派生して定義する。
・業務エラーも例外として扱う。
つまり画面に配置されたコントロールの入力エラーも例外をスローしていたのです。

けれど現在は以下のような事を聞きます。
・独自例外はExceptionから派生して定義する。
・業務エラーは戻り値で表す。
それでも業務エラーも例外として扱うべき的な意見も聞きます。

なぜこのような事になったのでしょうか?
Microsoftは以前、以下のようなルールを採用していました。
・CLRがスローする例外は SystemException クラス (System) 派生の例外
・アプリケーションがスローする例外は ApplicationException クラス (System) 派生の例外
つまりシステム例外とアプリケーション例外を明確に区別し、アプリケーション例外である独自の例外を定義する際はApplicationExceptionから派生して定義すること推奨していたのです。

しかし、現在はこれを推奨していません。変わりにExceptionから派生させることを推奨しています。理由は.NET Framework クラスライブラリ 自身がこのルールを守れなかったからのようです。.NET Framework クラスライブラリ に用意されている例外の中にApplicationExceptionから派生しているクラスや、Exceptionクラスから直接派生しているクラスがあるのです。.NET Framework クラスライブラリ がルールを守れていない以上、ApplicationExceptionから派生させる意味はなくなります。

では現在の例外処理の一般的な実装方法はどうすればよいのでしょうか?
業務エラーは戻り値で返すのでしょうか?
それとも例外で返すのでしょうか?

業務エラーを例外で返す場合、
例えば例外をスローする可能性のあるメソッドを呼び出した際、例外が発生したとしても、呼び出し側で特に処理をしなくても例外はそのまま上位へと伝わり適切な箇所で処理が行われます。

業務エラーを戻り値で返す場合、
呼び出し側で戻り値を適切に判断し損ねてしまうと、アプリケーションは「一見」正常な動作を行いながら不整合な処理を行うかもしれません。また不具合の発見も困難になるかと思います。

業務エラーを例外で処理する方が利がありそうです。
ですが業務エラーを例外として扱うと違和感のあるメソッドもあります。例えばチェック系メソッドを考えた場合、入力エラーがあったコントロールにフォーカスを与えるなどよくあると思います。チェック系メソッド内で複数のコントロールの入力チェックを行ったとしましょう。入力エラーのあったコントロールにFocusを与え例外をThrowしメッセージは最上位で表示することになってしまいます。なんだか違和感がありませんか?この場合はチェック系メソッド内でメッセージを表示し該当コントロールにフォーカスを与え戻り値でエラーがあったフラグを返すとスッキリします。

現時点で、業務エラーを例外で返すか戻り値で返すかについてはCaseByCaseなのでしょう。事前にできるだけのチェックを行い、このチェックで発生する業務エラーは戻り値で処理。それでも発生するエラーは例外をスローします。

Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 
Try
'入力チェックを行います。
If IsInputCheck = False Then
Return 
End If

'更新処理を行います。
Call Update()

Catch ex As Exceptionから派生した独自例外
MessageBox.Show(ex.Message,"システム名")
Catch ex As Exception
MessageBox.Show("予期しないエラーが発生しました。" & ex.ToString)
'ログを残す
Call CreateLog(ex.ToString)
End Try
End Sub

Private Function IsInputCheck() As Boolean
Try
If ("").Equals(Me.TextBox1.Text) Then
MessageBox.Show("TextBox1は必須入力です。","システム名")
Me.TextBox1.Focus()
Return False
End If
If Me.ComboBox1.SelectedIndex = -1 Then
MessageBox.Show("ComboBox1を選択してください。","システム名")
Me.ComboBox1.Focus()
Return False
End If
Catch ex As Exception
Throw
End Try
End Function

Private Sub Update()
Try
      If 登録しようとした主キーのIDが他のユーザによりすでに登録されていた場合 Then
Throw New Exceptionから派生した独自例外("すでに登録されています。")   
End If
     If 更新しようとした主キーのIDが他のユーザにより削除されていた場合 Then
Throw New Exceptionから派生した独自例外("すでに削除されました。")   
End If
'DB永続化処理を行います。
Catch ex As Exception
Throw
End Try
End Sub 


コードを見て何かお気づきでしょうか?
恥ずかしながらThrowしているだけなのに例外をCatchしています。
これは設計者の俺様ルールで
「デバッグするとき例外が発生したら最上位のCatchでコードが止まるのが不便」
という理由ですべてのコードにThrowするだけのTry~Catchを書かなくてはいけないのです。
これってどうなんでしょう?

0 件のコメント: