まえがき
むかーし、グーグルで適当な個人サイト探すより、なるべく公式ドキュメントを参照してねという動画を作ったことがあります。
ExcelVBA 一口講座 資料の調べ方 https://t.co/loD36SEETu #sm21809660 #ニコニコ動画
— m.ita (@mima_ita) 2019年7月19日
残念ながら、「動けばいいんだよ」勢には効果がなかったようなので、最近、見つけた一見動くが、公式ドキュメントで認められていないことやってバグが出る例を出してリベンジしようと思います。
問題
Excel VBAで動的配列の作成されているか否かを判断するにはどうしたらいいでしょうか?
たとえば以下のコードでエラーになる場合と、ならない場合を判別する方法を考えてみてください。
Public Sub test()
Dim v() As Variant
' Debug.Print UBound(v) ' エラーになる
ReDim v(1) As Variant
Debug.Print UBound(v)
Erase v
' Debug.Print UBound(v) ' エラーになる
End Sub
想定される回答
おそらく、googleとかで検索した場合に、この問題で想定される回答は次の4つになります。
OnErrorを使用する方法
動的配列が作成されていなかったり、削除されている場合でUBoundやLBoundでエラーが発生するなら、素直にエラーハンドリングをして動的配列の作成の有無をチェックします。
Public Sub testArraySample()
Dim v() As Variant
Debug.Assert isEmptyArray(v) = True
ReDim v(1) As Variant
Debug.Assert isEmptyArray(v) = False
Erase v
Debug.Assert isEmptyArray(v) = True
End Sub
Private Function isEmptyArray(v() As Variant) As Boolean
On Error GoTo ErrUbound:
Dim tmp As Long
tmp = UBound(v)
isEmptyArray = False
Exit Function
ErrUbound:
If Err.Number <> 9 Then
Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
End If
isEmptyArray = True
End Function
SafeArrayGetDimを利用する方法
VBAの配列は動的であれ、固定であれ、SafeArrayになっています。
http://msdn.microsoft.com/en-us/library/windows/desktop/ms221482%28v=vs.85%29.aspx
このSafeArrayの次元数はSafeArrayGetDimで取得できます。次元数が0ならまだ配列が作られていないと判断できます。
http://msdn.microsoft.com/en-us/library/windows/desktop/ms221539%28v=vs.85%29.aspx
Private Declare PtrSafe Function SafeArrayGetDim Lib "oleaut32" (ByRef psa() As Any) As Long
Private Function isEmptyArray(v() As Variant) As Boolean
If SafeArrayGetDim(v) <> 0 Then
isEmptyArray = False
Else
isEmptyArray = True
End If
End Function
Sgnを使う方法
Sgnを利用することで判定できます。
なんで動くかはよくわかりません(ドヤァ!)
Private Function isEmptyArray(v() As Variant) As Boolean
If Sgn(v) Then
isEmptyArray = False
Else
isEmptyArray = True
End If
End Function
Not Notを利用する場合
Not Notのテクニックを使うことで判定できます。
なんで動くかはよくわかりません(ドヤァ!)
Private Function isEmptyArray(v() As Variant) As Boolean
If Not Not v Then
isEmptyArray = False
Else
isEmptyArray = True
End If
End Function
各実装の評価
まず、簡単な動作確認だと、この4つのソースコードは、それっぽく動作します。
その上で、レビューで、このコードに遭遇した場合どう判断するか考えてみてください。
仮に私がレビュワーだった場合は以下のような判定をします。
OnErrorを使用する方法
VBAの言語仕様通りの実装なのでレビューを通します。
SafeArrayGetDimを利用する方法
WinAPIを使っているので32bitと64bitの両方のExcelで動くか微妙な宣言ですが、少なくともMSDNの根拠があるので32bit/64bit両方のExcelで動作確認しておくことという条件でレビューを通します。
また、レビュー時に、SafeArrayのあたりの根拠の説明ができない場合は、仮に動くコードでも却下します。
Sgnを使う方法
これは却下します。
この実装の動かないケースについては以下のページに詳しく書いてあります。
VBAで配列のNull判定にSgn関数を使ってはいけない
https://qiita.com/satoko138/items/7e06dda56683065968f7
簡単にいうと、配列の割り当て前はアドレスが0になる。
IF分岐でそのままだと判定できないので、Sgn関数を通してからIF分岐に渡している。
しかし、Sgn関数の仕様として引数には「Double containing any valid numeric expression.」とあるので、配列などを渡すと、一見動く場合もあるが、予期せぬ動作をしてしまう。
Not Notを使う方法
これも却下です。
Sgn関数の代わりにNot論理演算子をつかって回避していますが、VBAの論理演算子の説明に「§ A logical operator is invalid if the declared type of any operand is an array or a UDT.」、つまり論理演算子に配列渡しても無効と明記してあります。
「Sgnを使う方法」とことなり、予期せぬ結果になるパターンを見つけられていませんが、「見つからない=不具合がない」ということではないので、先の公式ページより信憑性の高い資料を提出しない限り却下です。
いいたいこと
・とりあえず公式のページを参考に論理をたてられるようにしましょう
→Qiita含めて個人サイトより優先的に参考にしましょう。
・もし、公式のページが読みづらくて、個人サイトとかを参考にするとしても、その個人サイトが公式を参考にしているかで信憑性を判断しましょう。
・動けばいいってのはだめです。
→テストの原則で「欠陥がない」ことは示せないから、よくわからんけど動いたなんてのはレビューで通せるわけないです。
・とりあえず公式ページ参照していれば、結果が間違っても責任回避できます。
(補足)
・Googleで調べて何故か公式より信憑性のないサイトが上にくる場合はGoogleの設定を英語設定にするとマシになるかもしれません
https://needtec.sakura.ne.jp/wod07672/?p=9205