Исходя из фона Java / Scala с сильным акцентом на ООП + ФП, Я недавно начал работать с Excel + VBA, чтобы реализовать интерфейс для SaaS. Это было похоже на шаг назад во времени, более чем на два десятилетия, в разработке программного обеспечения, пытаясь понять, как использовать и эксплуатировать VBA.
В последующие 3 месяца погружения в VBA я столкнулся с уникальным решением ряда проблем, используя более принципиальный подход к разработке программного обеспечения. Это включает в себя такие вещи, как СУХОЙ (Не повторяйся, что означает воздерживаться от копирования / пасты), DbC (Дизайн по контракту, что означает определение четких границ типов и функций), инкапсуляция, неизменность, ссылочная прозрачность, так далее.
Первое, что я сделал, было связано с VBA Array
. Запах кода императивных решений, которые я нашел на StackOverflow и в Интернете, в целом очень расстраивал. Вдобавок казалось, что все предлагаемые решения, которые я нашел, не только не были СУХИМИ (не повторяйся) вообще, почти все из них, казалось, имели небольшие ошибки, которые они не учитывали. После того, как вы столкнулись с проблемой за проблемой, просто пытаясь выяснить, есть ли Array
был выделен, пуст или определен (не пуст), я наконец попытался создать оптимальное решение.
Учитывая отсутствие у меня опыта работы с VBA (у меня есть годовой опыт работы с VBA 3.0 с 1994 по 1995 год), я хотел бы понять, в какой степени предлагаемое мной решение является безопасным, надежным и производительным.
И как напоминание, я хочу, чтобы критика была больше с точки зрения принципов разработки программного обеспечения. IOW, я не ориентирован на начинающих программистов макросов Excel. Или придирки к конкретному синтаксису и семантике VBA (если это специально не относится к DRY, DbC и т. Д.). Намерение состоит в том, чтобы помочь будущим Java / Scala / Python / и т. Д. инженеры-программисты, которые должны создавать и поддерживать кодовые базы VBA. Как я.
Обратная связь приветствуется.
ПРИМЕЧАНИЕ: В этой заявке, чтобы обсуждение было чистым, я не планирую обсуждать свой уникальный подход к форматированию кода VBA. Если вас интересует обсуждение этого вопроса, дайте мне знать в комментариях ниже, и я начну отдельную подачу обзора для этого.
Основная функция, f_ua_lngSize
, просто фокусируется на получении размера. Функцию можно вызвать на любом из Array
размеры и по умолчанию первое.
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
Затем я создал две вспомогательные функции, f_ua_blnIsEmpty
и f_ua_blnIsDefined
, которые просто интерпретируют вызовы f_ua_lngSize
выше.
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
2 ответа
Как только вы узнаете, что у вас есть массив, мне кажется, что в этом сравнении нет необходимости:
If (lngLBound <= lngUBound) Then
lngSize = lngUBound - lngLBound + 1 'Non-Empty, so return size
End If
Причина в том, что если у вас есть неинициализированный массив, то
lngLBound = 0
lngUBound = -1
Итак, ваше уравнение оценивается следующим образом:
lngSize = -1 - 0 + 1
Который означает, что lngSize = 0
Так что безопасно упростить основную функцию следующим образом:
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
lngLBound = LBound(pr_avarValues, pv_lngDimensionOneBased)
lngUBound = UBound(pr_avarValues, pv_lngDimensionOneBased)
lngSize = lngUBound - lngLBound + 1 ' return size
End If
NormalExit:
f_ua_lngSize = lngSize
Exit Function
Recovery:
GoTo NormalExit
End Function
Это делает M_UA_SIZE_EMPTY
неиспользованный.
Чтобы доказать это, я рекомендую написать набор модульных тестов, который проверяет все случаи, которые вы ожидаете обработать. Вы можете пройти тест по крайней мере для трех состояний, перечисленных над функцией.
Вы можете использовать RubberDuckVBA чтобы помочь вам писать и запускать модульные тесты. Он также предоставит вам некоторые из современных функций, которых вам не хватает в VBA. ПРЕДУПРЕЖДЕНИЕ: Он расскажет вам обо всех стилевых ошибках, которые вы делаете, о которых, по вашему мнению, не хотите слышать. Так что будьте готовы к изумлению, а также к тому, что ваши чувства обидятся.
ПРИМЕЧАНИЕ: Я ответил за исполнителя сокращением инструкции. Я ответил безопасно, предположив, что модульное тестирование — это ваша доказуемая мера безопасности. Что касается прочности, я думаю, что он делает то, что написано на банке, без побочных эффектов, так что это хорошо.
@Hackslash Утверждение
lngLBound = 0
lngUBound = -1
для неинициализированного массива неверно. Код ниже демонстрирует, почему
Public Sub ArrayTypeInfo()
Dim myUbound As Long
Dim myUboundMsg As String
On Error Resume Next
Debug.Print , , , "TypeName", "VarType", "IsArray", "IsNull", "IsEmpty", "Ubound"
Dim myLongs() As Long
myUbound = UBound(myLongs)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "Dim myLongs() As Long", , TypeName(myLongs), VarType(myLongs), IsArray(myLongs), IsNull(myLongs), IsEmpty(myLongs), myUboundMsg
On Error GoTo 0
On Error Resume Next
'@Demonstrates nicely the dangers of assumption after on error resume next
'@ its not a Ubound error. You can't assign an array created using array to an array not declared as variant holding array of x.
myLongs = Array(1&, 2&, 3&, 4&, 5&)
myUbound = UBound(myLongs)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myLongs = Array(1&, 2&, 3&, 4&, 5&)", TypeName(myLongs), VarType(myLongs), IsArray(myLongs), IsNull(myLongs), IsEmpty(myLongs), myUboundMsg
On Error GoTo 0
On Error Resume Next
ReDim myLongs(1 To 5)
myUbound = UBound(myLongs)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "ReDim myLongs(1 To 5)", , TypeName(myLongs), VarType(myLongs), IsArray(myLongs), IsNull(myLongs), IsEmpty(myLongs), myUboundMsg
On Error GoTo 0
On Error Resume Next
Debug.Print
Dim myArray As Variant
myUbound = UBound(myArray)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "Dim myArray As Variant", , TypeName(myArray), VarType(myArray), IsArray(myArray), IsNull(myArray), IsEmpty(myArray), myUboundMsg
On Error GoTo 0
On Error Resume Next
myArray = Array()
myUbound = UBound(myArray)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myArray = Array()", , TypeName(myArray), VarType(myArray), IsArray(myArray), IsNull(myArray), IsEmpty(myArray), myUboundMsg
On Error GoTo 0
On Error Resume Next
myArray = Array(1, 2, 3, 4, 5)
myUbound = UBound(myArray)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myArray = Array(1, 2, 3, 4, 5)", TypeName(myArray), VarType(myArray), IsArray(myArray), IsNull(myArray), IsEmpty(myArray), myUboundMsg
On Error GoTo 0
On Error Resume Next
myArray = myLongs
myUbound = UBound(myArray)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myArray = myLongs", , TypeName(myArray), VarType(myArray), IsArray(myArray), IsNull(myArray), IsEmpty(myArray), myUboundMsg
On Error GoTo 0
On Error Resume Next
Debug.Print
Dim myArrayOfVar() As Variant
myUbound = UBound(myArrayOfVar)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "Dim myArrayOfVar() As Variant", TypeName(myArrayOfVar), VarType(myArrayOfVar), IsArray(myArrayOfVar), IsNull(myArrayOfVar), IsEmpty(myArrayOfVar), myUboundMsg
On Error GoTo 0
On Error Resume Next
myArrayOfVar = Array()
myUbound = UBound(myArrayOfVar)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myArrayOfVar = Array()", , TypeName(myArrayOfVar), VarType(myArrayOfVar), IsArray(myArrayOfVar), IsNull(myArrayOfVar), IsEmpty(myArrayOfVar), myUboundMsg
On Error GoTo 0
On Error Resume Next
myArrayOfVar = Array(1&, 2&, 3&, 4&, 5&)
myUbound = UBound(myArrayOfVar)
myUboundMsg = IIf(Err.Number = 0, CStr(myUbound), "Ubound error")
Debug.Print "myArrayOfVar = Array(1&, 2&, 3&, 4&, 5&)", TypeName(myArrayOfVar), VarType(myArrayOfVar), IsArray(myArrayOfVar), IsNull(myArrayOfVar), IsEmpty(myArrayOfVar), myUboundMsg
On Error GoTo 0
On Error Resume Next
End Sub
что дает результат
TypeName VarType IsArray IsNull IsEmpty Ubound
Dim myLongs() As Long Long() 8195 True False False Ubound error
myLongs = Array(1&, 2&, 3&, 4&, 5&) Long() 8195 True False False Ubound error
ReDim myLongs(1 To 5) Long() 8195 True False False 5
Dim myArray As Variant Empty 0 False False True Ubound error
myArray = Array() Variant() 8204 True False False -1
myArray = Array(1, 2, 3, 4, 5) Variant() 8204 True False False 4
myArray = myLongs Long() 8195 True False False 5
Dim myArrayOfVar() As Variant Variant() 8204 True False False Ubound error
myArrayOfVar = Array() Variant() 8204 True False False -1
myArrayOfVar = Array(1&, 2&, 3&, 4&, 5&) Variant() 8204 True False False 4
Следует отметить, что если Ubound дает результат -1, также необходимо проверить Lbound на действительный результат, поскольку следующее определение вполне приемлемо в VBA.
Dim myArray(-10 to -1,-10 to -1, -10 to -1)
и, наконец, вам также нужно знать, что если Lbound и Ubound имеют одинаковый знак, то размер можно рассчитать с помощью Ubound-Lbound + 1, НО если они имеют противоположные знаки, тогда размер будет просто Ubound-Lbound.