プログラムのエラーは、原因や発生するタイミングなどで分類されます。分類の方法や考え方には様々ありますが、Excel VBAでマクロを開発中に発生するエラーを、ここでは次のように分類します。
構文エラーは、いわゆる"打ち間違い"です。オブジェクト名やプロパティ名のスペルを間違えたり、括弧の数が合わなかったり、カンマ(,)を入力しなければいけない場所にコロン(.)を打ってしまったり…そうした原因によるエラーです。
コンパイルエラーは、マクロのコードがコンパイルされるときに発生するエラーです。ここでは、主に論理的なエラーがチェックされます。たとえば、変数の宣言を強制する設定をしたにも拘わらず、宣言しないで変数を使ったようなケースです。
実行時エラーは、マクロを実行して初めて露呈するエラーです。コンパイルエラーと同じように、論理的な間違いが多く、たとえば、ワークシートが3枚しか存在しないのに4枚目のワークシートを操作しようとしたり、存在しないブックを開こうとしたときなどに発生します。
実行時エラーには、それぞれ固有のエラー番号が割り当てられていて、Errオブジェクトを使えば、マクロのコード内で「どんなエラーが発生したか」を判定できます。On Errorステートメントなどを使うことで、あらかじめ想定したエラーに対する対処をプログラミングしておくことが可能です。これをエラートラップと呼びます。
われわれがVBEのコードペインでマクロを作成するとき、裏でVBAが何をしているのかを解説します。
(1)まず、ユーザーが1行分の命令を入力して、行末でEnterキーを押します。
(2)するとVBAは、いま入力された行のスペルミスなど構文チェックをして問題がなければ、その行を中間コードにコンパイルします。この中間コードをPコードと呼びます。
(3)[F5]キーを押すなどして、このマクロを実行すると、それまでコンパイルしてあった中間コード(Pコード)をVBAが理解できる最終的なコードにコンパイルしてから、実行します。
このように、VBAは2段階のコンパイルを行います。行末でEnterキーを押しただけでも「コンパイルエラー」が起こるのは、最初のコンパイルでエラーが発生するからです。
このことはヘルプにも記載されています。Excel 2007 VBEのヘルプ[Excel 2007 開発者用リファレンス ]-[Visual Basic 開発環境]-[構文チェックの設定と解除の切り替え]には、
Visual Basic には、次のような構文チェック機能があります。
- キーワードの入力間違いや区切り記号の入力漏れなど、ステートメントが入力されるごとに、その構文エラーをチェックし、エラーがあるときにはそれを通知します。
- 構文が正しいときはコードを内部形式に変換し、実行時の変換速度を向上させます。
と書かれています。この「ステートメントが入力されるごとに」のステートメントとは、IfステートメントやFor Nextステートメントなどを表すステートメントではなく、おそらく"ひとつの命令文"という意味のステートメントでしょう。VBAでは1行に1つの命令文を書くのがルールですから、「ステートメントが入力されるごとに」は「1行が入力されるごとに」という意味だと思います。
上の解説図のとおり、マクロを実行しようとすると、まずコンパイルが行われます。また、マクロを実行しなくても、VBEの[デバッグ]-[VBAProjectのコンパイル]を実行すれば、いつでもコンパイルできます。コンパイルが行われるとき、マクロのコード中に論理的な間違いがあればコンパイルエラーが発生します。主なコンパイルエラーについては、下記ページで解説します。
コンパイルが正常に完了するとマクロが実行されるわけですが、実行途中でしか発見できないエラーもあります。それが実行時エラーです。
たとえば、ワークシートが3枚しか存在しないブックで、次のマクロを実行すると「インデックスが有効範囲にありません」という実行時エラーが発生します。
Sub Sample1()
Dim i As Long
For i = 1 To 4
Cells(i, 1) = Worksheets(i).Name
Next i
End Sub
ワークシートが3枚しかないということは、Worksheets(1)・Worksheets(2)・Worksheets(3)しか操作できないということです。Worksheetsコレクションにはメンバーが3つしかありません。なのに上のコードは「For i = 1 To 4」と、存在しない4枚目のワークシートを操作しようとしています。だからエラーです。マクロは、プログラミングされたことしかできません。プログラミングされたら、たとえそれが間違った命令だったとしても、馬鹿正直に実行しようとします。その結果、エラーになります。「だってぇ…やれって言われたから」みたいな。ビジネスの現場では、こんなヤツ使い物になりませんね。「お前な、言われなくたって、ちょっと考えればわかるだろ?」と苦言のひとつも言いたくなります。まぁ、とにかく。エラーの原因と責任は、エラーになるコードを書いたプログラマにあります。
エラーが起こってもマクロを停止させないためには、エラーの発生を予想して、先手を打たなければなりません。そこで使うのがOn Errorステートメントです。On Errorステートメントは「もしエラーが起こったら○○しろ」という"ルールを決める"ためのステートメントです。On Errorステートメントでよく使うやり方は次の2つです。
「On Error Goto ラベル」は、「もしエラーが起こったら'ラベル'の行にジャンプしろ」という命令です。たとえば次のように使います。
Sub Sample2() Dim i As Long On Error GoTo ErrorRoutine For i = 1 To 4 Cells(i, 1) = Worksheets(i).Name Next i Exit Sub ErrorRoutine: MsgBox "エラーが発生しました" End Sub
ラベルは「ラベル名:」と入力します。
上のコードは「もしエラーが起こったらErrorRoutineの行へジャンプしろ」と指示しました。忘れてならないのは、ラベルErrorRoutineの直前にあるExit Subです。ラベルは、コード内の単なる印に過ぎません。On Errorステートメントで指定したとはいえ、その他の行と何ら変わりません。なので、もしエラーが起こらなかったときには実行させないように、正常なときは、その直前でマクロを終了させなければなりません。それがExit Subです。
もうひとつの「On Error Resume Next」は、「もしエラーが起こっても、そのエラーを無視して、次の命令から先を実行しろ」という命令です。次のように使います。
Sub Sample3() Dim i As Long On Error Resume Next For i = 1 To 4 Cells(i, 1) = Worksheets(i).Name Next i End Sub
実行してみるとわかりますが、Worksheets(4)を操作することによって起こる実行時エラーは無視されます。
エラーが無視されるということは、マクロがエラーで止まらないということです。これはこれで便利なケースも多いのですが、止まらないからといって"エラーが起きていない"とは断言できません。エラーを無視したときは、実際にはエラーが起きていたのかどうかを確かめなければなりません。そこで使うのがErrオブジェクトです。
Errオブジェクトは、マクロ実行中に発生したエラーが格納されるオブジェクトです。つまり、Errオブジェクトを調べれば、実際にはエラーが起きていたのかどうかを判定することができるのです。
Errオブジェクトにはいくつかのプロパティがありますが、その中のNumberプロパティには、発生したエラーの"エラー番号"が格納されます。Numberプロパティが0でないということは、何らかのエラーが発生したということです。
Sub Sample4() Dim i As Long On Error Resume Next For i = 1 To 4 Cells(i, 1) = Worksheets(i).Name Next i If Err.Number <> 0 Then MsgBox "エラーが発生しました" End If End Sub
このへんがエラー対策の基本です。On ErrorステートメントやErrオブジェクトを使って、できうる限りのエラーに対して先手を打ち、エラーで止まらない頑強なマクロを心がけてください。なお、主な実行時エラーの一覧と解説は、下記ページをご覧ください。