Jako zagorzały zwolennik kontroli wersji w Microsoft Access, muszę opowiedzieć o moim największym problemie związanym ze środowiskiem programistycznym VBA:automatycznym „przemienianiu” identyfikatorów. Pomyśl o tym jako o rozwinięciu mojej odpowiedzi na pytanie dotyczące tej „funkcji” na stackoverflow.
Zamierzam podejść do tego artykułu w dwóch częściach. W części 1 zdefiniuję zachowanie środowiska programistycznego. W części 2 omówię moją teorię, dlaczego to działa w ten sposób.
Część 1:Definiowanie zachowania
Jeśli spędziłeś trochę czasu na pisaniu kodu w VBA, jestem pewien, że zauważyłeś tę „funkcję”. Podczas wpisywania identyfikatorów — zmiennych, nazw funkcji, wyliczeń itp. — możesz zauważyć, że IDE automatycznie zmienia wielkość liter tych identyfikatorów. Na przykład możesz wpisać nazwę zmiennej wszystkimi małymi literami, ale gdy tylko przejdziesz do nowego wiersza, pierwsza litera zmiennej nagle zmieni się na wielką.
Gdy widzisz to po raz pierwszy, może to być wstrząsające. Gdy kontynuujesz programowanie, IDE kontynuuje zmianę przypadku pozornie losowo. Ale jeśli spędzisz wystarczająco dużo czasu w IDE, w końcu wzór się ujawni.
Aby uchronić cię przed koniecznością spędzenia ponad dziesięciu lat swojego życia w oczekiwaniu na ujawnienie się wzoru, opiszę teraz wzór tak, jak go zrozumiałem. O ile mi wiadomo, firma Microsoft nigdy oficjalnie nie udokumentowała żadnego z takich zachowań.
- Wszystkie automatyczne zmiany wielkości liter są globalne dla projektu VBA.
- Za każdym razem, gdy zmieniany jest wiersz deklaracji dowolnego z poniższych typów identyfikatorów, zmieniany jest również każdy inny identyfikator o tej samej nazwie:
- Nazwa podrzędna
- Nazwa funkcji
- Wpisz nazwę
- Nazwa wyliczenia
- Nazwa zmiennej
- Nazwa stała
- Nazwa nieruchomości
- Za każdym razem, gdy nazwa elementu wyliczenia jest zmieniana gdziekolwiek w kodzie, wielkość liter nazwy elementu wyliczenia jest aktualizowana, aby pasowała wszędzie.
Porozmawiajmy teraz o każdym z tych zachowań bardziej szczegółowo.
Zmiany globalne
Jak napisałem powyżej, zmiany wielkości identyfikatorów są globalne dla projektu VBA. Innymi słowy, VBA IDE całkowicie ignoruje zakres podczas zmiany wielkości liter w identyfikatorach.
Załóżmy na przykład, że masz prywatną funkcję o nazwie KontoIsActive w standardowym module. Teraz wyobraź sobie moduł klasy w innym miejscu tego samego projektu. Moduł klasy posiada prywatną procedurę Property Get. Wewnątrz tej procedury pobierania właściwości znajduje się zmienna lokalna o nazwie accountIsActive . Jak tylko wpiszesz wiersz Dim accountIsActive As Boolean
do środowiska IDE VBA i przejdź do nowej linii, funkcja Konto jest aktywne który zdefiniowaliśmy osobno w jego własnym standardowym module ma swoją linię deklaracji zmienioną na Private Function accountIsActive()
aby dopasować lokalną zmienną wewnątrz tego modułu klasy.
To kęs, więc pozwól, że zademonstruję to lepiej w kodzie.
Krok 1:Zdefiniuj funkcję KontoIsAktywne
'--== Module1 ==--
Private Function AccountIsActive() As Boolean
End Function
Krok 2:zadeklaruj, że zmienna lokalna konto jest aktywna w innym zakresie
'--== Class1 ==--
Private Sub Foo()
Dim accountIsACTIVE As Boolean
End Sub
Krok 3:IDE VBA... co zrobiłeś?!?!
'--== Module1 ==--
Private Function accountIsACTIVE() As Boolean
End Function
Zasady niedyskryminacji VBA Case-Obliteration
Nie zadowalając się po prostu ignorowaniem zakresu, VBA ignoruje również różnice między rodzajami identyfikatorów w swoim dążeniu do narzucenia spójności wielkości liter. Innymi słowy, za każdym razem, gdy deklarujesz nową funkcję, podprogram lub zmienną, która używa istniejącej nazwy identyfikatora, wszystkie inne wystąpienia tego identyfikatora mają zmienioną wielkość liter w celu dopasowania.
W każdym z poniższych przykładów jedyną rzeczą, którą zmieniam, jest pierwszy wymieniony moduł. IDE VBA jest odpowiedzialne za wszystkie inne zmiany we wcześniej zdefiniowanych modułach.
Krok 1:Zdefiniuj funkcję
'--== Module1 ==--
Public Function ReloadDBData() As Boolean
End Function
Krok 2:Zdefiniuj sub o tej samej nazwie
UWAGA:Jest to całkowicie ważne, o ile procedury są w różnych modułach. To powiedziawszy, tylko dlatego, że *możesz* coś zrobić, nie oznacza, że *powinieneś*. I *powinnaś* unikać takiej sytuacji, jeśli to w ogóle możliwe.
'--== Module2 ==--
Public Sub ReloadDbData()
End Sub
'--== Module1 ==--
Public Function ReloadDbData() As Boolean
End Sub
Krok 3:Zdefiniuj typ o tej samej nazwie
UWAGA:Ponownie, proszę nie definiować podrzędnej, funkcji i wpisywać wszystkich o tej samej nazwie w jednym projekcie.
'--== Module3 ==--
Private Type ReLoadDBData
Dummy As Variant
End Type
'--== Module2 ==--
Public Sub ReLoadDBData()
End Sub
'--== Module1 ==--
Public Function ReLoadDBData() As Boolean
End Sub
Krok 4:Zdefiniuj wyliczenie o tej samej nazwie
UWAGA:Proszę, proszę, proszę, z miłości do wszystkiego co święte...
'--== 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
Krok 5:Zdefiniuj zmienną o tej samej nazwie
UWAGA:nadal to robimy?
'--== 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
Krok 6:Zdefiniuj stałą o tej samej nazwie
UWAGA:Och, daj spokój. Poważnie?
'--== 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
Krok 7:Zdefiniuj właściwość klasy o tej samej nazwie
UWAGA:to się robi głupie.
'--== 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
Wyliczenie przedmiotów?!?!
W tym trzecim punkcie ważne jest, aby rozróżnić typ wyliczenia i Enumeracja .
Enum EnumTypeName ' <-- Enum type
EnumItemAlice ' <-- Enum item
EnumItemBob ' <-- Enum item
End Enum
Pokazaliśmy już powyżej, że typy Enum są traktowane tak samo, jak inne rodzaje deklaracji, takie jak subs, funkcje, stałe i zmienne. Za każdym razem, gdy zmienia się wiersz deklaracji dla identyfikatora o tej nazwie, każdy inny identyfikator w projekcie o tej samej nazwie ma zaktualizowaną wielkość liter, aby pasował do ostatniej zmiany.
Wyliczenie przedmiotów są wyjątkowe, ponieważ są jedynym rodzajem identyfikatora, którego wielkość liter może zostać zmieniona za każdym razem, gdy dowolna linia kodu który zawiera nazwę elementu wyliczenia jest zmieniana.
Krok 1. Zdefiniuj i wypełnij Enum
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemAlice
EnumItemBob
End Enum
Krok 2. Zapoznaj się z pozycjami Wyliczenie w kodzie
'--== Module8 ==--
Sub TestEnum()
Debug.Print EnumItemALICE, EnumItemBOB
End Sub
Wynik:zmiana deklaracji typu wyliczenia w celu dopasowania do zwykłego wiersza kodu
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemALICE
EnumItemBOB
End Enum
Część 2:Jak się tu dostaliśmy?
Nigdy nie rozmawiałem z nikim z wewnętrznego zespołu programistów VBA. Nigdy nie widziałem żadnej oficjalnej dokumentacji wyjaśniającej, dlaczego IDE VBA działa tak, jak działa. Więc to, co zamierzam napisać, jest czystym przypuszczeniem, ale myślę, że ma to jakiś sens.
Przez długi czas zastanawiałem się, dlaczego na świecie IDE VBA miałoby takie zachowanie. W końcu jest to wyraźnie zamierzone. Najłatwiejszą rzeczą do zrobienia dla IDE byłoby... nic. Jeśli użytkownik deklaruje zmienną wielkimi literami, niech będzie pisana wielkimi literami. Jeśli użytkownik odwołuje się do tej zmiennej kilka wierszy później małymi literami, zostaw to odwołanie małymi literami, a oryginalną deklarację wielkimi literami.
Byłaby to całkowicie akceptowalna implementacja języka VBA. W końcu sam język nie rozróżnia wielkości liter. Po co więc zawracać sobie głowę automatyczną zmianą wielkości liter identyfikatora?
Jak na ironię, uważam, że motywacją było uniknięcie zamieszania. (Swing i pudło, jeśli mnie pytasz.) Kpię z tego wyjaśnienia, ale ma to jakiś sens.
Kontrast z językami, w których rozróżniana jest wielkość liter
Najpierw porozmawiajmy o programistach wywodzących się z języka rozróżniającego wielkość liter. Powszechną konwencją w językach rozróżniających wielkość liter, takich jak C#, jest nazywanie obiektów klas wielkimi literami i nazywanie wystąpień tych obiektów taką samą nazwą jak klasa, ale z wiodącą małą literą.
Ta konwencja nie zadziała w VBA, ponieważ dwa identyfikatory, które różnią się tylko wielkością liter, są uważane za równoważne. W rzeczywistości Office VBA IDE nie pozwoli Ci jednocześnie zadeklarować funkcji z jednym typem wielkości liter i lokalnej zmiennej z innym rodzajem wielkości liter (omówiliśmy to dokładnie powyżej). Uniemożliwia to programiście założenie, że istnieje różnica semantyczna między dwoma identyfikatorami z tymi samymi literami, ale różnymi wielkościami liter.
Niewłaściwy kod wygląda źle
Bardziej prawdopodobnym wyjaśnieniem w moim umyśle jest to, że ta „funkcja” istnieje po to, aby równoważne identyfikatory wyglądały identycznie. Pomyśl o tym; bez tej funkcji literówki mogłyby łatwo przekształcić się w błędy w czasie wykonywania. Nie wierzysz mi? Rozważ to:
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
Jeśli spojrzysz szybko na powyższy kod, wygląda to całkiem prosto. To klasa z rozszerzeniem .MyAccountName własność. Zmienna członkowska dla właściwości jest inicjowana do wartości stałej podczas tworzenia obiektu. Podczas ustawiania nazwy konta w kodzie, zmienna składowa jest ponownie aktualizowana. Podczas pobierania wartości właściwości kod jedynie zwraca zawartość zmiennej składowej.
Przynajmniej tak ma robić. Jeśli skopiuję powyższy kod i wkleję go do okna VBA IDE, wielkość liter identyfikatorów stanie się spójna i nagle pojawią się błędy uruchomieniowe:
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
Wdrożenie:czy to naprawdę najlepsze podejście?
Umm nie. Nie zrozum mnie źle. Właściwie bardzo podoba mi się pomysł automatycznej zmiany wielkości liter w identyfikatorach, aby zachować spójność. Moim jedynym prawdziwym problemem jest to, że zmiana dotyczy każdego identyfikatora o tej nazwie w całym projekcie. Znacznie lepiej byłoby zmienić wielkość liter tylko w tych identyfikatorach, które odnoszą się do tej samej „rzeczy” (niezależnie od tego, czy „rzecz” jest funkcją, podrzędną, właściwością, zmienną itp.).
Więc dlaczego to nie działa w ten sposób? Oczekuję, że programiści VBA IDE zgadzają się z moim zdaniem, jak to powinno działać. Ale jest bardzo dobry powód dlaczego IDE nie działa w ten sposób. Jednym słowem wydajność.
Niestety, istnieje tylko jeden niezawodny sposób na odkrycie, które identyfikatory o tej samej nazwie faktycznie odnoszą się do tej samej rzeczy:przeanalizuj każdy wiersz kodu. To jest sloooowwww. To więcej niż prosta hipoteza z mojej strony. Projekt Rubberduck VBA faktycznie robi dokładnie to; analizuje każdy wiersz kodu w projekcie, dzięki czemu może przeprowadzić zautomatyzowaną analizę kodu i kilka innych fajnych rzeczy.
Projekt jest wprawdzie ciężki. Prawdopodobnie działa świetnie w projektach Excel. Niestety, nigdy nie byłem na tyle cierpliwy, aby używać go w żadnym z moich projektów Access. Rubberduck VBA to imponujący technicznie projekt, ale to także przestroga. Respektowanie zakresu przy zmianie wielkości liter w identyfikatorach byłoby dobrze mieć, ale nie kosztem obecnej, niesamowicie szybkiej wydajności VBA IDE.
Końcowe myśli
Rozumiem motywację tej funkcji. Myślę, że nawet rozumiem, dlaczego jest to tak zaimplementowane. Ale to dla mnie najbardziej irytujące dziwactwo VBA.
Gdybym mógł przekazać jedną rekomendację zespołowi programistów Office VBA, byłoby to zaoferowanie ustawienia w IDE, aby wyłączyć automatyczne zmiany wielkości liter. Bieżące zachowanie może pozostać domyślnie włączone. Jednak w przypadku zaawansowanych użytkowników, którzy próbują zintegrować się z systemami kontroli wersji, zachowanie to może zostać całkowicie wyłączone, aby zapobiec zanieczyszczaniu historii wersji przez uciążliwe „zmiany kodu”.