【VBA】変数のスコープの最小はプロシージャ内

変数のスコープを意識することの重要性

VBAの変数のスコープについて説明があるサイトは多いですが、ほとんどのサイトでPublic、Privateの説明がされています。

改修でバグが埋め込まれる可能性やコードの可読性を考慮すると、できる限り変数のスコープは限定すべきだと思いますので、これらについて理解をしておくことは重要です。

例えば、以下のサイトがとても分かりやすくまとめてくれていると思いました。

https://www.vba-ie.net/programing/variable-scope.php

自分なりにまとめてみると、

  1. Publicでプロシージャ外に宣言→プロジェクト全体から参照可能
  2. Privateでプロシージャ外に宣言→宣言したモジュールでのみ参照可能
  3. Dimステートメントのみでプロシージャ外に宣言→宣言したモジュールでのみ参照可能
  4. Dimステートメントのみでプロシージャ内に宣言→宣言したプロシージャでのみ参照可能
  5. Staticで宣言→プロシージャが終了しても変数の値を保持する

となるかなと思います。

Staticについてはほとんど使ったことがなくて、調べてみるまで知りませんでした。

プロシージャが閉じても変数はリセットしないということらしいのですが、汎用的なプロシージャを作る場合、前回の結果を引き継ぐというのは少し怖い気がしますね。

例えば以下のようなコードの場合です。

Public Sub outputFile()

    Static intCnt As Integer

    intCnt = intCnt + 1

    Debug.Print CStr(intCnt) & "回目の処理を実行します。"

    '何らかの処理
End Sub

Public Sub Main()
    Dim i As Integer
    For i = 0 To 10
        Call outputFile
    Next i
End Sub

実行結果

上記のMain関数を2回呼んでしまうと、「12回目の処理を実行します」から始まることになります。プロシージャを閉じても変数値がリセットされないためです。

一方でブックを閉じて、再度Main関数を呼ぶと「1回目の処理を実行します」から開始されることになります。

ブックを閉じたかどうかで結果が変わってしまいますし、複数の関数から呼ばれるとすぐバグりそうです。

またワークシートのように、1度代入したいものを使いまわしたい場合はモジュール変数の方が適当です。

そう考えると、Staticをあまり見ないのは使い道があまりないからなのかなと思います。

変数はプロシージャが終了されるまで有効

ここから、本記事で書き留めておきたかった内容になります。

前述したスコープのまとめで

Dimステートメントのみでプロシージャ内に宣言→宣言したプロシージャでのみ参照可能

と記載しました。実行しているプロシージャがスコープになります。

気を付けないといけないのが、他プログラム言語のようにネスト内で完結することはないということです。

下記のコードがわかりやすいと思います。

Sub test()
    For i = 0 To 1
        Dim test As Integer
        test = test + 1
        Debug.Print test
    Next i
    
    test = test + 1
    Debug.Print test
End Sub

実行結果

実行結果ですが「1→2→3」の順に出力されています。

上記のコードは、DimがFor文の中で宣言されていますが、For文の外からでも参照可能ということです。

また、For文の中にDimを書いているので、Dimが2回実行されそうに見えます。実際には、DimがFor文に含まれていても1回しか実行されません。(初期化されたり、エラーになったりはしない)


変数を直前で宣言する場合は注意する

前述のスコープを特に意識することになったのは、下記の記事を読んだことがきっかけでした。

VBAで変数宣言をまだプロシージャの先頭にまとめてしてますか?

https://hatena19.com/position-of-variable-declaration-in-vba/

VBAでは、昔からの慣習で変数宣言をプロシージャの先頭に集めているものが多いかと思いますが、変数を使う直前で宣言すべきという記事になります。

とても共感できる記事だったので、自分も変数の宣言は使用する直前に記載をすることにしました。

以前の自分

Private Sub test()
    Dim A As Integer
    Dim B As Integer
    Dim C As Integer

    A = 10
    MsgBox A

    B = A + 1
    MsgBox B

    C = A * B
    MsgBox C

End Sub

最近の自分

Private Sub test()
    Dim A As Integer
    A = 10
    MsgBox A
    
    Dim B As Integer
    B = A + 1
    MsgBox B

    Dim C As Integer
    C = A * B
    MsgBox C

End Sub

このように使用する直前で使うようになってから、ソースのコピペもしやすくなりましたし、可読性も上がったと思います。

また、変数は宣言前には使えないので、プロシージャの先頭で宣言するよりも変数が使えるコードが限定されて、安全性が向上していると言えるかと思います。

また以下のような初期化方法も素敵です。

Private Sub test()
    Dim A As Integer: A = 10
    MsgBox A

    Dim B As Integer: B = A + 1
    MsgBox B

    Dim C As Integer: C = A * B
    MsgBox C

End Sub

多くのプログラミング言語で、変数は宣言と同時に初期化できるので上記のような書き方のほうがわかりやすく感じます。

ただ、ForループやIf文でも上記のように使用直前に変数を宣言するルールにしてしまうと…

Sub test()
    Dim msg As String:msg = "始める"
    MsgBox msg
    Dim i As Integer
    For i = 0 To 1
        Dim test As Integer
        test = test + 1
        Debug.Print test
    Next i
End Sub

他のプログラミング言語を学んだ方が、変数testはFor文を抜けたタイミングで破棄されると思ってしまう可能性があります。

そこで、ForループやIf文がある場合には…

Sub test()
    Dim msg As String:msg = "始める"
    MsgBox msg
    Dim i As Integer

    Dim test As Integer
    For i = 0 To 1
        test = test + 1
        Debug.Print test
    Next i
End Sub

上記のように、ループの外に宣言を書くことにしました。

これで、「test変数は破棄されている」と誤解されることを防げるのではないかと思います。

また、コード上はDimが2回実行されているように見えるのですが、これは「test変数が自動的に初期化される」と見えてしまう恐れがあります。そちらも防止できるので、良い書き方ではないかなと思いました。

プロシージャの先頭に変数宣言を集める形だと、それらがプロシージャ全体で使えるということが感覚としてわかります。

なので、プロシージャ内の変数スコープ自体をあまり意識したことはなかったのですが、直前宣言派が増えてきたら、この辺を意識していないと予期しない動作につながりそうですね。


コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)