ネットや書籍で見かけるマクロでは、変数宣言はプロシージャの先頭にまとめられているものが多い。一般的に変数宣言はプロシージャの先頭にまとめて書くものだと紹介している記事さえある。
それはなぜかというと、みんながそうしてるからだ。
先頭にまとめるとコードが読みやすいとか、変数を一覧管理できて便利とかそういうことではない。書いてる本人も、なぜ先頭にまとめるべきなのか、確たる根拠を持っていないことが多いように思われる。
じゃあなぜ先頭に書くのが一般的になったのかというと、おそらくこれは初期のC言語に由来するものだろう。初期のC言語では、変数宣言は先頭に書かないとコンパイルできなかったようだ。そのようなコードでプログラミングを学んだ人たちが、VBAに同様の習慣を持ち込み、それが標準的な作法として扱われるようになったものと思われる。
現在ではローカル変数は使用する直前で宣言するのが主流
まずこちらの記事で述べられているレガシープログラマの判断項目「使われるローカル変数をすべてメソッドの最初に宣言する。」をご覧いただきたい。
blog.jnito.com
「できるだけ変数の寿命が短くなるように」とあるが、先頭で宣言してしまうとプロシージャの始まりから終わりまで変数の寿命が続くことになる。
たとえばバブルソートを例に、一般的とされる先頭での変数宣言で書いてみる。
Sub バブルソート() Dim arr(1 To 10) As Long Dim i As Long Dim j As Long Dim tmp As Long Dim min As Long Dim max As Long Dim LOWERBOUND As Long Dim UPPERBOUND As Long min = LBound(arr) max = UBound(arr) LOWERBOUND = 10 UPPERBOUND = 99 Debug.Print "ソート前" For i = min To max arr(i) = Int((UPPERBOUND - LOWERBOUND + 1) * Rnd + LOWERBOUND) Debug.Print arr(i) Next Debug.Print "ソート後" For i = min To max - 1 For j = i + 1 To max If arr(i) > arr(j) Then tmp = arr(i) arr(i) = arr(j) arr(j) = tmp End If Next Next For i = min To max Debug.Print arr(i) Next End Sub
ループ変数のiとjを例にとってみてみると、宣言からプロシージャの終了まで実に広い範囲で寿命を持っていることがわかる。
次に、変数宣言を使用の直前に変更してみた。
また、変数の使い回しをやめて新たにループ変数 k、l を作成し、minとmaxは変数を作らずに都度関数で求めるようにした。※配列の上限と下限を都度関数で計算させるのは賛否あると思うが後のコード分離を見越して余計な変数を減らしている。
Sub バブルソート() Dim arr(1 To 10) As Long Debug.Print "ソート前" Const LOWER_BOUND As Long = 10 Const UPPER_BOUND As Long = 99 Dim i As Long For i = LBound(arr) To UBound(arr) arr(i) = Int((UPPER_BOUND - LOWER_BOUND + 1) * Rnd + LOWER_BOUND) Debug.Print arr(i) Next Debug.Print "ソート後" Dim j As Long, k As Long, tmp As Long For j = LBound(arr) To UBound(arr) - 1 For k = j + 1 To UBound(arr) If arr(j) > arr(k) Then tmp = arr(j) arr(j) = arr(k) arr(k) = tmp End If Next Next Dim l As Long For l = LBound(arr) To UBound(arr) Debug.Print arr(l) Next End Sub
すると、各変数の寿命はこのとおり。
さらに寿命ではなく「使用範囲」と考えるとこのように狭い範囲に絞られる。
一つのマクロのなかにも、配列にランダム値を入れる、ソートする、表示するという風にいくつかの処理が存在するが、このように変数の使用範囲を狭めることで各処理の独立性が高まり、ほかに影響を与えにくくなる。
さて、各処理ごとにコメントを書きつつ、最初の表示部分を別処理に分離したものがこちら。
Sub バブルソート() Dim arr(1 To 10) As Long Debug.Print "ソート前" '■配列をランダムな整数で埋める。 Const LOWER_BOUND As Long = 10 Const UPPER_BOUND As Long = 99 Dim i As Long For i = LBound(arr) To UBound(arr) arr(i) = Int((UPPER_BOUND - LOWER_BOUND + 1) * Rnd + LOWER_BOUND) Next '■配列を表示させる。(上から分離して単機能化) Dim m As Long For m = LBound(arr) To UBound(arr) Debug.Print arr(m) Next Debug.Print "ソート後" '■配列をソートする。 Dim j As Long, k As Long, tmp As Long For j = LBound(arr) To UBound(arr) - 1 For k = j + 1 To UBound(arr) If arr(j) > arr(k) Then tmp = arr(j) arr(j) = arr(k) arr(k) = tmp End If Next Next '■配列を表示させる。 Dim l As Long For l = LBound(arr) To UBound(arr) Debug.Print arr(l) Next End Sub
処理の独立性が高まると次に何ができるかというと、前回紹介したプロシージャの分割である。
thom.hateblo.jp
まずは配列の表示から外だししてみる。この時ローカル変数はスコープを縮めているのでそのまま切り取ってもほかに影響を与えない。ただし配列arrはそうもいかないので、外から持ってこられるように引数にしておく。
Sub 配列を表示させる(arr) Dim m As Long For m = LBound(arr) To UBound(arr) Debug.Print arr(m) Next End Sub
メインコードはこうなる。
Sub バブルソート() Dim arr(1 To 10) As Long Debug.Print "ソート前" '■配列をランダムな整数で埋める。 Const LOWER_BOUND As Long = 10 Const UPPER_BOUND As Long = 99 Dim i As Long For i = LBound(arr) To UBound(arr) arr(i) = Int((UPPER_BOUND - LOWER_BOUND + 1) * Rnd + LOWER_BOUND) Next Call 配列を表示させる(arr) Debug.Print "ソート後" '■配列をソートする。 Dim j As Long, k As Long, tmp As Long For j = LBound(arr) To UBound(arr) - 1 For k = j + 1 To UBound(arr) If arr(j) > arr(k) Then tmp = arr(j) arr(j) = arr(k) arr(k) = tmp End If Next Next Call 配列を表示させる(arr) End Sub
この時配列の表示で使用していた変数mは、一般的なループ変数であるiに戻しておく。プロシージャを独立させたためメインコードのiと競合しなくなるからだ。
Sub 配列を表示させる(arr) Dim i As Long For i = LBound(arr) To UBound(arr) Debug.Print arr(i) Next End Sub
このようにどんどん分離させていくと最終的に以下のようになった。
Sub バブルソート() Dim arr(1 To 10) As Long Call 配列をランダムな整数で埋める(arr) Debug.Print "ソート前" Call 配列を表示させる(arr) Call 配列をソートする(arr) Debug.Print "ソート後" Call 配列を表示させる(arr) End Sub Sub 配列を表示させる(arr) Dim m As Long For m = LBound(arr) To UBound(arr) Debug.Print arr(m) Next End Sub Sub 配列をランダムな整数で埋める(arr) Const LOWER_BOUND As Long = 10 Const UPPER_BOUND As Long = 99 Dim i As Long For i = LBound(arr) To UBound(arr) arr(i) = Int((UPPER_BOUND - LOWER_BOUND + 1) * Rnd + LOWER_BOUND) Next End Sub Sub 配列をソートする(arr) Dim i As Long, j As Long, tmp As Long For i = LBound(arr) To UBound(arr) - 1 For j = i + 1 To UBound(arr) If arr(i) > arr(j) Then tmp = arr(i) arr(i) = arr(j) arr(j) = tmp End If Next Next End Sub
ローカル変数を使用する直前で宣言することで、各フェーズごとにコードの独立性が高まり、このようにコード分割の前準備にもなる。
コードをうまく分割できない、Functionが使いこなせないという方は、まず変数のスコープが大きすぎて他のフェーズとの依存関係を断ち切れないからじゃないかなと思う。VBAで一つのプロシージャに長々とコードを書いてしまうのは、ローカル変数の宣言を先頭でやっているのも原因の一つかもしれない。
また、コード分割しないにしても、変数宣言がスコープ開始の目印になるので、それ以前の箇所で変数がいじられていないことが明白になる。つまり各フェーズで把握すべき変数が絞られるのでコードの可読性が向上する。
いいことずくめ。
でもまあ、なんとなく変数宣言はプロシージャの先頭に書かないと気持ち悪いという方もいるだろう。
それはきっと単にそのようなコードに見慣れているからであり、なぜ見慣れているかといえばVBAでは皆そのようにコーディングしているからであり、突き詰めれば古いC言語の言語的制約でそのように書かれているものを見よう見まねでVBAに持ち込んだためである。
職場のコーディングルールなどがあって変数を先頭で宣言している方もいるだろうから、それはそれで間違っているとは思わないけれど、「一般的だから」という理由で採用しているのであれば、ぜひ再考してみてほしい。
単にVBAにおいて、いまだに変数をプロシージャの先頭で宣言するのが一般的だというだけであり、他言語のプログラマーはもうその書き方は古いものと見做している。
以下、変数のスコープを縮めることに言及している書籍紹介
良いコードを書く技術 ?読みやすく保守しやすいプログラミング作法 (WEB+DB PRESS plus)
- 作者: 縣俊貴
- 出版社/メーカー: 技術評論社
- 発売日: 2011/04/09
- メディア: 単行本(ソフトカバー)
- 購入: 46人 クリック: 2,459回
- この商品を含むブログ (68件) を見る
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
- 作者: Dustin Boswell,Trevor Foucher,須藤功平,角征典
- 出版社/メーカー: オライリージャパン
- 発売日: 2012/06/23
- メディア: 単行本(ソフトカバー)
- 購入: 68人 クリック: 1,802回
- この商品を含むブログ (140件) を見る
- 作者: 向山隆行,片山優司,阿部順一,寺田和朗,畑中良平,電通国際情報サービス
- 出版社/メーカー: 技術評論社
- 発売日: 2010/12/10
- メディア: 単行本(ソフトカバー)
- クリック: 26回
- この商品を含むブログ (1件) を見る