[VBA] 関数の引数を変更した場合にコンパイルエラーが多発するのを防ぐテクニック

概要

この記事について

プログラミングにおいて、
コードの保守性(他人や未来の自分から見て理解のしやすさ・変更の容易さ)を向上させるため、
外部から見た時の挙動は変えずに、プログラムの内部構造を変更するという作業が発生する場合がある。

これを リファクタリング と呼ぶ。

リファクタリングの際、関数の引数の数を減らしたり増やしたりすることがあるかもしれない。
すると、VBE(VBAのエディタ)でコンパイルが行われる際に、
その関数の使用箇所全てでコンパイルエラーが起こる可能性がある。

コンパイルエラー

これを防ぐために、
コンパイルエラーを一時抑制するテクニックを使用すると便利。

そのテクニックについて記したい。

説明のために作成したExcelファイルとソースコードはこちらでダウンロードできます。

作成環境

Windows 10 Home(64bit)
MSOffice 2016

事例

前提

次のように、
一つの関数が複数の関数によって
呼び出されている場合を考える。

関数依存関係

ここではcallingFunc01、02のみがcalledFuncを呼び出しているが、
たいていのVBAツールやVBAアプリケーションでは
一つの役割を持つ関数は3つ以上の多数の関数に呼び出しをされることもあるだろう。

コード

callingFunc01

 1'******************************************************************************************
 2'*関数名    :呼び出し元関数No.1
 3'*機能      :
 4'*引数      :
 5'******************************************************************************************
 6Public Sub callingFunc01()
 7    
 8    '定数
 9    Const FUNC_NAME As String = "callingFunc01"
10    
11    '変数
12    Dim result As Long
13    
14    On Error GoTo ErrorHandler
15
16    If Not calledFunc(result, 13.54, 28.3) Then GoTo ExitHandler
17    result = result + 1000
18    Debug.Print result 'output 1383
19    
20ExitHandler:
21
22    Exit Sub
23    
24ErrorHandler:
25
26    MsgBox "エラーが発生したため、マクロを終了します。" & _
27           vbLf & _
28           "関数名:" & FUNC_NAME & _
29           vbLf & _
30           "エラー番号:" & Err.Number & vbNewLine & _
31           Err.Description, vbCritical, "マクロ"
32        
33    GoTo ExitHandler
34        
35End Sub

callingFunc02

 1'******************************************************************************************
 2'*関数名    :呼び出し元関数No.2
 3'*機能      :
 4'*引数      :
 5'******************************************************************************************
 6Public Sub callingFunc02()
 7    
 8    '定数
 9    Const FUNC_NAME As String = "callingFunc02"
10    
11    '変数
12    Dim result As Long
13    
14    On Error GoTo ErrorHandler
15
16    If Not calledFunc(result, 12.5, 33.33) Then GoTo ExitHandler
17    result = result + 5000
18    Debug.Print result 'output 5416
19    
20ExitHandler:
21
22    Exit Sub
23    
24ErrorHandler:
25
26    MsgBox "エラーが発生したため、マクロを終了します。" & _
27           vbLf & _
28           "関数名:" & FUNC_NAME & _
29           vbLf & _
30           "エラー番号:" & Err.Number & vbNewLine & _
31           Err.Description, vbCritical, "マクロ"
32        
33    GoTo ExitHandler
34        
35End Sub
36

calledFunc

 1'******************************************************************************************
 2'*関数名    :呼び出される関数
 3'*機能      :引数の2数の乗算を行い、小数点を切り捨てし整数として返す
 4'*引数      :参照渡しで結果を返却する変数
 5'*引数      :乗算される数値1
 6'*引数      :乗算される数値2
 7'*戻り値    :True > 正常終了、False > 異常終了
 8'******************************************************************************************
 9Public Function calledFunc(ByRef returnNum As Long, ByVal num01 As Double, ByVal num02 As Double) As Boolean
10    
11    '定数
12    Const FUNC_NAME As String = "calledFunc"
13    
14    '変数
15    
16    On Error GoTo ErrorHandler
17
18    calledFunc = False
19    
20    returnNum = Int(num01 * num02)
21
22
23TruePoint:
24
25    calledFunc = True
26
27ExitHandler:
28
29    Exit Function
30    
31ErrorHandler:
32
33    MsgBox "エラーが発生したため、マクロを終了します。" & _
34           vbLf & _
35           "関数名:" & FUNC_NAME & _
36           vbLf & _
37           "エラー番号:" & Err.Number & vbNewLine & _
38           Err.Description, vbCritical, "マクロ"
39        
40    GoTo ExitHandler
41        
42End Function
43

リファクタリングで引数を増やすと起こること

例えば、
result = result + 1000
ここの加算の部分も
呼び出し元ではなくcalledFunc側で処理するようにリファクタリングしたい場合があるとする。

すると、calledFuncの引数を増やさなければいけない。

 1'******************************************************************************************
 2'*関数名    :呼び出される関数
 3'*機能      :引数の2数の乗算を行い、小数点を切り捨てし整数を得る。
 4'*      得た整数に整数を加算し、返す
 5'*引数      :参照渡しで結果を返却する変数
 6'*引数      :乗算される数値1
 7'*引数      :乗算される数値2
 8'*引数      :加算される整数値
 9'*戻り値    :True > 正常終了、False > 異常終了
