万能な書き方は存在しない?
あれから色々と試行錯誤しましたが、結局何がいいかよくわからなくなりました。
例外処理の Try…Catch はThrowして上位に持っていくほうが良いのでしょうか。そう思って作ったは良いのですが、思ったように行かず、とりあえず簡単な形から考えていこうかと思いました。
基本的にはメソッド内でエラーが出ると、その上位のメソッドで例外となり、 Try…Catch に入るようです。以下、例を出すとこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try dilv2() Catch ex As Exception Dim st As StackTrace = New System.Diagnostics.StackTrace(ex, True) MsgBox(ex.TargetSite.DeclaringType.Name + ":" + ex.TargetSite.Name + ":" + st.GetFrame(0).GetFileLineNumber(), vbOKOnly, "") End Try End Sub Private Sub dilv2() Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb End Sub End Class |
ここで発生させる例外は ‘System.DivideByZeroException’ です。
そして、結果がこんな感じです。
メッセージの通り、発生するエラー行が出ており、予想通り、ほしい結果となりました。まぁ、何もわざわざメッセージボックスでなくてもいいんですけどね。
例えばこんな問題点があります
ただし、メソッド内の Try…Catch の中のなんでもない普通のところで例外が出た場合、そのメソッドのCatchで捕捉されるが、上位へThrow(再スロー)されたとき、発生場所の例外は上書きされてしまうみたいです。
例を出すと、以下のような感じです。※先程の例でもそうでしたが、ここで発生させる例外はThrow exceptionではないということに注意です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try dilv2() Catch ex As Exception Dim st As StackTrace = New System.Diagnostics.StackTrace(ex, True) MsgBox(ex.TargetSite.DeclaringType.Name + ":" + ex.TargetSite.Name + ":" + st.GetFrame(0).GetFileLineNumber(), vbOKOnly, "") End Try End Sub Private Sub dilv2() Try Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb Catch ex As Exception Throw End Try End Sub End Class |
その結果が、こちらです。
補足された結果の行が、17行目。本来は’System.DivideByZeroException’の発生は15行目ということであるので、おかしいですね…。これでは、発生源の例外の場所が特定できなくなります。
ということは、こういう使い方は良くない(想定されていない)ということでしょうか。
例えば、どこでエラーが出るかわからないから、とりあえず全部 Try…Catch で囲っておけば良いなんてことはするなということでしょうか。
常識?的に考えて、プログラムを作るときは、例外出現を含めた動作まで、作者はしっかり把握しておかないといけないということなのでしょうね。
当たり前だと思いますが、みんなそうやっているのでしょうか。
出現場所ごとに Try…Catch を設置してみる
では、出現場所に Try…Catch を設置するということならば、こんな感じなでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try dilv2() Catch ex As Exception Dim st As StackTrace = New System.Diagnostics.StackTrace(ex, True) MsgBox(ex.TargetSite.DeclaringType.Name + ":" + ex.TargetSite.Name + ":" + st.GetFrame(0).GetFileLineNumber(), vbOKOnly, "") End Try End Sub Private Sub dilv2() Try dilv3_1() Catch ex As Exception Throw End Try Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb Try dilv3_2() Catch ex As Exception Throw End Try End Sub Private Sub dilv3_1() Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb End Sub Private Sub dilv3_2() Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb End Sub End Class |
うーん。いちいち出現箇所に Try…Catch を設置すると、単純にコードが長くなりますよね。
いやいや、そうならないように考えてプログラムを組むのが仕事ですよね。という感じでしょうか。
結局、やり方が色々とあるため、そこからうまいことやってくれというところに帰属しそうですね。そんな現実を受け止めつつ、改めてこの問題を真正面から解決しようという、今回はそのコンセプトで、まずは2通り考えてみました。
- 上書きされる始めの例外は発生時に別の場所に保管しておくパターン
- 発生時のメッセージのみを捕捉表示するパターン
次から説明していきます。
始めの例外は発生時に別の場所に保管しておくパターン
簡単に言うと、再スローで消えてしまうなら、その前に一旦どこかに退避させておけば良いというやり方です。方法は色々ありますが、今回はこんな感じで書きました。ほんの一例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Public Class Form1 Private ddd As String = "" Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try dilv2() Catch ex As Exception Dim st As StackTrace = New System.Diagnostics.StackTrace(ex, True) Dim eee As String = st.GetFrame(0).GetFileLineNumber().ToString() If ddd <> eee Then eee = ddd & "-" & eee End If MsgBox(ex.TargetSite.DeclaringType.Name + ":" + ex.TargetSite.Name + ":" + eee, vbOKOnly, "") End Try End Sub Private Sub dilv2() Try Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb Catch ex As Exception Dim st As StackTrace = New System.Diagnostics.StackTrace(ex, True) ddd = st.GetFrame(0).GetFileLineNumber().ToString() Throw End Try End Sub End Class |
そして、結果がこちらです。
一応、出現箇所は補足できましたが、例外の中でIf文を書くことになり、その中で例外が起きたらとか、ここで色々書くのはどうなのかとか、指摘されそうな問題はありそう。そして、ややこしい。うーん、微妙です。
発生時のメッセージのみを捕捉表示するパターン
こちらは、階層にとらわれること無く、例外が出たところで表示やログ取りなど色々処理を済ませてしまうということです。こちらのほうが単純で良いかもしれません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Public Class Form1 Private ddd As String = "" Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try dilv2() MsgBox("AfterMessage") Catch ex As Exception exmsg(ex) End Try End Sub Private Sub dilv2() Try Dim aaa As Integer = 0 Dim bbb As Integer = 0 Dim ccc = aaa \ bbb Catch ex As Exception exmsg(ex) End Try End Sub Private Sub exmsg(ByVal exa As Exception) Dim st As StackTrace = New System.Diagnostics.StackTrace(exa, True) MsgBox(exa.TargetSite.DeclaringType.Name + ":" + exa.TargetSite.Name + ":" + st.GetFrame(0).GetFileLineNumber(), vbOKOnly, "") 'ログ処理とか End Sub End Class |
その結果がこちらです。
多少、部分的に手を抜いてますが、 exmsgのメソッドで出た例外はまた別で捕捉して処理する形になります。
ただ、これの問題は Try…Catch の出現箇所で一旦処理が完了しているため、始めのButton1_Clickメソッドに戻ると通常通りその先の処理を続けることになります。(ここでは、その後のメッセージボックスが普通に出る。)
これにより、例外時に必要だった処理があった場合はそれが抜けて、その後の処理でエラー例外が連続して出てきてしまいます。これもこれで、やっかいな問題です。
そういうときは、処理の後でThrowすれば良いのかなと思いましたが、そうすると上位のメソッドでまた例外メッセージが出てしまいます。うーん、これも微妙ですね。
結局はしっかり把握して作りなさいということ
こうなると、経験と勉強が足りないということですかね。
色んなところで色んな人が書いたプログラムを見て、いつか自分にしっくり来るものがあれば、それを使えばいいかと。思った次第です。
コメント