Access
 sql >> Base de Dados >  >> RDS >> Access

Recurso de alteração de caso do VBA


Como um forte defensor do controle de versão no Microsoft Access, preciso falar sobre minha maior reclamação com o ambiente de desenvolvimento VBA:"recasing" automático de identificadores. Pense nisso como uma expansão da minha resposta a uma pergunta sobre esse "recurso" no stackoverflow.

Vou abordar este artigo em duas partes. Na Parte 1, definirei o comportamento do ambiente de desenvolvimento. Na Parte 2, discutirei minha teoria sobre por que funciona dessa maneira.

Parte 1:Definindo o comportamento


Se você passou algum tempo escrevendo código em VBA, tenho certeza de que notou esse "recurso". Conforme você digita identificadores—variáveis, nomes de funções, enumerações, etc.—você pode notar que o IDE altera automaticamente as maiúsculas e minúsculas desses identificadores. Por exemplo, você pode digitar o nome de uma variável com todas as letras minúsculas, mas assim que passar para uma nova linha, a primeira letra da variável  de repente muda para maiúscula.

A primeira vez que você vê isso pode ser chocante. À medida que você continua programando, o IDE continua alterando o caso aparentemente de forma aleatória. Mas, se você passar bastante tempo no IDE, eventualmente o padrão se revelará.

Para evitar que você tenha que passar mais de dez anos de sua vida esperando que o padrão se revele a você, vou agora descrever o padrão como passei a entendê-lo. Que eu saiba,  a Microsoft nunca documentou oficialmente nenhum desses comportamentos.
  1. Todas as alterações automáticas de maiúsculas e minúsculas são globais para o projeto VBA.
  2. Sempre que a linha de declaração de qualquer um dos tipos de identificadores a seguir for alterada, todos os outros identificadores com o mesmo nome também serão alterados:
    • Subnome
    • Nome da função
    • Nome do tipo
    • Nome da enumeração
    • Nome da variável
    • Nome da constante
    • Nome da propriedade
  3. Sempre que um nome de item de enumeração é alterado em qualquer parte do código, a capitalização do nome do item de enumeração é atualizada para corresponder a todos os lugares.

Vamos falar sobre cada um desses comportamentos com um pouco mais de detalhes agora.

Mudanças globais


Como escrevi acima, as alterações do caso do identificador são globais para um projeto VBA. Em outras palavras, o IDE do VBA ignora completamente o escopo ao alterar o caso dos identificadores.

Por exemplo, digamos que você tenha uma função privada chamada AccountIsActive em um módulo padrão. Agora, imagine um módulo de classe em outro lugar nesse mesmo projeto. O módulo de classe tem um procedimento Property Get privado. Dentro desse procedimento Property Get está uma variável local chamada accountIsActive . Assim que você digitar a linha Dim accountIsActive As Boolean no IDE do VBA e vá para uma nova linha, a função AccountIsActive que definimos separadamente em seu próprio módulo padrão tem sua linha de declaração alterada para Private Function accountIsActive() para corresponder à variável local dentro deste módulo de classe.

Isso é um bocado, então deixe-me demonstrar melhor no código.

Etapa 1:definir a função AccountIsActive
'--== Module1 ==--
Private Function AccountIsActive() As Boolean
End Function

Etapa 2:declare a variável local accountIsActive em escopo diferente
'--== Class1 ==--
Private Sub Foo()
    Dim accountIsACTIVE As Boolean
End Sub

Etapa 3:IDE VBA... o que você fez?!?!
'--== Module1 ==--
Private Function accountIsACTIVE() As Boolean
End Function

Política de não discriminação de obliteração de caso do VBA


Não contente em simplesmente ignorar o escopo, o VBA também ignora as diferenças entre os tipos de identificadores em sua busca para impor consistência de maiúsculas e minúsculas. Em outras palavras, toda vez que você declara uma nova função, sub-rotina ou variável que usa um nome de identificador existente, todas as outras instâncias desse identificador têm suas maiúsculas e minúsculas alteradas para corresponder.

Em cada um desses exemplos abaixo, a única coisa que estou alterando é o primeiro módulo listado. O IDE VBA é responsável por todas as outras alterações nos módulos definidos anteriormente.

Etapa 1:definir uma função
'--== Module1 ==--
Public Function ReloadDBData() As Boolean
End Function

Etapa 2:defina uma sub com o mesmo nome

NOTA:Isso é perfeitamente válido desde que os procedimentos estejam em módulos diferentes. Dito isso, só porque você *pode* fazer alguma coisa, não significa que você *deveria*. E você *deve* evitar essa situação se possível.
'--== Module2 ==--
Public Sub ReloadDbData()
End Sub

'--== Module1 ==--
Public Function ReloadDbData() As Boolean
End Sub

Etapa 3:defina um tipo com o mesmo nome

OBSERVAÇÃO:Novamente, não defina uma sub, função e digite tudo com o mesmo nome em um único projeto.
'--== Module3 ==--
Private Type ReLoadDBData
    Dummy As Variant