10'******************************************************************************************
11Public Function calledFunc(ByRef returnNum As Long, ByVal num01 As Double, ByVal num02 As Double, ByVal addNum As Long) As Boolean
12    
13    '定数
14    Const FUNC_NAME As String = "calledFunc"
15    
16    '変数
17    
18    On Error GoTo ErrorHandler
19
20    calledFunc = False
21    
22    returnNum = Int(num01 * num02)
23    returnNum = returnNum + addNum
24
25
26TruePoint:
27
28    calledFunc = True
29
30ExitHandler:
31
32    Exit Function
33    
34ErrorHandler:
35
36    MsgBox "エラーが発生したため、マクロを終了します。" & _
37           vbLf & _
38           "関数名:" & FUNC_NAME & _
39           vbLf & _
40           "エラー番号:" & Err.Number & vbNewLine & _
41           Err.Description, vbCritical, "マクロ"
42        
43    GoTo ExitHandler
44        
45End Function
46

このままコンパイルを実行する(もしくは自動でコンパイルが実行される)と、
それぞれの呼び出し元で、
コンパイル時にエラーが発生する。

引数の増加で誘起されたコンパイルエラー

厄介なのが、
このエラーを解消するためには
すべての呼び出し元の関数のcalledFunc呼び出し箇所で、
追加の引数を設定しなければならないこと。

上の事例のようなシンプルな関数ならば
影響範囲も狭く、特に変更に時間はかからない。

しかし、より複雑な関数や引数となると、
すべての変更箇所を対応しないことには、
コンパイルエラーが解消できず、
動作検証もできなくなってしまう。

Optionalキーワードの使用は?

エラーを起こらなくさせるために、
一時的にOptional(省略可能引数)キーワードを使うという方法がある。

1Public Function calledFunc(ByRef returnNum As Long, ByVal num01 As Double, _
2    ByVal num02 As Double, Optional ByVal addNum As Long = 0) As Boolean

Optional ByVal addNum As Long = 0 とすることによって、
エラーは解消され、
設定されていない呼び出し元においては全て0である数値をaddNum引数とするとみなされて
処理が行われる。

しかし、この場合怖いのは 戻し忘れ である。

コンパイルが通ってしまうばっかりに、
せっかくリファクタリングするつもりの呼び出し部分で引数の追加を忘れたり、
加算したつもりがされていなかったりするヒューマンエラーを招きかねない。

そのため、
一時的にコンパイルエラーを抑制し、
なおかつ変更の反映のできていない部分では最終的にチェックできるようなテクニックが望まれる。

テクニック

条件付きコンパイル引数

VBAプロジェクトには、
条件付きコンパイル引数という設定項目がある。

こちらのサイト様に詳細な説明があります。
条件付きコンパイル ー VBAでコードを選択的に実行する(LINK)

引用元;上記サイト
引用元;上記サイト

上記のように、
プロジェクトのプロパティの画面で
条件付きコンパイル引数を設定することができる。

これにより、
開発時の環境、動作検証時の環境、本番時の環境などを
分けることができる。

今回は、DEVELOP_MODEという引数を設定し、
値を-1とする(本当はTrueとしたいが、条件付きコンパイル引数はBoolean値を受け付けないため)。

条件付きコンパイル引数

コードの変更

次のように、
条件付きコンパイルのIF構文を用いて、
環境ごとに関数の宣言を分ける。

 1'******************************************************************************************
 2'*関数名    :呼び出される関数
 3'*機能      :引数の2数の乗算を行い、小数点を切り捨てし整数を得る。
 4'*      得た整数に整数を加算し、返す
 5'*引数      :参照渡しで結果を返却する変数
 6'*引数      :乗算される数値1
 7'*引数      :乗算される数値2
 8'*引数      :加算される整数値
 9'*戻り値    :True > 正常終了、False > 異常終了
10'******************************************************************************************
11#If DEVELOP_MODE Then
12    Public Function calledFunc(ByRef returnNum As Long, ByVal num01 As Double, ByVal num02 As Double, Optional ByVal addNum As Long = 0) As Boolean
13#Else
14    Public Function calledFunc(ByRef returnNum As Long, ByVal num01 As Double, ByVal num02 As Double, ByVal addNum As Long) As Boolean
15#End If
16
17    
18    '定数
19    Const FUNC_NAME As String = "calledFunc"
20    
21    '変数
22    
23    On Error GoTo ErrorHandler
24
25    calledFunc = False
26    
27    returnNum = Int(num01 * num02)
28    returnNum = returnNum + addNum
29
30
31TruePoint:
32
33    calledFunc = True
34
35ExitHandler:
36
37    Exit Function
38    
39ErrorHandler:
40
41    MsgBox "エラーが発生したため、マクロを終了します。" & _
42           vbLf & _
43           "関数名:" & FUNC_NAME & _
44           vbLf & _
45           "エラー番号:" & Err.Number & vbNewLine & _
46           Err.Description, vbCritical, "マクロ"
47        
48    GoTo ExitHandler
49        
50End Function

■上のコードでわかるように、
開発時(DEVELOP_MODEが-1)の場合は
追加した引数はOptionalとして扱われるため、
コンパイルエラーは抑制される。

■本番時あるいは動作検証時には、
DEVELOP_MODEをFalse、すなわち
DEVELOP_MODE = 0とする。

そのときは、
Optional(省略可能)ではない扱いであるため、
未対応の呼び出し元コードが存在すれば、
しっかりコンパイルエラーが発生してくれる。

VBEの不思議な挙動

一点、VBE(VBAのエディター画面)に
不思議な挙動が発生することを記したい。

開いたモジュールの関数の一覧欄で、
上記で#IF構文を使用した関数が、
二重で計上されている。

二重の計上

コードを実行してもエラーは起こらないので、
表示上のただのバグかと思われるけど。

サンプルとソースコード

こちらのリンクをご参照ください。

関連記事

comments powered by Disqus