上級者を目指すExcelVBA #4 『モジュールは適切に分割する』

関数同様、モジュールも適切に分割し、分かりやすい名前を付けてプログラムの全体像が分かりやすくなるように心がけます。

:::note 
<strong>適切なモジュール分割の仕方</strong>
・1つのモジュールには大きな単位の1つの仕事をさせる
　　※1つの関数では小さな単位の1つの仕事をさせる
・モジュール名には処理内容が推測できる名前を付ける
・モジュールの上部コメントには処理内容を記述する
:::

　
また、モジュール、クラスの使い分けとしては、大まかには以下のようにすると良いかと思います。

:::note 
<strong>モジュール、クラスの使い分け</strong>
・モジュール … 処理（ビジネスロジック）記述用
・クラス … 1件のデータ格納用。DBを使用する場合、テーブルに格納する1行分のデータ
:::

　
## モジュール分割例

実際のモジュールの分割例です。

以下プログラムでは天気予報データを外部サイトから取得し、Excelシート上に結果を描画する処理をやっているものとします。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200881/806b041d-4c56-ab4b-573a-67a8e45631db.png" width = "956">

大きな処理の単位ごとにモジュールを分割し、モジュール名には処理内容が推測できる名前を付けます。

モジュール名には動詞に"er"を付けて「何々する人」といった形の名詞にすると良いと思います。
また、関数名は動詞にします。
こうするとモジュールの関数を呼び出した際に 名詞 + 動詞 と言葉がつながるので文法的に分かりやすいですね。（例：WeatherDataFetcher.getData）

　
## モジュールの呼び出し例

分割した各モジュールは、以下のような形で順番に呼び出します。


<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200881/c829a24d-010d-f091-df6f-327446ddc74e.png" width = "917">

この場合の処理の流れとしては、
　1. MainControllerのメイン関数execが呼び出される
　2. メイン関数からWeatherDataFetcherモジュールの関数呼び出し 
　3. メイン関数からResultWriterモジュールの関数呼び出し
といった流れですね。


　
```VBA MainController.bas
Option Explicit

'===========================================================
'
' 天気予報取得用プログラム
'
' [処理概要]
' 　・複数の都道府県の天気予報データをAPIで取得し、
'     シート上のテーブルに描画する
'
' [索引]
'　 □ 1. メイン処理実行
'       ・天気予報データを取得（WeatherDataFetcher）
'       ・結果をシートに描画（ResultWriter）
'
'===========================================================

' モジュール名
Const MODULE_NAME = "MainController"

' 天気予報データ
Private WeatherDatas As Collection

'===========================================================
'
' 1. メイン処理
'
'===========================================================

' メイン処理実行
'   ・天気予報データを取得
'   ・結果をシートに描画
'
Public Sub exec()
    On Error GoTo ErrHdl

    ' 天気予報データを取得
    Set WeatherDatas = WeatherDataFetcher.getWeatherDatas
    
    ' 取得したデータをシートに描画
    Call ResultWriter.writeDataToSheet(WeatherDatas)
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".exec", Err.Description
    End If
End Sub

```
　

補足として、私は全体の処理の流れが1つの際には分かりやすさ重視のために上記のようにMainControllerモジュールを作成して他モジュールを呼び出しています。
ですが、複数の動作をするプログラムの場合はこの形にこだわらなくて良いかと思います。

例えばボタンを押した際に個別の処理を実行したい際などは、各ボタンに対応する関数から各モジュールを呼び出してやると良いでしょう。

　
## 各モジュール内の構成

以下はメイン関数MainController.execから呼び出される、WeatherDataFetcherの構成例です。
モジュール内の処理は、前記事で記述したように小さな処理の単位ごとに関数に分割します。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200881/d8c23dd5-1927-8253-9866-926fe6aaa0ee.png" width = "872">

　
以下がモジュールの全体のコードです。長くなるので処理の部分は簡略化します。

```VBA WeatherDataFetcher.bas
Option Explicit

'===========================================================
'
' 天気予報データ取得用モジュール
'
' [処理概要]
'　・APIで外部サイトから複数件の天気予報データを取得し、
'　  WeatherDataのインスタンスにデータを格納
'
' [索引]
'  □ 1. 天気予報データ取得
'      ・APIで外部サイトにアクセス
' 　　 ・指定した都道府県の天気予報データを取得
'      ・JSONをWeatherData形式にコンバート
'
'===========================================================

' モジュール名
Const MODULE_NAME = "WeatherDataFetcher"

' 天気予報データ
Private WeatherDatas As Collection

'===========================================================
'
' 1. 天気予報データ取得
'
'===========================================================

' 天気予報データ取得
'   ・天気予報データを取得
'   ・結果をシートに描画
'
' 戻値: 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

'===========================================================
' APIで外部サイトにアクセス
'===========================================================

' APIで外部サイトにアクセス
'
Private Sub connectToAPI()
    On Error GoTo ErrHdl

    ' APIで外部サイトにアクセスする処理を記述
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".connectToAPI", Err.Description
    End If
End Sub

'===========================================================
' 指定した都道府県の天気予報データをJSONで取得
'===========================================================

' 指定した都道府県の天気予報データをJSONで取得
'　・定数で定義された都道府県の天気予報データをJSONで取得
'
Private Sub fetchWeathersByJSON()
    On Error GoTo ErrHdl

    ' JSONで天気予報データを取得する処理を記述
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".fetchTargetPrefWeathers", Err.Description
    End If
End Sub

'===========================================================
' JSONをWeatherData形式にコンバート
'===========================================================

' JSONをWeatherData形式にコンバート
'　・1県の天気予報データを1件のWeatherDataにコンバートし、WeatherDatasに格納
'
Private Sub convertJSONtoWeatherDatas()
    On Error GoTo ErrHdl

    ' JSONをコンバートし、コレクションに格納する処理を記述
ErrHdl:
    If Err.Number <> 0 Then
        Util.logError MODULE_NAME & ".fetchTargetPrefWeathers", Err.Description
    End If
End Sub

```
また、天気予報データを格納するクラスWeatherDataの構成は以下のようにしています。
1県分のデータをこのクラスに格納するイメージです。

```vba:WeatherData.cls
Option Explicit

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

' モジュール名
Const MODULE_NAME = "WeatherData"

' 県名
Public PrefName As String

' 日付
Public TargetDate As Date

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

```

　
モジュール、関数を適切に分割するとプログラム全体の構成が非常に分かりやすくなります。
できるだけ分かりやすくて一貫性のある分割を心がけましょう。