End Type

'--== Module2 ==--
Public Sub ReLoadDBData()
End Sub

'--== Module1 ==--
Public Function ReLoadDBData() As Boolean
End Sub

Etapa 4:definir uma enumeração com o mesmo nome

NOTA:Por favor, por favor, por favor, pelo amor de todas as coisas sagradas...
'--== Module4 ==--
Public Enum ReloadDbDATA
    Dummy
End Enum

'--== Module3 ==--
Private Type ReloadDbDATA
    Dummy As Variant
End Type

'--== Module2 ==--
Public Sub ReloadDbDATA()
End Sub

'--== Module1 ==--
Public Function ReloadDbDATA() As Boolean
End Sub

Etapa 5:defina uma variável com o mesmo nome

OBSERVAÇÃO:ainda estamos fazendo isso?
'--== Module5 ==--
Public reloaddbdata As Boolean

'--== Module4 ==--
Public Enum reloaddbdata
    Dummy
End Enum

'--== Module3 ==--
Private Type reloaddbdata
    Dummy As Variant
End Type

'--== Module2 ==--
Public Sub reloaddbdata()
End Sub

'--== Module1 ==--
Public Function reloaddbdata() As Boolean
End Sub

Etapa 6:defina uma constante com o mesmo nome

NOTA:Ah, vamos lá. Sério?
'--== Module6 ==--
Private Const RELOADDBDATA As Boolean = True

'--== Module5 ==--
Public RELOADDBDATA As Boolean

'--== Module4 ==--
Public Enum RELOADDBDATA
    Dummy
End Enum

'--== Module3 ==--
Private Type RELOADDBDATA
    Dummy As Variant
End Type

'--== Module2 ==--
Public Sub RELOADDBDATA()
End Sub

'--== Module1 ==--
Public Function RELOADDBDATA() As Boolean
End Sub

Etapa 7:defina uma propriedade de classe com o mesmo nome

OBSERVAÇÃO:isso está ficando bobo.
'--== Class1 ==--
Private Property Get reloadDBData() As Boolean
End Property

'--== Module6 ==--
Private Const reloadDBData As Boolean = True

'--== Module5 ==--
Public reloadDBData As Boolean

'--== Module4 ==--
Public Enum reloadDBData
    Dummy
End Enum

'--== Module3 ==--
Private Type reloadDBData
    Dummy As Variant
End Type

'--== Module2 ==--
Public Sub reloadDBData()
End Sub

'--== Module1 ==--
Public Function reloadDBData() As Boolean
End Sub

Itens de enumeração?!?!


Para este terceiro ponto, é importante distinguir entre um tipo Enum e um item de enumeração .
Enum EnumTypeName   ' <-- Enum type
    EnumItemAlice   ' <-- Enum item
    EnumItemBob     ' <-- Enum item
End Enum

Já mostramos acima que os tipos Enum são tratados da mesma forma que outros tipos de declarações, como subs, funções, constantes e variáveis. Sempre que a linha de declaração de um identificador com esse nome for alterada, todos os outros identificadores no projeto com o mesmo nome terão suas maiúsculas atualizadas para corresponder à última alteração.

Enum itens são especiais por serem o único tipo de identificador cujas maiúsculas podem ser alteradas sempre que qualquer linha de código que contém o nome do item de enumeração é alterado.

Etapa 1. Defina e preencha o Enum

'--== Module7 ==--
Public Enum EnumTypeName
    EnumItemAlice
    EnumItemBob
End Enum

Etapa 2. Consulte os itens de Enum no código

'--== Module8 ==--
Sub TestEnum()
    Debug.Print EnumItemALICE, EnumItemBOB
End Sub

Resultado:a declaração de tipo de enumeração muda para corresponder à linha regular de código

'--== Module7 ==--
Public Enum EnumTypeName
    EnumItemALICE
    EnumItemBOB
End Enum

Parte 2:Como chegamos aqui?


Eu nunca falei com ninguém da equipe interna de desenvolvimento do VBA. Eu nunca vi nenhuma documentação oficial sobre por que o VBA IDE funciona da maneira que funciona. Então, o que estou prestes a escrever é pura conjectura, mas acho que faz algum sentido.

Por muito tempo, me perguntei por que diabos o IDE VBA teria esse comportamento. Afinal, é claramente intencional. A coisa mais fácil para o IDE fazer seria... nada. Se o usuário declarar uma variável em maiúsculas, deixe-a em maiúsculas. Se o usuário fizer referência a essa variável em letras minúsculas algumas linhas depois, deixe essa referência em letras minúsculas e a declaração original em maiúsculas.

Esta seria uma implementação perfeitamente aceitável da linguagem VBA. Afinal, a linguagem em si não diferencia maiúsculas de minúsculas. Então, por que se dar ao trabalho de alterar automaticamente a caixa do identificador?

