目次
はじめに
VBAで関数を作るとき、変数の値をどこで管理するかは重要な判断です。関数の引数で渡す方法とグローバル変数で共有する方法、それぞれの特徴を理解して適切に使い分けることが大切です。
前回の記事で引数を使った理由
前回の記事では、3つの関数を組み合わせてデータ範囲を絞り込む方法を紹介しました。
その中で、関数間で値を受け渡すために引数を使いました。
Function GetVisibleColumn(ByVal dataRange As Range, _
ByVal colNum As Long, Optional ByRef errMsg As String) As Range
Set GetVisibleColumn = Nothing
Dim rng As Range
Set rng = GetColumnData(dataRange, colNum)
If rng Is Nothing Then
errMsg = "列" & colNum & "がデータ範囲外です"
Exit Function
End If
Set rng = GetBodyRange(rng)
If rng Is Nothing Then
errMsg = "データ行がありません"
Exit Function
End If
Set rng = GetVisibleCells(rng)
If rng Is Nothing Then
errMsg = "可視セルがありません"
Exit Function
End If
Set GetVisibleColumn = rng
End Function
この実装では、処理の状態を表す文字列をerrMsgという引数で管理しています。呼び出し側では、この引数を確認することで処理の結果が分かります。
Sub TestWithErrorMessage()
Dim result As Range
Dim errMsg As String
Set result = GetVisibleColumn(ActiveSheet.Range("A1:D10"), 3, errMsg)
If result Is Nothing Then
MsgBox "エラー: " & errMsg
Else
result.Interior.Color = RGB(255, 255, 200)
End If
End Sub
引数を使った理由は、変数のスコープ(有効範囲)を限定することで、値の管理がしやすくなるからです。引数リストを見れば、どんな値が必要で、どんな値が変更されるかが一目で分かります。
グローバル変数との比較
同じ処理をグローバル変数で実装すると、以下のようになります。
Public g_ErrorMessage As String
Function GetVisibleColumn(ByVal dataRange As Range, _
ByVal colNum As Long) As Range
Set GetVisibleColumn = Nothing
g_ErrorMessage = ""
Dim rng As Range
Set rng = GetColumnData(dataRange, colNum)
If rng Is Nothing Then
g_ErrorMessage = "列" & colNum & "がデータ範囲外です"
Exit Function
End If
Set rng = GetBodyRange(rng)
If rng Is Nothing Then
g_ErrorMessage = "データ行がありません"
Exit Function
End If
Set rng = GetVisibleCells(rng)
If rng Is Nothing Then
g_ErrorMessage = "可視セルがありません"
Exit Function
End If
Set GetVisibleColumn = rng
End Function
Sub TestWithGlobalVariable()
Dim result As Range
Set result = GetVisibleColumn(ActiveSheet.Range("A1:D10"), 3)
If result Is Nothing Then
MsgBox "エラー: " & g_ErrorMessage
Else
result.Interior.Color = RGB(255, 255, 200)
End If
End Sub
グローバル変数を使うと、引数が減ってシンプルに見えます。
【引数方式のメリット】
- 変数の管理範囲が明確で、引数リストから値の流れが分かる
- 予期しない値の変更が起きにくい
- 複数の処理を同時に実行しても値の衝突がない
- テストが容易で、変数の初期化状態を気にする必要がない
- 再利用性が高く、他のプロジェクトへの移植が簡単
【引数方式のデメリット】
- 引数が増えると関数シグネチャ(関数の宣言部分)が長くなる
-
ByRefを使う場合、呼び出し側で渡した変数が変更されることを理解しておく必要がある
【グローバル変数のメリット】
- 引数リストが短くなり、関数呼び出しがシンプルに見える
- 複数の関数間で値を共有でき、何度も引数で渡す手間が省ける
【グローバル変数のデメリット】
- どこで変数の値が変更されるか追跡が困難
- 意図しない値の書き換えが、発生する可能性がある
- 再利用性が低下し、関数を移植する際にグローバル変数も必要になる
- テストとデバッグが困難で、変数の状態によって関数の動作が変わる
- 保守性が大きく低下し、バグの原因特定が難しい
- 変数名の衝突リスクが高い
グローバル変数を使うべきタイミング
では、グローバル変数で値を管理しても良い場面はどこでしょうか。
【アプリケーション全体で共有する設定値】
処理で使用する固定的な設定値は、グローバル変数に向いています。
Public g_outputPath As String
Public g_startTime As Date
Sub Initialize()
g_outputPath = "C:\Output\"
g_startTime = Now
End Sub
これらの値は初期化時に一度だけ設定され、その後は複数の処理から参照されます。毎回引数で渡すよりも、グローバル変数として管理する方が合理的です。
【頻繁にアクセスする読み取り専用の値】
初期化時に一度だけ設定して、その後は読み取りだけを行う値も、グローバル変数として扱えます。
Public g_masterWb As Workbook
Sub Initialize()
Set g_masterWb = Workbooks.Open("C:\Data\master.xlsx")
End Sub
このワークブックの参照を毎回引数で渡すと、すべての関数に同じ引数を追加する必要があります。グローバル変数で管理すれば、引数の増加を防げます。
【アプリケーションライフサイクル全体で使う参照】
ワークブックやワークシートオブジェクトなど、アプリケーションの実行中ずっと使い続けるオブジェクトの参照も、グローバル変数が便利です。
【使用時のポイント】
グローバル変数で値を管理する場合は、以下のポイントに注意します。
- 読み取り専用として扱い、変更は初期化時のみに限定する
- 変数名にプレフィックス(接頭辞)
g_を付けて識別しやすくする - 使用箇所を最小限に抑え、依存関係を複雑にしない
- ドキュメントやコメントで使用目的を明記する
引数からグローバル変数への移行判断
では、どんなタイミングでグローバル変数に移行するのが良いのでしょうか。
【移行を検討する状況】
以下のような状況では、グローバル変数への移行を検討できます。
- 同じ値を5個以上の関数で使い回している場合
(これ以上増えると、引数の追加や変更が煩雑になる) - 値が初期化後に変更されず、完全に読み取り専用である場合
- 明確に「設定管理」の責任を持つモジュールがあり、そこでグローバル変数を管理できる場合
【移行すべきでない状況】
一方で、以下のような状況では引数を使い続けるのがおすすめです。
- 処理の途中で値が変わる場合
- 複数の処理が同じグローバル変数を書き換える可能性がある場合
- 関数を他のプロジェクトで再利用する予定がある場合
- テストのしやすさを重視する場合
【推奨される判断基準】
迷ったときは、引数を使う方を選ぶのがおすすめです。後からグローバル変数に変更するのは比較的簡単ですが、グローバル変数から引数に戻すのは大変です。
また、グローバル変数を使う場合でも、最初は限定的な範囲で試してみて、問題がなければ徐々に広げていく方が安全です。
変数の管理方法を選ぶときは、「この値はいつ、どこで、誰が変更するのか」を明確にすることが大切です。変更のタイミングと場所が限定されていれば、グローバル変数でも問題ありません。逆に、あちこちで変更される可能性がある値は、引数で管理する方が安全です。
まとめ
変数の値を管理する方法として、基本的には引数を使う方が値の変化を追跡しやすく、テストもしやすくなります。グローバル変数は、アプリケーション全体で共有する設定値など、読み取り専用の用途で使うのがおすすめです。