VBAの案件をやっていて思うのは、「ツールを納品後にほぼ確実に問い合わせ対応することになる」ということです。
超簡単なツールであればないのですが、ある程度外部参照などを使用したツールを作成すると、環境依存の問題が必ず発生します。
(そのような環境依存問題を発生させないようにするのが優れたVBAプレイヤーなのかもしれません。)
その時、エラー処理が適切にされていないとアプリがヤベーことになる可能性もあるので、しっかりと対処をしておきたいところです。
エラーハンドリングをしない場合
エラーに対して完全に無策の場合、エラーが発生した時点でプロジェクトが終了してしまいます。
↓下記で終了を押すとプロジェクト終了

その結果何が起こるかと言うと、後処理が一切できなくなります。
シートに値を書き出す、ボタンを活性化させる、ファイルを書き出すなどの処理はエラーが起きた時点でプログラム実行前に戻してあげる必要があります。
ユーザーはエラーが起きたら、すぐにアプリを中断して問い合わせるようなリテラシーの高い人ばかりではないと思いますしね。
またエラーメッセージもマイクロソフトのデフォルトのものでは、非常にわかりにくいです。
あなたが「こんなエラーが出た」と言って上記の画面を見せられたところで、原因の箇所を洗い出すのは困難ですよね。
予めエラーになりそうなところには、エラー処理を忘れずに入れる
当然ですが、予めエラーが発生しそうなところについては、エラーチェックを行ってから処理を行うのがベストです。
Sub fileCheck()
If Dir("C:\hoge.xlsx") = "" Then
Call MsgBox("C:\hoge.xlsxが見つかりません", vbOKOnly + vbCritical, "エラー")
Dir(vbNullString)
exit sub
End if
'ファイル操作処理
End Sub

上記エラーであれば、「この関数でエラーが起こったのだな」というのを開発者がすぐに特定できるため、エラーへの対応力が上がります。
予期せぬ不具合に対するエラー処理について
VBAには、「On Error」というエラーが発生した時にどうするかを定義できる関数があり、「On Error GoTo xxxxx」と書くことでエラー用の処理に飛ばすことが出来ます。
他の言語で言うと「try catch」文みたいな感じですね。
Sub main()
On Error GoTo errHandle
i = 100 / 0
MsgBox "正常終了したよ"
Exit Sub
errHandle:
MsgBox "異常終了したよ"
End Sub

通常、エラー処理は、関数の最後につけていきます。
何故なら、正常終了した場合には、エラー処理を実行させたくない場合が大半だからです。
そのため正常処理は「Exit Sub」させ、その後にエラー用の処理を書いていきます。
ただ、どの時点でエラーが発生したのか読みにくいため関数名を入れておくのがベストです。行番号も入れられればよいのですが、これはラベルを定義しまくらないと難しいので現実的ではないです。
Sub main()
On Error GoTo errHandle
i = 100 / 0
MsgBox "正常終了したよ"
Exit Sub
errHandle:
MsgBox "異常終了したよ" & vbCrLf & "エラー発生箇所:main" & vbCrLf & Err.Description & vbCrLf & Err.Number
End Sub
ついでに、Err.Descriptionでエラーの説明、Err.Numberでエラー番号を取れるので出しておきましょう。
悩ましいサブルーチンに対するエラー処理
マクロを作成する上で、サブルーチンを使わないことはまずありえないのですが、サブルーチン内でエラーになった場合の対処は結構悩ましいところです。
Sub main()
On Error GoTo errHandle
Call child
MsgBox "main「正常終了したよ」"
Exit Sub
errHandle:
MsgBox "main「異常終了したよ」"
End Sub
Sub child()
Dim i As Integer
On Error GoTo errHandle
i = 100 / 0
Exit Sub
errHandle:
MsgBox "child「異常終了したよ」"
End Sub
上記のmain関数を実行すると…
child「異常終了したよ」→main「正常終了したよ」という順番で表示されてしまいます。
つまりchild関数でエラーを処理した時点で、エラーがクリアされているためmain関数ではchild関数でエラーになったことを検知できないのです。
よくやる方法として…
- 親で全てエラー処理を行う
- 子から親にエラーが起きたかどうかの情報を渡す
というものがあります。大規模なアプリになるほど「子から親にエラーが起きたかどうかの情報を渡す」のほうが良いですね。
親で全てエラー処理を行う
Sub main()
On Error GoTo errHandle
Call child
MsgBox "正常終了したよ"
Exit Sub
errHandle:
MsgBox "異常終了したよ"
End Sub
Sub child()
Dim As Integer
i = 100 / 0
End Sub
上記を実行すると、childでエラーが処理されていないため、mainの「On Error GoTo」が発火して”異常終了したよ”が表示されます。
この場合は問題ないのですが、サブルーチンが増えると適切な後処理を行うことが難しくなるので、マクロが大きくなるにつれて不便になってきます。
子から親にエラーが起きたかどうかの情報を渡す
Sub main()
If Not child Then Exit Sub
MsgBox "正常終了したよ"
End Sub
Function child() As Boolean
Dim i As Integer
On Error GoTo errHandle
child = False
i = 100 / 0
child = True
Exit Function
errHandle:
MsgBox "異常終了したよ"
End Function
child関数がboolean型になっており、その関数がエラーであったかどうかの情報を返します。
まず「child = False」を指定しているので、エラーとなった場合には関数の戻り値がFalseとなります。
呼び元の関数は、その戻り値を見て処理を続行するかやめるかを判断すればよいです。
問題点として、関数の戻り値がBooleanになってしまうので、直感で何をやる関数なのかわかりにくいということですね。
その場合は、ByRefでエラー情報を渡したほうが良いのかもしれません。
自分は、ほぼ返したい変数の方をByRefにしてしまっていますが、改めて見るとコメントなしだとかなりわかりにくい気がします…。
Sub main()
Dim i As Integer
If Not child(i) Then Exit Sub
MsgBox "iは" & i & "だよ"
End Sub
Function child(ByRef i As Integer) As Boolean
On Error GoTo errHandle
child = False
i = 100 / 0
child = True
Exit Function
errHandle:
MsgBox "異常終了したよ"
End Function
コメントを残す