VBAでプログラムを書いていて、あれ、この動作はなんだろう、と戸惑ったことはありませんか?
VBAは入門者にとってはとっつきやすい言語で学習コストも高くありませんが、慣れてくると仕様に戸惑う面が少なからずある言語です。
この記事では、VBAの仕様の中から、特に戸惑うものをまとめて解説します。
Round関数にはバグがある
さて、これはけっこう衝撃かもしれません。
VBAで数値の四捨五入を行う関数はRonud関数ですが、このRound関数にはバグがあります。
(Excel 2003、Excel 2010で確認済み)
ときどき、
0.5
が切り上げられずに切り捨てられるのです。
いつも切り捨てられるなら百歩譲ってそういう仕様ともできますが、ときどきなのでもうRound関数は使えません。計算結果が間違っているのですから。
以下のコード
Sub test1() For i = 1 To 100000 v = i - 0.5 Cells(i, 1).Value = v Cells(i, 2).Value = Round(v) Next For r = 2 To 100000 - 2 Step 2 If Cells(r, 2).Value <> Cells(r + 1, 2).Value Then MsgBox (r + "行目で違いがありました") End If Next End Sub
は0.5から99999.5までの数を書き込み、隣に0の位で四捨五入した結果を書き込み、
1.5を四捨五入した結果と2.5を四捨五入した結果(ちなみに2です)
3.5を四捨五入した結果と4.5を四捨五入した結果(ちなみに4です)
・・・
(n-1)×2 + 1 + 0.5を四捨五入した結果とn×2 + 0.5を四捨五入した結果(ちなみにn×2です)
・・・
を比較して、違っていたらメッセージボックスを出すコードですが、このコードを実行してもメッセージボックスは出ません。
もちろん出なければおかしいのです。
(n-1)×2 + 1 + 0.5 → n×2
n×2 + 0.5 → n×2 + 1
ですから。
(Excel 2010で動作確認しています)
これはバグです。
Round関数を使う代わりに、ワークシート関数のRoundを使いましょう。
Round(n)
は
WorksheetFunction.Round(n , 0)
と等価です。
配列のインデックスは0から
配列を宣言するとき、たとえば要素数が3の配列なら、以下のように宣言すると思います。
Dim a(3)
これは、他の言語から来た慣習かもしれません。
このとき、
a(1)
a(2)
a(3)
を使う場合が多いようです。
ところが、実際にはこのとき、
a(0)
も確保されているのです。
つまり、
Dim a(3)
とすると、要素数は4つとなります。
かといって、
Dim a(2)
として、a(0)、a(1)、a(2)を使うのは何とも分かりにくいですね。
やはり
Dim a(3)
とするべきです。
自分たちの書いたコードの中では、その配列のインデックスを0から使っているのか1から使っているのかを統一しておけばいいのですが、少し困ることがあります。
それは、文字列を特定の文字で区切り配列に代入して返す
Split関数
の戻り値の配列は0から始まる、ということです。インデックス0の要素に最初の分割された文字列が入っています。
これは割と見落としがちな点です。注意しましょう。
動的配列はPreserveを付けないと値が保持されない
動的配列はReDimで宣言します。ReDimを繰り返すことで、配列の要素数を動的に変えられます。
ところがこのReDim、一点注意しなければならないことがあります。
それは、2度目のReDim以降で、Preserveを付けていないと、配列の値が全てクリアされることです。
以下のコード
Sub test2() ReDim a(2) a(0) = 1 a(1) = 3 a(2) = 2 Cells(1, 1).Value = a(0) Cells(2, 1).Value = a(1) Cells(3, 1).Value = a(2) MsgBox ("OK?") ReDim a(5) Cells(1, 1).Value = a(0) Cells(2, 1).Value = a(1) Cells(3, 1).Value = a(2) End Sub
は期待通りの動作をしません。セルの値はクリアされます。
正しくは以下のように書きます。
Sub test2() ReDim a(2) a(0) = 1 a(1) = 3 a(2) = 2 Cells(1, 1).Value = a(0) Cells(2, 1).Value = a(1) Cells(3, 1).Value = a(2) MsgBox ("OK?") ReDim Preserve a(5) Cells(1, 1).Value = a(0) Cells(2, 1).Value = a(1) Cells(3, 1).Value = a(2) End Sub
これは知っていないとできないポイントです。注意してください。
スクレイピングにIEしか使えない
VBAを使ってもスクレイピングをすることはできます。
ただし、注意しなくてはいけない点があります。
それは、ホームページを読み込むブラウザとして、IE(Internet Exploler)しか使えない、ということなのです。
これは重大な注意点です。
今どきのホームページは、もはやIEには対応していないホームページも多いです。
したがって、実質的にスクレイピングできない、ということが非常にしばしば起こります。
また、動的なホームページもスクレイピングできません。
JavaScriptで表示を変える動的なホームページをスクレイピングするとき、他の言語であればSeleniumを使い、その後必要に応じてスクレイピングするライブラリにソースを渡します。リアルタイムでソースを解析することが不可欠です。
ところがVBAでのスクレイピングにはそういった機能はありません。
したがって、スクレイピングできないサイトはますます多くなっています。
筆者の個人的な見解としては、スクレイピングにVBAを使うべきではないと考えています。
スクレイピングできないサイトがあまりにも多いからです。
Excelに結果を残すにしても、例えばPythonならopenpyxlなどがあります。他の言語からExcelに書き込むことが可能です。そちらを使うことにして、最初から、VBAでスクレイピングはしないようにしたほうが結果的に工数が短縮されると考えています。
システムコール呼び出しには十分気をつけて!
VBAからWindowsのAPIを呼び出すことが可能です。
しかしその際には十分注意しましょう。
そのマシンの管理者権限でログオンしていた場合(個人使用ならまずこれです)、Windows API呼び出しによって、システムの動作をおかしくしたりシステムを破壊したりすることはいとも簡単なことです。VBAにはそれだけの力があるのです。
API呼び出しの際のちょっとした不注意によって、そのような動作が引き起こされる可能性は十分にあります。意図していなくてもです。
ネットからダウンロードしたxlsmファイルのマクロがデフォルトで使えなくなっているのはそういった理由です。
ですので、API呼び出しには十分注意してください。
まとめ
この記事では、VBAの仕様の注意点について解説しました。
いずれも、分かっている人にはもはや常識ですが、初心者からすればやや戸惑いを覚えるものかもしれません。
しかし知は力です。覚えてしまえば簡単なことです。
十分注意してVBAを使いましょう。