t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA ユーザーの領分を侵さないコードを書く ~ SelectionやActiveWorkbookはユーザーのもの

セルやブックの操作にSelectionやActiveWorkbookが多用されているコードを見かけることがある。特にマクロの記録を使った場合は顕著だ。
これはこれでちゃんと動くのだが、できればちゃんと変数に格納したほうが良いと思う。

さて、いちいちSelectionを用いると速度が遅いから良くないと説明されることがあるが、これはあまり納得度の高い説明とは言えない。速度を問題にするなら、別に急いでないからSelectionでいいやとか、これで十分早いから何が問題なの?という意見も出るだろう。

では何が問題なのだろうか。

SelectionやActiveWorkbookはスコープが広い。

Selectionは今選択されているものが格納されている変数のようなものである。

わかりやすいプログラムを書くために、変数の有効範囲は狭ければ狭いほど良い。この有効範囲のことをスコープという。
thom.hateblo.jp

たとえば、For文で使用する変数iは、そのFor文の中だけで完結できるのが理想的だ。VBAではブロックレベルスコープが無いのでそうしたコードは書けないが、それでも1画面に収まる程度に使用範囲を限定することでその変数の役割が把握しやすく、コードが理解しやすくなる。

しかしSelectionやActiveWorkbookはどこからでもアクセスすることができ、そのスコープはプログラムを実行する前からすでに始まっている。
一般的に広いスコープを持つ変数は名前からその役割を特定できないと困るのだが、Selectionという名前からは「今選択されているもの」ということは分かってもそれが何を指しているのかは前後の文脈を見ないとわからない。

つまりコードが文脈依存になってしまうのだ。

ためしにマクロの記録で作成したコードを見てみる。

Sub Sample()
1    Windows("Sample.xlsm").Activate
2    Sheets("Sheet1").Select
3    Range("B3").Select
4    ActiveCell.FormulaR1C1 = "Hello"
5    Range("C4").Select
6    ActiveCell.FormulaR1C1 = "VBA"
7    Windows("Sample2.xlsm").Activate
8    Sheets("Sheet1").Select
9    Range("B3").Select
10  ActiveCell.FormulaR1C1 = "Hello"
11  Range("C4").Select
12  ActiveCell.FormulaR1C1 = "VBA"
End Sub

このようなマクロでは、常に今どのブックの、どのシートの、どのセルがアクティブなのかを意識する必要がある。
これは、6行目を読んでいるときに、1行目、2行目、5行目を意識する必要があるということだ。
小規模なマクロならこのように時系列で追っていくこともできるが、コードが大きくなると破綻する。

名前とものを一対一で対応付ける

次に、書き込むシートをそれぞれ変数に入れてみる。

Sub Sample2()
1    Set sh1 = Workbooks("Sample.xlsm").Sheets("Sheet1")
2    Set sh2 = Workbooks("Sample2.xlsm").Sheets("Sheet1")
3    sh1.Range("B3").Value = "Hello"
4    sh1.Range("C4").Value = "VBA"
5    sh2.Range("B3").Value = "Hello"
6    sh2.Range("C4").Value = "VBA"
End Sub

ついでにFormulaR1C1もValueに修正。
さっきよりずいぶんコンパクトになった。

最大の改善点は、ひとつの名前が常に同じものを指す状態を作り出したことである。
SelectionやActive○○は文脈に依存していたが、sh1は常にSample.xlsmのSheet1を指す。
名前とものが一対一で紐づくことで、どういった文脈で現れても名前を見れば何を指すのかがわかる。

今回はsh1などと適当な名前を付けているが、たとえば入力シートと計算シートがあるのなら、shInput、shCalcなどと自分でわかりやすい変数名を付けると良い。日本語も使えるので、そのまま「入力シート」という名前の変数でも良い。
これで文脈依存から抜け出し、コードを読むときに同時に意識するべき行数が減るので思考もクリアになる。

SelectionやAvtiveWorkbookはユーザーの領分である

「今選択しているセル」「現在アクティブなブック」は本来ユーザーがExcel上の操作で行うものなので、どちらかといえばユーザーの領分である。
私はプログラムがユーザーの領分に立ち入って好き勝手振る舞うというのはあまりよろしくないと考えている。
理想を言えばこれらはキッチリ線引きするのが良いと思う。
f:id:t-hom:20161023125312p:plain

まあユーザーの領分に立ち入らなければできないことも多いのでなかなかそうもいかないのだけれど、不要なSelectやActivateはなるべく避けたい。

以下にユーザーの領分に極力立ち入らずに済む方法を考えてみる。

SelectionやActive○○

既に紹介したように変数を用いることでユーザー領分への立ち入りを回避することができる。
マクロの開始前と終了後で、ユーザーが最後に使用していたブックが変わってしまうというのは良くない。
要はマクロが好き勝手にブックやシートをいじり倒した結果、あと片づけもせずに終了するのがマズイのである。
逆にそのマクロによって何が起きたのかを知らせるためにあえて最後に特定のブック、特定の箇所をSelectやActivateさせるのは良いアイデアだと思う。

ブック名

ユーザーが自由に書き換える可能性があるという意味ではユーザーの領分である。
たとえば「Sample.xlsm」のモジュールに書くコードで、Workbooks("Sample.xlsm")という指定はよろしくない。
ファイル名が変わったら動かなくなるからだ。コードが書かれた自身のブックを指すときは、ThisWorkbookを用いるのが良い。

シート名や並び順

本来はユーザーの領分である。シートの指定にはシート名、並び順のほかに、オブジェクト名による直接指定もある。

以下参考記事
thom.hateblo.jp

そのブックに固定のシートであれば、オブジェクト名による指定が望ましい。そうすればユーザーによってシート名が変更されたり並び順が変わってもマクロは動作する。
※ただしユーザーがそのシートを消して作り直した場合はどうしようもない。

SelectionやActive○○のより良い使い方

これはあくまで私個人の考えだけれど、SelectionやActive○○は、ユーザーとプログラムがコミュニケーションするために使うオブジェクトだと思う。これらを用いると、ユーザーがExcelの操作で選択した領域に対して、マクロを作用させることができる。
また、VBAのコードで特定のセルをSelectしたりActivateしたりもできるが、それはユーザーに特定箇所を見せたい場合に使う。いわば演出のための命令として利用できる。

単に文字入力や背景色を変えるための手段としてセルを選択したり、シートを切り替えたりするのは、プログラムが自分の仕事を進めるためにユーザーの領分を勝手に荒らしまわっているようにも見える。

速度、スコープの広さ、名前のわかりにくさという観点からも、ユーザー領分への不必要な立ち入りは避けたいと思う。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。