Som en stærk fortaler for versionskontrol i Microsoft Access, er jeg nødt til at tale om mit største greb med VBA-udviklingsmiljøet:automatisk "recasing" af identifikatorer. Tænk på dette som en udvidelse af mit svar på et spørgsmål om denne "funktion" på stackoverflow.
Jeg vil nærme mig denne artikel i to dele. I del 1 vil jeg definere udviklingsmiljøets adfærd. I del 2 vil jeg diskutere min teori om, hvorfor det virker på denne måde.
Del 1:Definition af adfærden
Hvis du har brugt noget tid på at skrive kode i VBA, er jeg sikker på, at du har bemærket denne "funktion." Mens du indtaster identifikatorer – variabler, funktionsnavne, enums osv. – vil du muligvis bemærke, at IDE automatisk ændrer størrelsen af disse identifikatorer. Du kan f.eks. skrive et variabelnavn med små bogstaver, men så snart du flytter til en ny linje, skifter det første bogstav i din variabel pludselig til store bogstaver.
Første gang du ser dette kan det være skurrende. Mens du fortsætter programmeringen, fortsætter IDE med at ændre sagen på dig tilsyneladende tilfældigt. Men hvis du bruger nok tid i IDE, afslører mønsteret sig selv til sidst.
For at redde dig fra at skulle bruge ti-plus år af dit liv på at vente på, at mønsteret åbenbarer sig for dig, vil jeg nu beskrive mønsteret, som jeg er kommet til at forstå det. Så vidt jeg ved, har Microsoft aldrig officielt dokumenteret noget af denne adfærd.
- Alle automatiske sagsændringer er globale for VBA-projektet.
- Når erklæringslinjen for en af følgende typer identifikatorer ændres, ændres også små og store bogstaver for hver anden identifikator med samme navn:
- Undernavn
- Funktionsnavn
- Skriv navn
- Enum-navn
- Variabelnavn
- Konstant navn
- Ejendomsnavn
- Når et navn på enum-elementet ændres hvor som helst i koden, opdateres hoved- og bogstaverne i enum-elementets navn, så det passer overalt.
Lad os nu tale om hver af disse adfærdsmønstre lidt mere detaljeret.
Globale ændringer
Som jeg skrev ovenfor, er ændringer i identifikatortilfælde globale for et VBA-projekt. Med andre ord ignorerer VBA IDE fuldstændigt omfanget, når det ændrer tilfælde af identifikatorer.
Lad os f.eks. sige, at du har en privat funktion ved navn AccountIsActive i et standardmodul. Forestil dig nu et klassemodul et andet sted i det samme projekt. Klassemodulet har en privat Property Get-procedure. Inde i denne Property Get-procedure er en lokal variabel ved navn accountIsActive . Så snart du skriver linjen Dim accountIsActive As Boolean
ind i VBA IDE og flyt til en ny linje, funktionen AccountIsActive som vi definerede separat i sit eget standardmodul, har sin erklæringslinje ændret til Private Function accountIsActive()
for at matche den lokale variabel inde i dette klassemodul.
Det er en mundfuld, så lad mig demonstrere det bedre i kode.
Trin 1:Definer AccountIsActive-funktionen
'--== Module1 ==--
Private Function AccountIsActive() As Boolean
End Function
Trin 2:Erklær accountIsActive lokal variabel i andet omfang
'--== Class1 ==--
Private Sub Foo()
Dim accountIsACTIVE As Boolean
End Sub
Trin 3:VBA IDE...hvad har du gjort?!?!
'--== Module1 ==--
Private Function accountIsACTIVE() As Boolean
End Function
VBA Case-Obliterations politik for ikke-diskrimination
VBA er ikke tilfreds med blot at ignorere omfanget, men ignorerer også forskelle mellem typer af identifikatorer i sin bestræbelse på at påtvinge konsistens i kabinetter. Med andre ord, hver gang du erklærer en ny funktion, subrutine eller variabel, der bruger et eksisterende identifikationsnavn, vil alle andre forekomster af denne identifikator få deres store og små bogstaver ændret til at matche.
I hvert af disse eksempler nedenfor er det eneste, jeg ændrer, det første modul på listen. VBA IDE er ansvarlig for alle de andre ændringer af tidligere definerede moduler.
Trin 1:Definer en funktion
'--== Module1 ==--
Public Function ReloadDBData() As Boolean
End Function
Trin 2:Definer en undergruppe med samme navn
BEMÆRK:Dette er helt gyldigt, så længe procedurerne er i forskellige moduler. Når det er sagt, bare fordi du *kan* gøre noget, betyder det ikke, at du *burde*. Og du *bør* undgå denne situation, hvis det overhovedet er muligt.
'--== Module2 ==--
Public Sub ReloadDbData()
End Sub
'--== Module1 ==--
Public Function ReloadDbData() As Boolean
End Sub
Trin 3:Definer en type med samme navn
BEMÆRK:Igen, lad være med at definere en sub, funktion og skriv alle med det samme navn i et enkelt projekt.
'--== Module3 ==--
Private Type ReLoadDBData
Dummy As Variant
End Type
'--== Module2 ==--
Public Sub ReLoadDBData()
End Sub
'--== Module1 ==--
Public Function ReLoadDBData() As Boolean
End Sub
Trin 4:Definer en enum med samme navn
BEMÆRK:Please, please, please, for kærligheden til alt det hellige...
'--== 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
Trin 5:Definer en variabel med samme navn
BEMÆRK:Gør vi faktisk stadig dette?
'--== 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
Trin 6:Definer en konstant med samme navn
BEMÆRK:Åh, kom nu. Seriøst?
'--== 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
Trin 7:Definer en klasseegenskab med samme navn
BEMÆRK:Det her bliver dumt.
'--== 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
Enum Items?!?!
For dette tredje punkt er det vigtigt at skelne mellem en Enum-type og et Enum-element .
Enum EnumTypeName ' <-- Enum type
EnumItemAlice ' <-- Enum item
EnumItemBob ' <-- Enum item
End Enum
Vi har allerede vist ovenfor, at Enum-typer behandles på samme måde som andre former for erklæringer, såsom subs, funktioner, konstanter og variabler. Hver gang erklæringslinjen for en identifikator med det navn ændres, vil alle andre identifikatorer i projektet med samme navn blive opdateret, så de passer til den seneste ændring.
Nævn emner er specielle ved, at de er den eneste type identifikator, hvis store og små bogstaver kan ændres, når som helst en hvilken som helst linje med kode der indeholder enum-elementets navn, ændres.
Trin 1. Definer og udfyld enum
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemAlice
EnumItemBob
End Enum
Trin 2. Se Enum-elementerne i koden
'--== Module8 ==--
Sub TestEnum()
Debug.Print EnumItemALICE, EnumItemBOB
End Sub
Resultat:Enum-typeerklæringen ændres for at matche almindelig kodelinje
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemALICE
EnumItemBOB
End Enum
Del 2:Hvordan kom vi hertil?
Jeg har aldrig talt med nogen på det interne VBA-udviklingsteam. Jeg har aldrig set nogen officiel dokumentation om, hvorfor VBA IDE fungerer, som den gør. Så det, jeg er ved at skrive, er ren formodning, men jeg synes, det giver mening.
I lang tid undrede jeg mig over, hvorfor i alverden VBA IDE ville have denne adfærd. Det er jo klart med vilje. Den nemmeste ting for IDE at gøre ville være ... ingenting. Hvis brugeren erklærer en variabel med store bogstaver, så lad den stå med store bogstaver. Hvis brugeren derefter refererer til denne variabel med små bogstaver et par linjer senere, skal du lade denne reference stå med små bogstaver og den originale erklæring med store bogstaver.
Dette ville være en helt acceptabel implementering af VBA-sproget. Sproget i sig selv er trods alt ufølsomt for store og små bogstaver. Så hvorfor gøre alt det besværet for automatisk at ændre identifikatorens hus?
Ironisk nok tror jeg, at motivationen var at undgå forvirring. (Gynge og en frøken, hvis du spørger mig.) Jeg håner denne forklaring, men den giver mening.
Kontrast med store og små bogstaver
Lad os først tale om programmører, der kommer fra et sprog, der skelner mellem store og små bogstaver. En almindelig konvention i sprogfølsomme sprog, såsom C#, er at navngive klasseobjekter med store bogstaver og at navngive forekomster af disse objekter med samme navn som klassen, men med små bogstaver foran.
Denne konvention vil ikke fungere i VBA, fordi to identifikatorer, der kun adskiller sig med hensyn til bogstaver, betragtes som ækvivalente. Faktisk vil Office VBA IDE ikke give dig mulighed for samtidigt at erklære en funktion med én type hus og en lokal variabel med en anden form for hus (vi dækkede dette udtømmende ovenfor). Dette forhindrer udvikleren i at antage, at der er en semantisk forskel mellem to identifikatorer med de samme bogstaver, men forskellige store og små bogstaver.
Får forkert kode til at se forkert ud
Den mere sandsynlige forklaring i mit sind er, at denne "funktion" eksisterer for at få ækvivalente identifikatorer til at se identiske ud. Tænk over det; uden denne funktion ville det være let for stavefejl at blive til runtime-fejl. Tror du mig ikke? Overvej dette:
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
Hvis du kigger hurtigt på ovenstående kode, ser det ret ligetil ud. Det er en klasse med et .MyAccountName ejendom. Medlemsvariablen for egenskaben initialiseres til en konstant værdi, når objektet oprettes. Ved indstilling af kontonavnet i kode, opdateres medlemsvariablen igen. Når egenskabsværdien hentes, returnerer koden blot indholdet af medlemsvariablen.
Det er i hvert fald det, den skal gøre. Hvis jeg kopierer ovenstående kode og indsætter den i et VBA IDE-vindue, bliver identifikatorernes indfatning konsistent, og runtime-fejlene viser sig pludselig:
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
Implementering:Er dette virkelig den bedste tilgang?
Umm, nej. Misforstå mig ikke. Jeg kan faktisk rigtig godt lide ideen om automatisk at ændre brugen af store bogstaver i identifikatorer for at bevare konsistensen. Min eneste virkelige klage er, at ændringen er foretaget til hver identifikator med det navn i hele projektet. Meget bedre ville være kun at ændre brugen af store bogstaver for de identifikatorer, der refererer til den samme "ting" (uanset om denne "ting" er en funktion, sub, egenskab, variabel osv.).
Så hvorfor virker det ikke på denne måde? Jeg forventer, at VBA IDE-udviklerne er enige i mit perspektiv på, hvordan det skal fungere. Men der er en meget god grund hvorfor IDE ikke fungerer på den måde. I et ord, præstation.
Desværre er der kun én pålidelig måde at finde ud af, hvilke identifikatorer med det samme navn, der rent faktisk refererer til det samme:parse hver linje kode. Det er sloooowwwwww. Dette er mere end en simpel hypotese fra min side. Rubberduck VBA-projektet gør faktisk præcis dette; den analyserer hver linje kode i projektet, så den kan lave automatiseret kodeanalyse og en masse andre fede ting.
Projektet er ganske vist tungt. Det fungerer sikkert godt til Excel-projekter. Desværre har jeg aldrig været tålmodig nok til at bruge det i nogen af mine Access-projekter. Rubberduck VBA er et teknisk imponerende projekt, men det er også en advarselsfortælling. At respektere omfanget, når man ændrer store bogstaver for identifikatorer, ville være rart at have, men ikke på bekostning af VBA IDE's nuværende lynhurtige ydeevne.
Sidste tanker
Jeg forstår motivationen for denne funktion. Jeg tror endda, jeg forstår, hvorfor det er implementeret, som det er. Men det er for mig den mest vanvittige særegenhed ved VBA.
Hvis jeg kunne komme med en enkelt anbefaling til Office VBA-udviklingsteamet, ville det være at tilbyde en indstilling i IDE for at deaktivere automatiske sagsændringer. Den aktuelle adfærd kunne forblive aktiveret som standard. Men for superbrugere, der forsøger at integrere med versionskontrolsystemer, kan adfærden blive fuldstændig deaktiveret for at forhindre generende "kodeændringer" i at forurene revisionshistorikken.