[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構文を使用した関数が、
二重で計上されている。
コードを実行してもエラーは起こらないので、
表示上のただのバグかと思われるけど。
サンプルとソースコード
こちらのリンクをご参照ください。
関連記事
- [VBA] 例外処理の典型的なパターン&使用例サンプル
- [VBA] クラスを利用するメリットと方法について & 簡単なサンプル(1)
- [Excel VBA] 個人的に作業がはかどった自作Excelショートカット
- [Excel VBA]ポリモーフィズムを用いて、IF文を使わずラジオボタンごとの処理分岐を行う
- [Excel VBA]選択フォルダ配下のエクセルブックの全シートでA1にカーソル移動させるツールを作成した