目的
筆者は本当に VB が嫌いなんです。VB.net も VBScript も VBA も全て早く滅びればいいのにと心の底から願っています。
仕様書もなく謎に作り込まれた VBA のツールを他の言語に移植することを目的に、ソースをキレイに整理しましょうということでリファクタリングの指針をまとめてみました。
※VB.net がいかにダメダメかまとめてくださっている方の記事です。
なぜVBはC#と比べて駄目なのか
コーディング規約
Visual Basic のコーディング規約に沿って、命名規則等を見直してください。
- 別紙参照 (Visual Basic の概念)
ソースコードの分析
未使用コードの分析
- 使用されていない、変数、関数を分析する。
類似コードの分析
- フローが似ている関数について、共通化関数として抜き出せないか分析する。
静的コード解析
- ソースコードを実行せずにエラーがないか解析する。実行前にコンパイルをかけることでコンパイルエラーを検知できる。
修正方針
関数 (プロシージャ)
記述先の整理
ThisWorkbook
ブック全体に関わるイベント、関数を記述する。
- 特定のシートでしか使われていない関数は、各 Sheet へ移動させる。
- メンバー変数に依存しない静的な Public 関数は標準モジュールへの移動を検討する。
各 Sheet
そのシートのみで使用する関数を記述する。
- シートのセル、範囲及び、テーブル等を参照しているような関数が対象となる。
- メンバー変数に依存しない静的な Public 関数は標準モジュールへの移動を検討する。
標準モジュール
なんでもここに記述してしまいがちだが、メンバー変数に依存しない静的な共通関数のみを記述するように整理する。
クラスモジュール
メンバー変数やプロパティを利用して動的に使用する共通関数を記述してクラス化する。
参照を明確にする
-
Public, Private を明示的に記述する。
- 他のモジュールで参照している関数は、Public をつける。
- そのモジュール内で完結している関数は、Private をつける。
引数を明確にする
- 使用していない引数は削除する。
- ※Worksheet_Change 等のイベントプロシージャについては、引数が決まっているため消してはダメ。
-
ByVal, ByRef を明示的に記述する。
- 関数の引数に ByVal, ByRef の記述を省略した場合、ByRef の扱いになる。
- 読取りしかしていない引数は、ByVal を指定。
- 関数内で引数に値を代入したり再定義しているような場合は、ByRef を指定する。
Public Sub Method01(strValue01 As String, strValue02 As String, strValue03 As String)
Dim strA As String: strA = strValue01
MsgBox strValue01
strValue02 = "文字列を代入"
End Sub
↓↓↓↓↓
Public Sub Method01(ByVal strValue01 As String, ByRef strValue02 As String)
Dim strA As String: strA = strValue01
MsgBox strValue01
strValue02 = "文字列を代入"
End Sub
類似処理の関数化
- コピー&ペーストをして処理を流用しているような個所は、共通関数として抜き出す。
関数の静的化
- 関数内部でモジュール変数やグローバル変数に依存しているものを引数で設定するように外出しする。
変数
定数化
- 変更されない固定値の場合、定数として定義する。
Const SALES_TAX_RATE = 1.10
変数名
- 意味のある名前に置き換える。
- 変数名に接頭辞 (プレフィックス) をつける。
- ※strXXXX, objXXX 等 (詳細は、Visual Basic の概念 参照)
宣言
宣言の強制
モジュールの先頭に下記の構文を記述して型の宣言を強制する。
- 変数名の記述ミスをコンパイル時に検知する。
Option Explicit
不要変数の削除
- 宣言しているにもかかわらず使用していない変数は削除する。
宣言と初期化
-
変数の型を省略してる暗黙の型 (Variant) を使わないようにする。
-
宣言と同時に New をしない。
- 変数に代入されるまでインスタンスが生成されなため。
Dim obj AS New Class1
↓↓↓↓↓
Dim obj AS Class1: Set obj = New Class1
-
変数の宣言と代入 or 初期化を同時に行うように記述する。
Dim fso As Object Dim str As String str = "文字列" Set fso = CreateObject("Scripting.FileSystemObject")
↓↓↓↓↓
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 を使用する。
Public gstrVal As String
Private mstrVal As String
Public Sub Method()
Dim strVal As String
End Sub
With 句
- 同じ変数や関数を何度も読んでいる箇所は With 句を使って有効範囲を明確にする。
Worksheets("Sheet1").Cells(rowIndex, 1) = "A"
Worksheets("Sheet1").Cells(rowIndex, 2) = "B"
Worksheets("Sheet1").Cells(rowIndex, 3) = "C"
Worksheets("Sheet1").Cells(rowIndex, 4) = "D"
↓↓↓↓↓
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
Worksheets("Sheet1").Cells(rowIndex, 1) = "A"
Worksheets("Sheet1").Cells(rowIndex, 2) = "B"
Worksheets("Sheet1").Cells(rowIndex, 3) = "C"
Worksheets("Sheet1").Cells(rowIndex, 4) = "D"
↓↓↓↓↓
Me.Cells(rowIndex, 1) = "A"
Me.Cells(rowIndex, 2) = "B"
Me.Cells(rowIndex, 3) = "C"
Me.Cells(rowIndex, 4) = "D"
条件文
条件による代入
- 条件によって代入する値が変わる処理は、三項演算子 (IIf関数) を使う。
Dim val As String
If a >= 0 Then
val = "正"
Else
val = "負"
End if
↓↓↓↓↓
Dim val As String: val = IIF(a >= 0, "正", "負")
注意
VBA の三項演算子は、厳密にいうと関数である。既存バグがあるので使わない方がいいという意見もあります。
VBAの三項演算子
条件式の反転
- 条件を満たした際に処理を行うのではなく、条件を満たしていない時に処理を抜けるように記述する。
- If 文のネストが深くならない。
- End If に到達するまでの処理を分析する必要がなくなる。
If a >= 0 Then
' 処理
End if
↓↓↓↓↓
If a < 0 Then
Exit Sub
End if
' 処理
演算子
- 文字列の連結は、"+" ではなく "&" を使う。
未使用コードの削除
- どこからも参照されていない関数およびそのプロトタイプ宣言、使用されないマクロ定義を削除する。
- 実質的に無意味な処理を削除。
- 例としては、If 文の中の処理がコメントアウトされている等。
- コメントアウトされている処理は削除する。
コメントの更新
- コメントの説明が修正によって意味が変わってしまっているものは適切な説明に更新する。