Насколько моя функция размера массива VBA безопасна, надежна и высокопроизводительна?

Исходя из фона 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 ответа
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.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *