不具合を防ぐエクセルマクロの記述法

※エクセル・・・Excel。会計や出た解析等に利用されるマイクロソフト社の表計算ソフト
※マクロ・・・一連の操作を自動実行する機能の一つ

知っているようで知らない不具合防止の意義

不具合の量は、発注者に問題を発生させます。
開発中であれば納期の遅延であり、運用開始を遅らせる要因となり得ます。
次に問題となるのは運用中に確認された不具合です。発生すること自体が運用の妨げになりますが、問題はその被害の量です。これを左右するのは、修正期間の長さです。一般的に不具合が発生しても一両日中に修正されますが、必ずしもそうとは限りません。発生頻度や再現性等の原因を突き止める要素が関わるためです。
つまり、運用中に見つかった不具合に被害の量は、原因特定し易さに関わるのです。円滑な運用を実現できるのは、開発者の不具合防止の取り組み姿勢によると言い換えてもいいでしょう。

ExcelのVBAマクロは、業務システムに向くのか?

ExcelのVBA(Visual Basic for Applications プログラミング言語)は、スレッド(thread。複数処理を平行実行する仕組みで、平行実行の最小単位を指す)や排他制御(複数の実行単位が共有する資源を占有して利用するために、他の実行単位が利用できないようにする処理)、オブジェクト指向(プログラミング技法の一つで、データをモノとして捉えモノ同士の処理のやり取りをプログラミング化する)等の小難しい知識がなくても実装できるお手軽な言語ですが、不都合な点もあります。その一つが、システム開発では厄介な問題の一つでもある不具合を起こしやすく見つけにくい要素がある事です。例えば、変数の型宣言をしなくても実行でき、しかも実行前にエラーとして表示してくれません。このような要素があると、業務システムとしては不向きと考えるかもしれません。
しかし、不具合を起こさず、例え起こっても原因特定を容易にするマクロの記述方法があります。これにより、注意を払えば業務システムでも十分利用可能です。以下では、その手法の一部を紹介します。

強制変数宣言して不具合を防止する

デフォルトの設定で、VBAは変数宣言なしで変数を利用できてしまいます。そのために、意図通りに動作しない事が起こり得ます。
例えば

Sub Macro1()
    Dim value As Long
    value = 1    
    If 0 = valuea Then
        Exit Sub
    End If
    
    Range("A1").value = value
End Sub
上のマクロコードでは、「value」と「valuea」を打ち間違えがあります。しかし、設定によってはVBAでは宣言なしに変数を利用できるため、エラーとはなりません。そして、valueaに0が入っているため、セルA1には何も書きこまれず、期待通りに動作しないのです。
このような不具合を防ぐ方法があります。マクロの先頭行に「Option Explicit」を入れ、コンパイル(メニュー→VBEのデバッグ→VBAProjectのコンパイル)を掛けて変数宣言を強制しています。

Option Explicit   '< 追加
Sub Macro1()
    Dim value As Long 
 略
End Sub
僅か一行の追加とコンパイルの実行だけですが、不具合の防止効果は絶大です。
変数宣言されていなければ、コンパイル時に変数が定義されていない旨が表示され、先述のような変数名のミスによる不具合が完全に根絶できるようになるからです。

これ以外にも、VBAプロジェクトのコンパイルでは、変数名・マクロ名の重複定義等も分かり不具合を未然に防ぐのに欠かせない機能の一つなっています。

それでも起こる実行時のエラーはどのように防ぐのか

エラーの中にはコンパイル時点では分からず、実行時に分かるものがあります。しかも、その処理を通過しなければ起こりません。
例えば、配列の範囲を超えてインデックスを指定した時です。配列の範囲を超えたアクセスにならないように、LBound/UBoundやfor eachを使い防ぐ事で不具合が防げます。

Option Explicit
Sub Macro2()
    Dim values As Variant
    values = Array(10, 20, 30)

    Dim nIndex As Integer
    For nIndex = LBound(values) To UBound(values) 
        Debug.Print (values(nIndex))
    Next

    Dim value As Variant
    For Each value In values
        Debug.Print (value)
    Next
End Sub
LBound(values)は配列の最小インデックス値である0です。UBound(values)は最大インデックス値の2です。そのため、下記のように固定値に置き換えても動作します。打ち込む量が減るので数が多ければそれだけ編集する工数は短縮できます。

Option Explicit
Sub Macro2()
    Dim values As Variant
    values = Array(10, 20, 30)

    Dim nIndex As Integer
    For nIndex = 0 To 2  ‘ 固定値で記述
        Debug.Print (values(nIndex))
    Next
  略
End Sub
しかし、固定値による記述方法は、配列要素数の変更による編集量を増やし、編集ミスを増やします。結果、不具合の発生確率は増えます。従って、一般的に配列の要素数が変化する可能性を考慮して先述のLBound/UBoundや「for each」を使った記述とします。
尚、LBound/UBoundはインデックス値を利用する時に使用され、 「for each」は必要としない時に使われます。
このように少々面倒でも仕様変更による編集ミスをあらかじめ防ぐ記述方法を選択するだけで、不具合を最小限に抑える事ができます

Debug.Assertで不具合を未然に察知する価値

Debug.Printと比較すると、Assert()はあまり利用されません。Assert(条件)は、通過時に条件が偽であるなら一時停止し利用者にイレギュラーな条件になった事を知らせ、不具合を察知させます。ところが、そもそもの条件が偽であるなら、その後にエラーが発生する可能性が高く、エラー発生前にAssert()を入れる理由がありません。例えば0除算して実行されないと分かっているのに、その前にAssert()で一時停止を入れる事はないでしょう。ソースの位置を特定し難い他の環境ではAssert()はとても重要です。しかし、ソース付きのマクロなら、Assert()が入ってなくても実行時のエラーによりその場所が特定されます。従って、Assert()は不要なのかもしれません。

しかし、必ずしも無用の長物ではありません。
マクロが正常に動作する条件を明示する効果があるからです。下記は価値のないAssert()です。

Sub Macro3(arg0 As Integer, arg1 As Integer)
    Debug.Assert (arg1 <> 0)
    Debug.Print (arg0 / arg1)
End Sub
引数arg1は0では正常動作せず、それを防ぐためにAssert()で一時停止しています。ところが直ぐ後で0除算エラーにより知る事になります。

しかし、次のようなマクロではAssert()を入れる価値があります。

Sub Macro3(arg0 As Integer, arg1 As Integer)
    Debug.Assert (arg1 <> 0)
  If arg0 >100 then
       End sub        ‘ ①
  End If
    Debug.Print (arg0 / arg1)	‘ ②
End Sub
0除算が②で起こりますが、①でマクロを抜けているとエラーが発生しません。
従って、arg0が101かつarg1が0の時は、設定してはいけないarg1の0設定を見逃してしまう可能性があります。このようにAssertは注意喚起する効果があるのです。また、多人数で編集する際、ソース内に明示する事により、他の編集者が意識する機会を増やすメリットがあります。

正常運用の一翼を担うのは、開発者の何?

以上の三つの具体的な手法を利用し不具合を防止する方法を紹介しましたが、これ以外にも様々な方法があります。実行時のログを残し不具合の助けとする方法、利用実績の多いアドインを利用する方法、.Net Frameworkのモジュールを呼び出す方法等です。こうした、引出の数が不具合の件数を抑制し、同時に発生しても原因究明の早さに比例します。つまり、開発者が普段どれだけ知識欲がありどれだけ不具合防止策を取り入れているかが、正常運用の要となるのです。