En tant que fervent partisan du contrôle de version dans Microsoft Access, je dois parler de mon plus gros reproche avec l'environnement de développement VBA :la "remise en cas" automatique des identifiants. Considérez cela comme une extension de ma réponse à une question sur cette "fonctionnalité" sur stackoverflow.
Je vais aborder cet article en deux parties. Dans la partie 1, je définirai le comportement de l'environnement de développement. Dans la partie 2, je discuterai de ma théorie sur les raisons pour lesquelles cela fonctionne de cette façon.
Partie 1 :Définir le comportement
Si vous avez passé du temps à écrire du code en VBA, je suis sûr que vous avez remarqué cette "fonctionnalité". Lorsque vous saisissez des identifiants (variables, noms de fonctions, énumérations, etc.), vous remarquerez peut-être que l'EDI modifie automatiquement la casse de ces identifiants. Par exemple, vous pouvez taper un nom de variable entièrement en lettres minuscules, mais dès que vous passez à une nouvelle ligne, la première lettre de votre variable passe soudainement en majuscule.
La première fois que vous voyez cela, cela peut être choquant. Au fur et à mesure que vous continuez à programmer, l'IDE continue de changer la casse sur vous apparemment au hasard. Mais, si vous passez suffisamment de temps dans l'EDI, le modèle finit par se révéler.
Pour vous éviter d'avoir à passer plus de dix ans de votre vie à attendre que le modèle se révèle à vous, je vais maintenant décrire le modèle tel que j'en suis venu à le comprendre. À ma connaissance, Microsoft n'a jamais officiellement documenté ce comportement.
- Tous les changements de casse automatiques sont globaux pour le projet VBA.
- Chaque fois que la ligne de déclaration de l'un des types d'identifiants suivants est modifiée, la casse de tous les autres identifiants portant le même nom est également modifiée :
- Sous-nom
- Nom de la fonction
- Saisir le nom
- Nom de l'énumération
- Nom de la variable
- Nom constant
- Nom de la propriété
- Chaque fois qu'un nom d'élément enum est modifié n'importe où dans le code, la casse du nom de l'élément enum est mise à jour pour correspondre partout.
Parlons maintenant de chacun de ces comportements un peu plus en détail.
Modifications globales
Comme je l'ai écrit ci-dessus, les changements de cas d'identifiant sont globaux pour un projet VBA. En d'autres termes, l'IDE VBA ignore complètement la portée lors de la modification de la casse des identifiants.
Par exemple, supposons que vous ayez une fonction privée nommée AccountIsActive dans un module standard. Maintenant, imaginez un module de classe ailleurs dans ce même projet. Le module de classe a une procédure privée Property Get. À l'intérieur de cette procédure Property Get se trouve une variable locale nommée accountIsActive . Dès que vous tapez la ligne Dim accountIsActive As Boolean
dans l'IDE VBA et passez à une nouvelle ligne, la fonction CompteIsActive que nous avons défini séparément dans son propre module standard a sa ligne de déclaration changée en Private Function accountIsActive()
pour faire correspondre la variable locale à l'intérieur de ce module de classe.
C'est une bouchée, alors laissez-moi mieux le démontrer dans le code.
Étape 1 :Définir la fonction AccountIsActive
'--== Module1 ==--
Private Function AccountIsActive() As Boolean
End Function
Étape 2 :Déclarez la variable locale accountIsActive dans une portée différente
'--== Class1 ==--
Private Sub Foo()
Dim accountIsACTIVE As Boolean
End Sub
Étape 3 :VBA IDE... qu'avez-vous fait ?!?!
'--== Module1 ==--
Private Function accountIsACTIVE() As Boolean
End Function
Politique de non-discrimination de VBA Case-Obliteration
Non content d'ignorer simplement la portée, VBA ignore également les différences entre les types d'identificateurs dans sa quête pour imposer la cohérence de la casse. En d'autres termes, chaque fois que vous déclarez une nouvelle fonction, sous-routine ou variable qui utilise un nom d'identifiant existant, toutes les autres instances de cet identifiant voient leur casse modifiée pour correspondre.
Dans chacun de ces exemples ci-dessous, la seule chose que je change est le premier module répertorié. L'IDE VBA est responsable de toutes les autres modifications apportées aux modules précédemment définis.
Étape 1 :Définir une fonction
'--== Module1 ==--
Public Function ReloadDBData() As Boolean
End Function
Étape 2 :Définissez un abonné avec le même nom
REMARQUE :Ceci est parfaitement valable tant que les procédures sont dans des modules différents. Cela dit, ce n'est pas parce que vous *pouvez* faire quelque chose que vous *devriez*. Et vous *devriez* éviter cette situation si possible.
'--== Module2 ==--
Public Sub ReloadDbData()
End Sub
'--== Module1 ==--
Public Function ReloadDbData() As Boolean
End Sub
Étape 3 :Définissez un type portant le même nom
REMARQUE :Encore une fois, veuillez ne pas définir une sous-fonction, une fonction et un type avec le même nom dans un seul projet.
'--== Module3 ==--
Private Type ReLoadDBData
Dummy As Variant
End Type
'--== Module2 ==--
Public Sub ReLoadDBData()
End Sub
'--== Module1 ==--
Public Function ReLoadDBData() As Boolean
End Sub
Étape 4 :Définissez une énumération portant le même nom
NOTE :S'il vous plaît, s'il vous plaît, s'il vous plaît, pour l'amour de toutes les choses saintes...
'--== 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
Étape 5 :Définissez une variable portant le même nom
REMARQUE :En fait, nous continuons à le faire ?
'--== 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
Étape 6 :Définissez une constante portant le même nom
REMARQUE :Oh, allez. Sérieusement ?
'--== 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
Étape 7 :Définissez une propriété de classe portant le même nom
REMARQUE :Cela devient idiot.
'--== 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
Énumérer les éléments ?!?!
Pour ce troisième point, il est important de distinguer un type Enum et un élément Enum .
Enum EnumTypeName ' <-- Enum type
EnumItemAlice ' <-- Enum item
EnumItemBob ' <-- Enum item
End Enum
Nous avons déjà montré ci-dessus que les types Enum sont traités de la même manière que les autres types de déclarations, comme les subs, les fonctions, les constantes et les variables. Chaque fois que la ligne de déclaration d'un identifiant portant ce nom est modifiée, la casse de tous les autres identifiants du projet portant le même nom est mise à jour pour correspondre à la dernière modification.
Énumérer les éléments sont spéciaux en ce qu'ils sont le seul type d'identifiant dont la casse peut être modifiée à chaque fois que n'importe quelle ligne de code qui contient le nom de l'élément enum est modifié.
Étape 1. Définir et remplir l'énumération
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemAlice
EnumItemBob
End Enum
Étape 2. Reportez-vous aux éléments Enum dans le code
'--== Module8 ==--
Sub TestEnum()
Debug.Print EnumItemALICE, EnumItemBOB
End Sub
Résultat :la déclaration de type Enum change pour correspondre à la ligne de code normale
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemALICE
EnumItemBOB
End Enum
Partie 2 :Comment en sommes-nous arrivés là ?
Je n'ai jamais parlé à personne de l'équipe de développement interne de VBA. Je n'ai jamais vu de documentation officielle expliquant pourquoi l'IDE VBA fonctionne comme il le fait. Donc, ce que je suis sur le point d'écrire n'est que pure conjecture, mais je pense que cela a du sens.
Pendant longtemps, je me suis demandé pourquoi dans le monde l'IDE VBA aurait ce comportement. Après tout, c'est clairement intentionnel. La chose la plus simple à faire pour l'IDE serait... rien. Si l'utilisateur déclare une variable en majuscules, laissez-la en majuscules. Si l'utilisateur fait ensuite référence à cette variable en minuscules quelques lignes plus tard, laissez cette référence en minuscules et la déclaration d'origine en majuscules.
Ce serait une implémentation parfaitement acceptable du langage VBA. Après tout, le langage lui-même est insensible à la casse. Alors, pourquoi se donner la peine de changer automatiquement la casse de l'identifiant ?
Ironiquement, je crois que la motivation était d'éviter la confusion. (Swing et raté, si vous me demandez.) Je me moque de cette explication, mais elle a du sens.
Contraste avec les langues sensibles à la casse
Parlons d'abord des programmeurs issus d'un langage sensible à la casse. Une convention courante dans les langages sensibles à la casse, tels que C#, consiste à nommer les objets de classe avec des lettres majuscules et à nommer les instances de ces objets du même nom que la classe, mais avec une lettre minuscule au début.
Cette convention ne fonctionnera pas dans VBA, car deux identificateurs qui ne diffèrent que par la casse sont considérés comme équivalents. En fait, l'Office VBA IDE ne vous permet pas de déclarer simultanément une fonction avec un type de casse et une variable locale avec un type de casse différent (nous avons couvert cela de manière exhaustive ci-dessus). Cela empêche le développeur de supposer qu'il existe une différence sémantique entre deux identifiants avec les mêmes lettres mais une casse différente.
Faire en sorte que le mauvais code semble faux
L'explication la plus probable dans mon esprit est que cette "fonctionnalité" existe pour rendre les identifiants équivalents identiques. Pensez-y; sans cette fonctionnalité, il serait facile pour les fautes de frappe de se transformer en erreurs d'exécution. Vous ne me croyez pas ? Considérez ceci :
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
Si vous regardez rapidement le code ci-dessus, il semble assez simple. C'est une classe avec un .MyAccountName biens. La variable membre de la propriété est initialisée à une valeur constante lorsque l'objet est créé. Lors de la définition du nom de compte dans le code, la variable membre est à nouveau mise à jour. Lors de la récupération de la valeur de la propriété, le code renvoie simplement le contenu de la variable membre.
Du moins, c'est ce qu'il est censé faire. Si je copie le code ci-dessus et que je le colle dans une fenêtre VBA IDE, la casse des identifiants devient cohérente et les bogues d'exécution se manifestent soudainement :
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
Implémentation :est-ce vraiment la meilleure approche ?
Hum, non. Ne vous méprenez pas. En fait, j'aime beaucoup l'idée de changer automatiquement la capitalisation des identifiants pour maintenir la cohérence. Mon seul vrai reproche est que la modification est apportée à chaque identifiant portant ce nom dans l'ensemble du projet. Il serait bien mieux de changer la capitalisation des seuls identifiants qui font référence à la même "chose" (que cette "chose" soit une fonction, un sous-ensemble, une propriété, une variable, etc.).
Alors pourquoi cela ne fonctionne-t-il pas ainsi ? Je m'attends à ce que les développeurs VBA IDE soient d'accord avec mon point de vue sur la façon dont cela devrait fonctionner. Mais il y a une très bonne raison pourquoi l'IDE ne fonctionne pas de cette façon. En un mot, des performances.
Malheureusement, il n'y a qu'un seul moyen fiable de découvrir quels identifiants portant le même nom se réfèrent réellement à la même chose :analyser chaque ligne de code. C'est sloooowwwww. C'est plus qu'une simple hypothèse de ma part. Le projet Rubberduck VBA fait exactement cela; il analyse chaque ligne de code du projet afin de pouvoir effectuer une analyse de code automatisée et un tas d'autres trucs sympas.
Le projet est certes lourd. Cela fonctionne probablement très bien pour les projets Excel. Malheureusement, je n'ai jamais été assez patient pour l'utiliser dans aucun de mes projets Access. Rubberduck VBA est un projet techniquement impressionnant, mais c'est aussi un récit édifiant. Respecter la portée lors de la modification de la capitalisation des identifiants serait une bonne chose, mais pas au détriment des performances extrêmement rapides actuelles de VBA IDE.
Réflexions finales
Je comprends la motivation de cette fonctionnalité. Je pense que je comprends même pourquoi c'est implémenté comme ça. Mais c'est la bizarrerie la plus exaspérante de VBA pour moi.
Si je pouvais faire une seule recommandation à l'équipe de développement d'Office VBA, ce serait de proposer un paramètre dans l'IDE pour désactiver les changements de casse automatiques. Le comportement actuel peut rester activé par défaut. Mais, pour les utilisateurs expérimentés qui tentent de s'intégrer aux systèmes de contrôle de version, le comportement peut être complètement désactivé pour empêcher les "modifications de code" intempestives de polluer l'historique des révisions.