Coming from a Java/Scala background with a strong focus on OOP+FP, I recently started working with Excel+VBA to implement a front-end for a SaaS. It was like stepping way back in time, over two decades, in software engineering trying to understand how to use and exploit VBA.
In the ensuing 3 months of being immersed in VBA, I have ended up running into and then unique solving a number of problems using a more principled software engineering principled approach. This includes things like DRY (Don't Repeat Yourself which means eschew copy/pasta), DbC (Design by Contract which means define clear boundaries around types and functions), encapsulation, immutability, referential transparency, etc.
The first things I hit had to do with VBA's Array
. The code smell of the imperative solutions I found on StackOverflow and the web, in general, was very frustrating. Additionally, it seemed all the proposed solutions I found were not only not DRY (Don't Repeat Yourself) whatsoever, almost all of them seemed to have subtle errors for which they didn't account. After hitting issue after issue with just trying to figure out if an Array
was allocated, empty, or defined (non-empty), I finally set about trying to create an optimal solution.
Given my lack of VBA-specific experience (I have a year's worth of VBA 3.0 from back in 1994-1995), I'd like to understand to what degree the solution I am proposing is safe, robust, and performant.
And just as a reminder, I am desiring the critique to be more from a software engineering principles perspective. IOW, I am not focused on novice Excel macro programmers. Or nitpicking about particular VBA syntax and semantics (unless that specifically relates to DRY, DbC, etc.). The intention is to assist future Java/Scala/Python/etc. software engineers who must create and support VBA code bases. Like me.
Feedback is appreciated.
SIDENOTE: In this submission, to keep the discussion clean, I don't plan to discuss my unique VBA code formatting approach. If you are interested in a discussion around that, let me know in the comments below and I will start a separate review submission for that.
The main function, f_ua_lngSize
, just focuses on obtaining the size. The function can be called on any of the Array
's dimensions, and defaults to the first.
Public Const M_UA_SIZE_NOT_ARRAY As Long = -1
Public Const M_UA_SIZE_EMPTY As Long = 0
'Return Value:
' -1 - Not an Array
' 0 - Empty
' > 0 - Defined
Public Function f_ua_lngSize( _
ByRef pr_avarValues As Variant _
, Optional ByVal pv_lngDimensionOneBased As Long = 1 _
) As Long
Dim lngSize As Long: lngSize = M_UA_SIZE_NOT_ARRAY 'Default to not an Array
Dim lngLBound As Long
Dim lngUBound As Long
On Error GoTo Recovery
If (IsArray(pr_avarValues) = True) Then
lngSize = M_UA_SIZE_EMPTY 'Move default to Empty
lngLBound = LBound(pr_avarValues, pv_lngDimensionOneBased)
lngUBound = UBound(pr_avarValues, pv_lngDimensionOneBased)
If (lngLBound <= lngUBound) Then
lngSize = lngUBound - lngLBound + 1 'Non-Empty, so return size
End If
End If
NormalExit:
f_ua_lngSize = lngSize
Exit Function
Recovery:
GoTo NormalExit
End Function
Then I've created two helper functions, f_ua_blnIsEmpty
and f_ua_blnIsDefined
, which just interpret calls to f_ua_lngSize
above.
Public Function f_ua_blnIsEmpty( _
ByRef pr_avarValues As Variant _
, Optional ByVal pv_lngDimensionOneBased As Long = 1 _
) As Boolean
f_ua_blnIsEmpty = f_ua_lngSize(pr_avarValues, pv_lngDimensionOneBased) = 0
End Function
Public Function f_ua_blnIsDefined( _
ByRef pr_avarValues As Variant _
, Optional ByVal pv_lngDimensionOneBased As Long = 1 _
) As Boolean
f_ua_blnIsDefined = f_ua_lngSize(pr_avarValues, pv_lngDimensionOneBased) > 0
End Function
Variant
which I understood to have very poor performance. I do realize the code above is based onVariant
as that is the only way to create collection "generic" code in VBA, from what I can tell. \$\endgroup\$