104
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

公式ドキュメントが読まれない こんな世の中じゃ ポイズン

Last updated at Posted at 2019-07-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://qiita.com/mima_ita/items/29e02c755062921e5e87

 

104
72
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?