モダンなVBAの書き方
「糞VBAコードのメンテナンスが減りますように。なむなむ」
VBAは、非常に古い(VB6ベース)の言語なので、既存のコードは、非常に読み難い物が多いです。しかし、最近、ちょっとしたことをVBAで書いちゃうというソリューションは、案外一般的になりつつあるように思います(これ自体は悪いことではないと思う)。新しく書くVBAのコードをどう書くべきか?という自分なりの意見を纏めてみました。これは、VBAで完全なOOPをしろと言っているものではなく、単純な構造化された小さい関数を組み合わせてプログラムを書く方針(関数型言語のことではない)を勧めるものです。
## 変数宣言を強制する!
これは、自分のため。とにかく書く。オプションで設定しておけば自動で挿入される。
Option Explicit |
## 変数宣言は、必要になったところで宣言する!
そのコードを読む人に、いちいち、関数の先頭までスクロールさせるな。変数の有効範囲をなるべく狭くすることが大事。関数の先頭で全ての変数を定義するのは、変数の有効範囲をなるべく広くしてるのと同じことになる。
## 配列は使うな ReDim禁止!
基本的な変数として配列がありますが、これをうまく使うのは非常に難しい。たくさんの配列を駆使してプログラムを書くことがVBAだったのは昔の話。配列を一切定義しなくてもプログラムは書ける。VB配列は、要素数が自動で拡張されず、ReDimで動的に配列のサイズを変更するコードを書くのが定石になっているが、そんなことするくらいなら、全部Collectionを使うようにするべき。
Sub CollectionAccess() Dim items as Collection Set items = new Collection Call items.Add("first item") Call items.Add("second item") Dim e as Variant For Each e in items Debug.Print e Next End Sub |
## 変数名は好きにすればいいけど、Collectionなら複数形にする!
やむを得ず配列を使う場合も同様。hogehogeName という変数名が、実は配列という罠は、読む人を混乱させる。配列ならhogehogeNamesという変数名を使うべき。
## データ構造は、クラスとCollectionで!
配列を複数用意して、同じindexでアクセスすることで、データ構造を定義せずに、データを扱うなんて以ての外。読み難いしメンテしにくい。クラスを構造体として使う。構造体が無いんだからこれは必然。(ありました。コメント欄でのytaniikeさんの指摘より。でも、Typeで宣言した構造体をCollectionにAddできないので、現実的にはクラスでデータ構造を表すべき)
## キーによる検索が必要なら、Scripting.Dictionaryを使う!
クラスとCollectionでほとんどのデータ構造を表現できるが、検索を行なう場合に、専用の関数を用意する必要が出てくる。キーによる検索を行なうことがわかっている場合は、Scripting.Dictionaryを使えば、高速な検索を簡潔に記述できる。
Sub MapAccess() Dim map As Object Set map = CreateObject("Scripting.Dictionary") Call map.Add("one", "first item") Call map.Add("two", "second item") Dim key As Variant For Each key In map.Keys Debug.Print key & ": " & map(key) Next 'Key search Debug.Print map.Item("one") End Sub |
## 関数は短かく!
関数が広くなると、似た処理に使う変数を似た名前で複数宣言する必要が出てくるのでよろしくない。VBAは変数宣言と値の代入が同時にできないから、非常に煩雑になる原因となってしまう。どんどん関数に分割することで、短い変数名で、処理を記述できる方向に持っていくこと。
## 副作用の無い関数に分解する!
副作用というのは、ある関数内で、グローバルや、関数の外の変数を更新するということ。副作用のある関数とは、2回呼び出したら結果が変わる可能性がある関数のこと。これがあると、プログラムが複雑化するので、なるべく減らすべき。引数のみを元に関数の処理内容が決定され、処理結果が戻り値のみに反映される関数を目指す。
## Sub呼び出しにはCallを付ける!
Sub呼び出し時の引数の見た目が、Functionと同じになるという利点もあるが、Callを付けることで、副作用を期待してるコードを書いてることに罪悪感を覚えるようにする。
## モジュールは、機能毎に分割すること!
シートに対する処理を纏めたモジュール。ユーティリティを纏めたモジュール。主要な処理を纏めたモジュール。など、怖がらずに分割する。
## モジュール内の関数呼び出しは、modulename.subname 形式で呼び出す!
関数名だけで、呼び出せるが、わかりやすさのために、modulename.subname形式で呼び出す。
## printfデバックは、Debug.Printで!
MsgBoxでやるとリリース時に消さなければならないし、メンテナンス時に利用できない。Debug.Printで変数の値などを出力するようにすれば、そのままリリースできるし、メンテナンス時にも参考になる。ただし、Debug.Printも大量に出力しているとパフォーマンスに影響するので、その場合は、コメント化するなり削除する。
VBA自体、簡潔に、スマートに書くのが難しい言語だと思います。だからこそ、読み易いコードを書きたいものです。そう言えばコメントについて書いてないですが、個人的には、クラス名、モジュール名、メソッド名、変数名で多くを語るべきだと考えてます。あと、もうちょっと再利用がやりやすいといいんですけどね。VBAは。
> 構造体が無いんだから
Type UserDefinedType
identifier As Long
key_name As String
key_value As String
End Type
と書けます。
クラス・モジュールとかが導入される前から可能です。
これは「構造体」ではないのでしょうか?
宣言すれば、
Dim MyVarFoo As UserDefinedType
という風に、このタイプの変数を確保できます。
上手なプログラマは変数の名前付け一つとっても
わかりやすいですよね。
コレクションなら複数形の名前には共感しました。
>ytaniikeさん
指摘ありがとうございます。エントリの方も修正しました。
> Rさん
他の人にも「わかりやすい」ってのが難しいんですけどね。
C#経験者でVBAの仕事があり頑張っております。
このトピックスのようにルールやデザインパターンを解説してくださるととてもありがたいです。
ところで、クラスをCollectionにAddするということですが、
これはインスタンスを取り出すのにどうしたらよいのでしょうか?
Dim hoge as MyClass
Dim hoges as Collection
hoges.add hoge
この後にMyClassにあるHogeHogeメソッドを使いたいとすると、どのように記述すればよいのでしょうか?
CTypeもないようですし、お返事いただけたら幸いです。