Ironicamente, acredito que a motivação foi evitar confusão. (Swing e um erro, se você me perguntar.)  Eu zombo dessa explicação, mas faz algum sentido.

Contraste com idiomas que diferenciam maiúsculas de minúsculas


Primeiro, vamos falar sobre programadores vindos de uma linguagem que diferencia maiúsculas de minúsculas. Uma convenção comum em linguagens que diferenciam maiúsculas de minúsculas, como C#, é nomear objetos de classe com letras maiúsculas e nomear instâncias desses objetos com o mesmo nome da classe, mas com uma letra minúscula inicial.

Essa convenção não funcionará no VBA, porque dois identificadores que diferem apenas em maiúsculas e minúsculas são considerados equivalentes. Na verdade, o Office VBA IDE não permite que você declare simultaneamente uma função com um tipo de caixa e uma variável local com um tipo diferente de caixa (abordamos isso exaustivamente acima). Isso evita que o desenvolvedor assuma que há uma diferença semântica entre dois identificadores com as mesmas letras, mas com maiúsculas e minúsculas diferentes.

Fazer o código errado parecer errado


A explicação mais provável em minha mente é que esse "recurso" existe para fazer com que identificadores equivalentes pareçam idênticos. Pense nisso; sem esse recurso, seria fácil para erros de digitação se transformarem em erros de tempo de execução. Não acredite em mim? Considere isto:
Private mAccountName As String
Private Const ACCOUNT_NAME As String = "New User"

Private Sub Class_Initialize()
    mAccountName = ACCOUNT_NAME
End Sub

Public Property Get MyAccountName() As String
    MAccountName = Account_Name
End Property

Public Property Let MyAccountName(AccountName As String)
    mAccountName = Account_Name
End Property

Se você olhar rapidamente para o código acima, parece bastante simples. É uma classe com um .MyAccountName propriedade. A variável de membro da propriedade é inicializada com um valor constante quando o objeto é criado. Ao definir o nome da conta no código, a variável de membro é atualizada novamente. Ao recuperar o valor da propriedade, o código apenas retorna o conteúdo da variável membro.

Pelo menos, é isso que deve fazer. Se eu copiar o código acima e colá-lo em uma janela VBA IDE, a caixa dos identificadores se tornará consistente e os bugs de tempo de execução aparecerão de repente:
Private mAccountName As String
Private Const ACCOUNT_NAME As String = "New User"

Private Sub Class_Initialize()
    mAccountName = ACCOUNT_NAME   ' <- This is OK
End Sub

Public Property Get MyAccountName() As String
    mAccountName = ACCOUNT_NAME   ' <- This is probably not what we intended
End Property

Public Property Let MyAccountName(AccountName As String)
    mAccountName = ACCOUNT_NAME   ' <- This is definitely not what we meant
End Property

Implementação:essa é realmente a melhor abordagem?


Umm não. Não me entenda mal. Na verdade, gosto muito da ideia de alterar automaticamente a capitalização dos identificadores para manter a consistência. Minha única reclamação real é que a alteração é feita em todos os identificadores com esse nome em todo o projeto. Muito melhor seria alterar a capitalização apenas dos identificadores que se referem à mesma "coisa" (se essa "coisa" é uma função, sub, propriedade, variável etc.).

Então por que não funciona assim? Espero que os desenvolvedores do VBA IDE concordem com minha perspectiva sobre como deve funcionar. Mas há uma boa razão por que o IDE não funciona dessa maneira. Em uma palavra, desempenho.

Infelizmente, há apenas uma maneira confiável de descobrir quais identificadores com o mesmo nome realmente se referem à mesma coisa:analisar cada linha de código. Isso é sloooooowwww. Isso é mais do que uma simples hipótese da minha parte. O projeto Rubberduck VBA realmente faz exatamente isso; ele analisa cada linha de código no projeto para que possa fazer análises de código automatizadas e um monte de outras coisas legais.

O projeto é reconhecidamente pesado. Provavelmente funciona muito bem para projetos do Excel. Infelizmente, nunca tive paciência suficiente para usá-lo em nenhum dos meus projetos do Access. Rubberduck VBA é um projeto tecnicamente impressionante, mas também é um alerta. Respeitar o escopo ao alterar a capitalização para identificadores seria bom ter, mas não às custas do atual desempenho incrivelmente rápido do VBA IDE.

Considerações finais


Eu entendo a motivação para esse recurso. Acho que até entendo porque é implementado do jeito que é. Mas é a peculiaridade mais enlouquecedora do VBA para mim.

Se eu pudesse fazer uma única recomendação para a equipe de desenvolvimento do Office VBA, seria oferecer uma configuração no IDE para desabilitar as alterações automáticas de maiúsculas e minúsculas. O comportamento atual pode permanecer ativado por padrão. Mas, para usuários avançados que estão tentando se integrar a sistemas de controle de versão, o comportamento pode ser completamente desabilitado para evitar que "mudanças de código" incômodas poluam o histórico de revisões.