SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

ここが辛いよExcelVBA

ただの感想というか…そういうネタです。

仕事柄、不本意ながらVBAを触る機会*1があります。
VBA…自分でメンテナンスできないなら最初から使うな、とは言いたいところですが、来てしまうので対応しています。

まあ…所詮はOfficeのおまけなので、機能は少ないし、今後発展する可能性も無いです。
C#が得意な筆者から見て、ExcelVBAの辛いところを語ってみます。

コンストラクタが無い

無いんです!

VBAインスタンスを作成する場合は、

Dim item As MenuItem
Set item = New MenuItem

と書きますが、

var item = new MenuItem(name);

みたいに、コンストラクタで引数を渡す方法が提供されていません。

※初期化と終了処理はあります。クラスに以下の処理を記述します。
インタンス生成後、はじめて参照された時点で、自動的に呼ばれます。
どのクラスであっても、同じ記述で良いです。

Private Sub Class_Initialize()
    fName = "My Name"
End Sub

For Each

int total = 0;
foreach(var item in items) {
    total += item.Point;
}

みたいな、コレクションを順次処理する構文は、VBAにもあります。
が…

Dim items() As MenuItem

'ループ変数定義
Dim item As MenuItem

'ループ本体
For Each item in items
    Debug.Print item.Point
Next item

上の処理はコンパイルエラーが発生します。
なぜか、For Eachで配列をループさせる場合は、Variantで変数を受けるというルールがあります。
配列なら型ぐらいわかるだろ!という気はしますが、ダメらしいです。

戻り値が分かりづらい

前提として言いますが、VBAは継承という概念がありません*2

よって、型がわかれば、その通りの動きをします。

しかし、よく使うRangeオブジェクトはかなり変な動きで…
対象として四角範囲、列、行を指定した場合で、それぞれ呼び出せないメソッドが変わったり、返してくる値が異なります。

Dim useRange As Range
Set useRange = Sheet.Range("~~")

という形で、Rangeを取得します。
例えば、Range.Countでは、以下の値が返ってきます。

指定値 返却値
四角指定 セル数
列指定 指定範囲の列数
行指定 指定範囲の行数

取得方法によってメソッドの意味が変わるなんて、止めていただきたいです。

何が困るかというと、引数でRangeを受け取る際、それがどうやって生成されたか、気にしなければならない点です。

変数設定

上で、さらっと書きましたが、VBAの変数設定は、

  • 基本型
Dim val As Integer
val = 100
  • クラス型
Dim useRange As Range
Set useRange = Sheet.Range("~~")

のように、Setを先頭に付与しなければなりません。

とても面倒です。
C#では、当然付ける必要が無く、付け忘れが多発します。

しかも、コンパイルエラーにならず、動かすまでエラーを吐かないのが、非常にいやらしいです。

コレクション

標準機能に絞ると、コレクションは「配列」と「コレクション(Collection)クラス」が該当します。
当然、ジェネリクスなどという、便利な代物はありません。

配列は、上で述べたように、ループ中でVariantで受ける制約が面倒で扱いづらい等、問題があります。

これの何が困るかというと…
Collectionをメソッド間で引き継げないという点です。

Collectionの中に何が入っているかわからない(なんでも入る)ため、中身を当てにした実装になってしまいます。
「引数でCollectionを受け取る」や、「クラスのメンバーとしてCollectionを公開する」というのが、非常にやりづらいです。

例外

C#でいう、try - catchなどという、おしゃれなものはありません。

エラーメッセージ

これはね…擁護できません。意味不明すぎて泣けます。

不満はあるのだけれども

オブジェクト指向でやろうとすると、いろいろと足りない部分は多いですが、そういうソースが書けないわけでもありません。

Rangeが扱いづらいのならば、使うクラスは限定して、独自定義したクラスのインスタンスを受け渡せばよいです。
Excelのシートにアクセスするクラスを、Viewとして切り出してしまう等です。

Collectionが公開に向かないなら、目的特化した、最低限の操作を提供したクラスを自分で用意し、コレクションを受け渡さなくとも処理できるようにするとか。

継承は使えないけど、インターフェイスは使えるので、依存性の注入*3で多態を実現するとか。

オブジェクト指向として、綺麗なソースを書こうとすると、工夫のし甲斐はあります。

おわりに

上で、VBAを擁護しましたが、結局は修行みたいなものです。
ガスコンロがあるのに、わざわざ火を熾している、という感じです。

今後、何か新規で作る場合、VBAを使うのは避けたほうが良いです。
使うとしても、自分の手元に留めましょう。

ではでは。


*1:業務効率化のためにマクロを使うのではなく、改修依頼が来る

*2:インターフェイスはある

*3:コンストラクタが使えないため、実質的に、メソッド注入しか選択肢にならない