VBA リファクタリング

# 目的
筆者は本当に VB が嫌いなんです。VB.net も VBScript も VBA も全て早く滅びればいいのにと心の底から願っています。
仕様書もなく謎に作り込まれた VBA のツールを他の言語に移植することを目的に、ソースをキレイに整理しましょうということでリファクタリングの指針をまとめてみました。

>※VB.net がいかにダメダメかまとめてくださっている方の記事です。
>[なぜVBはC#と比べて駄目なのか](http://crapp.hatenablog.com/entry/2014/12/31/235842)

# コーディング規約

Visual Basic のコーディング規約に沿って、命名規則等を見直してください。

- 別紙参照 ([Visual Basic の概念][])

[Visual Basic の概念]: <https://qiita.com/MakotoIshikawa/private/ccb571de7e91541f17b2>

# ソースコードの分析

### 未使用コードの分析

- 使用されていない、変数、関数を分析する。

### 類似コードの分析

- フローが似ている関数について、共通化関数として抜き出せないか分析する。

### [静的コード解析](https://ja.wikipedia.org/wiki/静的コード解析)

- ソースコードを実行せずにエラーがないか解析する。実行前にコンパイルをかけることでコンパイルエラーを検知できる。

> [VBAでマクロを記述したらProjectのコンパイルは忘れずに！反論は聞かない！](http://hensa40.cutegirl.jp/archives/7095)

# 修正方針

## 関数 (プロシージャ)

### 記述先の整理

#### ThisWorkbook

ブック全体に関わるイベント、関数を記述する。

- 特定のシートでしか使われていない関数は、各 Sheet へ移動させる。
- メンバー変数に依存しない静的な Public 関数は標準モジュールへの移動を検討する。

#### 各 Sheet

そのシートのみで使用する関数を記述する。

- シートのセル、範囲及び、テーブル等を参照しているような関数が対象となる。
- メンバー変数に依存しない静的な Public 関数は標準モジュールへの移動を検討する。

#### 標準モジュール

なんでもここに記述してしまいがちだが、メンバー変数に依存しない静的な共通関数のみを記述するように整理する。

#### クラスモジュール

メンバー変数やプロパティを利用して動的に使用する共通関数を記述してクラス化する。

> [クラスモジュールのメリット・使い方を徹底解説！](https://www.sejuku.net/blog/67476)

### 参照を明確にする

- **Public**, **Private** を明示的に記述する。
  - 他のモジュールで参照している関数は、**Public** をつける。
  - そのモジュール内で完結している関数は、**Private** をつける。

### 引数を明確にする

- 使用していない引数は削除する。
  - ※**Worksheet_Change** 等のイベントプロシージャについては、引数が決まっているため消してはダメ。
- **ByVal**, **ByRef** を明示的に記述する。
  - 関数の引数に **ByVal**, **ByRef** の記述を省略した場合、**ByRef** の扱いになる。
  - 読取りしかしていない引数は、**ByVal** を指定。
  - 関数内で引数に値を代入したり再定義しているような場合は、**ByRef** を指定する。

```vb
Public Sub Method01(strValue01 As String, strValue02 As String, strValue03 As String)
    Dim strA As String: strA = strValue01
    MsgBox strValue01

    strValue02 = "文字列を代入"
End Sub
```

↓↓↓↓↓

```vb
Public Sub Method01(ByVal strValue01 As String, ByRef strValue02 As String)
    Dim strA As String: strA = strValue01
    MsgBox strValue01

    strValue02 = "文字列を代入"
End Sub
```

### 類似処理の関数化

- コピー＆ペーストをして処理を流用しているような個所は、共通関数として抜き出す。

### 関数の静的化

- 関数内部でモジュール変数やグローバル変数に依存しているものを引数で設定するように外出しする。

## 変数

> [変数の宣言と初期化、有効(適用)範囲](http://www.asahi-net.or.jp/~ef2o-inue/vba_k/sub04_050_04.html)

### 定数化

- 変更されない固定値の場合、定数として定義する。

```vb
Const SALES_TAX_RATE = 1.10
```

### 変数名

- 意味のある名前に置き換える。
- 変数名に接頭辞 (プレフィックス) をつける。
  - ※strXXXX, objXXX 等 (詳細は、[Visual Basic の概念][] 参照)

### 宣言

#### 宣言の強制

モジュールの先頭に下記の構文を記述して型の宣言を強制する。

- 変数名の記述ミスをコンパイル時に検知する。

```
Option Explicit
```

#### 不要変数の削除

- 宣言しているにもかかわらず使用していない変数は削除する。

#### 宣言と初期化

- 変数の型を省略してる暗黙の型 (Variant) を使わないようにする。

- 宣言と同時に **New** をしない。

  - 変数に代入されるまでインスタンスが生成されなため。

  ```vb
  Dim obj AS New Class1
  ```

  ↓↓↓↓↓

  ```vb
  Dim obj AS Class1: Set obj = New Class1
  ```

- 変数の宣言と代入 or 初期化を同時に行うように記述する。

  ```vb
  Dim fso As Object
  Dim str As String
  
  str = "文字列"
  Set fso = CreateObject("Scripting.FileSystemObject")
  ```

  ↓↓↓↓↓

  ```vb
  Dim str As String: str = "文字列"
  Dim fso As Object: Set fso = CreateObject("Scripting.FileSystemObject")
  ```

### スコープ (有効範囲)

#### **Public**, **Private**, **Dim** を明確にする。

- **Public** で定義されているグローバル変数、関数について、使用しているモジュールを特定して使用個所が限定されているようであれば、使用しているモジュールに定義を移して **Private** とする。
- **Public** なグローバル変数にはプリフィックスとして変数名の先頭に ’g’ をつける。
- **Private** なメンバー変数にはプリフィックスとして変数名の先頭に ’m’ をつける。**Dim** で定義しない。
- プロシージャ内のローカル変数の定義に **Dim** を使用する。

```vb
Public gstrVal As String
Private mstrVal As String

Public Sub Method()
    Dim strVal As String
End Sub
```

#### With 句

- 同じ変数や関数を何度も読んでいる箇所は With 句を使って有効範囲を明確にする。

```vb
Worksheets("Sheet1").Cells(rowIndex, 1) = "A"
Worksheets("Sheet1").Cells(rowIndex, 2) = "B"
Worksheets("Sheet1").Cells(rowIndex, 3) = "C"
Worksheets("Sheet1").Cells(rowIndex, 4) = "D"
```

↓↓↓↓↓

```vb
With Worksheets("Sheet1")
    .Cells(rowIndex, 1) = "A"
    .Cells(rowIndex, 2) = "B"
    .Cells(rowIndex, 3) = "C"
    .Cells(rowIndex, 4) = "D"
End With
```

#### Me

ワークシートモジュール内で、自分自身を呼び出している箇所は、**Me** に置き換える。

###### 例：Sheet1.cls

```vb
Worksheets("Sheet1").Cells(rowIndex, 1) = "A"
Worksheets("Sheet1").Cells(rowIndex, 2) = "B"
Worksheets("Sheet1").Cells(rowIndex, 3) = "C"
Worksheets("Sheet1").Cells(rowIndex, 4) = "D"
```

↓↓↓↓↓

```vb
Me.Cells(rowIndex, 1) = "A"
Me.Cells(rowIndex, 2) = "B"
Me.Cells(rowIndex, 3) = "C"
Me.Cells(rowIndex, 4) = "D"
```

## 条件文

#### 条件による代入

- 条件によって代入する値が変わる処理は、三項演算子 (IIf関数) を使う。

```vb
Dim val As String
If a >= 0 Then
    val = "正"
Else
    val = "負"
End if
```

↓↓↓↓↓

```vb
Dim val As String: val = IIF(a >= 0, "正", "負")
```

> ###### 注意
>
> VBA の三項演算子は、厳密にいうと関数である。既存バグがあるので使わない方がいいという意見もあります。
> [**VBAの三項演算子**](https://vbabeginner.net/vbaの三項演算子%ef%bc%88iif関数%ef%bc%89/)

#### 条件式の反転

- 条件を満たした際に処理を行うのではなく、条件を満たしていない時に処理を抜けるように記述する。
  - If 文のネストが深くならない。
  - End If に到達するまでの処理を分析する必要がなくなる。

```vb
If a >= 0 Then
    ' 処理
End if
```

↓↓↓↓↓

```vb
If a < 0 Then
    Exit Sub
End if

' 処理
```

## 演算子

- 文字列の連結は、"+" ではなく "&" を使う。

## 未使用コードの削除

- どこからも参照されていない関数およびそのプロトタイプ宣言、使用されないマクロ定義を削除する。
- 実質的に無意味な処理を削除。
  - 例としては、If 文の中の処理がコメントアウトされている等。
- コメントアウトされている処理は削除する。

## コメントの更新

- コメントの説明が修正によって意味が変わってしまっているものは適切な説明に更新する。

# 参考サイト

- [Excel VBAコーディング ガイドライン案](https://qiita.com/mima_ita/items/8b0eec3b5a81f168822d)
- [エクセルVBAコーディングガイドライン](https://tonari-it.com/excel-vba-coding-guide-line/)
- [【ExcelVBA入門】クラスモジュールのメリット・使い方を徹底解説！](https://www.sejuku.net/blog/67476)
- [Excel VBA 標準モジュールとクラスモジュールの違い](https://kiyotatsu.com/excel-vba-mj/)
- [ExcelVBAのクラスモジュールって何？という人向けの使い方まとめ](https://ateitexe.com/how-to-use-class-module/)