上級者を目指すExcelVBA #7 『グローバル変数は使わない』

VBAの場合、変数には以下の3つのスコープ（使用可能な範囲）が存在します。
* <strong>ローカル変数</strong>（関数の中でDimで定義する変数）
* <strong>モジュール変数</strong>（関数の外でDim、Privateで定義する変数）
* <strong>グローバル変数</strong>（Publicで定義する変数）

　
### グローバル変数は複雑なコードの原因になりやすい

このうち、Publicで記述するグローバル変数はデメリットが多数あるため、基本的に使わないようにします。

:::note alert
<strong>グローバル変数のデメリット</strong>
・複数人での開発時に名前が衝突する可能性がある
・モジュール変数との区別がつきにくい
・コード補完時に常に候補に出てきてしまう
・値が全モジュールで共有されるため、リセットされずバグの温床になる
:::

グローバル変数があると全体的にコードが複雑化し、保守しづらくなる一因になります。
シンプルなコードを書くためにも基本的にモジュール変数、ローカル変数のみを使うようにしましょう。
　
　
### 値をモジュール間で受け渡す方法

グローバル変数を使わずにモジュール間で変数の値を受け渡したい場合は、主に以下の2つの方法があります。

* 関数実行後に戻り値で受け取る
* 変数の値を渡すためだけの関数（プロパティ）を作成する

　

### 関数実行後に戻り値で受け取る

まずは関数を呼び出してそのまま値を戻り値で受け取るケース。
これは一番シンプルですね。

```vba:MainController.bas

' メイン関数
'   ・天気予報データを取得
'
Public Sub exec()
    On Error GoTo ErrHdl

    ' WeatherDataFetcherから天気予報データを取得
    Set WeatherDatas = WeatherDataFetcher.getWeatherDatas
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".exec", Err.Description
    End If
End Sub
```

```vba:WeatherGetter.bas
' 指定された県の天気を返す
'   ・天気予報データを取得
'   ・指定した都道府県の天気予報データを取得し、天気を返す
'
' 戻値: Colleciton WeatherDataのコレクション
'
Public Function getWeatherDatas() As Collection
    On Error GoTo ErrHdl

    ' APIで外部サイトにアクセス
    Call connectToAPI
    ' 指定した都道府県の天気予報データをJSONで取得
    Call fetchWeathersByJSON
    ' JSONをWeatherData形式にコンバート
    Call convertJSONtoWeatherDatas
    
    ' 値を戻り値として返す
    Set getWeatherDatas = WeatherDatas
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".getWeatherDatas", Err.Description
    End If
End Function

```
　
　
### 変数の値を渡すためだけの関数（プロパティ）を作成する

他の手段としては、Java言語のように変数の値を渡すためのだけのget関数を作っても良いと思います。

```vba:Sample.bas
' ユーザ名のコレクション
Private UserNames As New Collection

' 都市名
Private CityName As String

'============================================================
' プロパティ
'============================================================

Public Function getUserNames() As Collection
    Set getUserNames = UserNames
End Function

Public Function getCityName() As String
    getCityName = CityName
End Function
```

以下のように関数getUserNames()を他モジュールから呼び出すことでUserNamesの値を受け取ることが出来ます。

```
' メイン関数実行
' 　・サンプルモジュールの処理実行後、ユーザ名、都市名を取得
'
Public Sub exec()
    On Error GoTo ErrHdl
    Dim uNames As Collection,cName

    ' サンプルモジュールの処理実行
    Call Sample.exec
    ' ユーザ名を取得
    Set uNames = Sample.getUserNames
    ' 都市名を取得
    cName = Sample.getCityName 
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".exec", Err.Description
    End If
End Sub
```

この場合は複数の値を受け取りたいときにも問題なく受け取ることができますね。
　

### クラス内部ではPublicを使っても良い

ただし、Publicを使っても良いケースもあります。
以下のようにクラスを1件のデータとして使っている場合、クラス内部ではPublicを使っても全く問題ないと思います。

クラス内部の変数は全モジュールで使用できる共通変数とはならないためです。

```vba:WeatherData.cls
'===========================================================
'
' 天気予報データ格納用クラス
'
' [処理概要]
'　・APIで外部サイトから取得した1県分の天気予報データを格納
'===========================================================

' 県名
Public PrefName As String

' 日付
Public TargetDate As Date

' 天気  ※例："晴れ"、"雨"
Public WeatherName As String
```

```vba:Sample.bas
' サンプル用関数
'
Public Sub exec()
    On Error GoTo ErrHdl
    Dim d As New WeatherData
    
    ' WeatherDataのインスタンスに値をセット
    d.PrefName = "長崎県"
    d.TargetDate = Now
    d.WeatherName = "晴れ"
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".exec", Err.Description
    End If
End Sub
```