In qualità di forte sostenitore del controllo di versione in Microsoft Access, devo parlare della mia più grande lamentela con l'ambiente di sviluppo VBA:il "recasing" automatico degli identificatori. Pensa a questo come un'espansione della mia risposta a una domanda su questa "funzione" su StackOverflow.
Mi avvicinerò a questo articolo in due parti. Nella parte 1, definirò il comportamento dell'ambiente di sviluppo. Nella parte 2, discuterò la mia teoria sul perché funziona in questo modo.
Parte 1:definizione del comportamento
Se hai trascorso un po 'di tempo a scrivere codice in VBA, sono sicuro che hai notato questa "funzione". Mentre si digitano gli identificatori, variabili, nomi di funzioni, enumerazioni, ecc., è possibile notare che l'IDE cambia automaticamente l'involucro di questi identificatori. Ad esempio, puoi digitare il nome di una variabile in tutte le lettere minuscole, ma non appena ti sposti su una nuova riga, la prima lettera della variabile diventerà improvvisamente maiuscola.
La prima volta che lo vedi può essere stridente. Mentre continui a programmare, l'IDE continua a cambiare il caso su di te apparentemente a caso. Ma, se trascorri abbastanza tempo nell'IDE, alla fine il modello si rivela.
Per salvarti dal dover trascorrere più di dieci anni della tua vita in attesa che lo schema ti si riveli, ora descriverò lo schema come sono arrivato a comprenderlo. Per quanto ne so, Microsoft non ha mai documentato ufficialmente nessuno di questi comportamenti.
- Tutte le modifiche automatiche dei casi sono globali per il progetto VBA.
- Ogni volta che la riga di dichiarazione di uno qualsiasi dei seguenti tipi di identificatori viene modificata, anche ogni altro identificatore con lo stesso nome cambia il suo case:
- Nome secondario
- Nome funzione
- Digita nome
- Nome enum
- Nome variabile
- Costante nome
- Nome proprietà
- Ogni volta che il nome di un elemento enum viene modificato in un punto qualsiasi del codice, l'involucro del nome di un elemento enum viene aggiornato in modo che corrisponda ovunque.
Parliamo ora di ciascuno di questi comportamenti in modo un po' più dettagliato.
Cambiamenti globali
Come ho scritto sopra, le modifiche al caso dell'identificatore sono globali per un progetto VBA. In altre parole, l'IDE VBA ignora completamente l'ambito quando si cambia il caso degli identificatori.
Ad esempio, supponiamo che tu abbia una funzione privata denominata AccountIsActive in un modulo standard. Ora, immagina un modulo di classe altrove nello stesso progetto. Il modulo di classe ha una procedura Property Get privata. All'interno della procedura Property Get è presente una variabile locale denominata accountIsActive . Non appena si digita la riga Dim accountIsActive As Boolean
nell'IDE VBA e passare a una nuova riga, la funzione Account è attivo che abbiamo definito separatamente nel proprio modulo standard ha la sua riga di dichiarazione modificata in Private Function accountIsActive()
per abbinare la variabile locale all'interno di questo modulo di classe.
È un boccone, quindi lascia che lo dimostri meglio nel codice.
Passaggio 1:Definisci la funzione AccountIsActive
'--== Module1 ==--
Private Function AccountIsActive() As Boolean
End Function
Passaggio 2:dichiara la variabile locale accountIsActive in un ambito diverso
'--== Class1 ==--
Private Sub Foo()
Dim accountIsACTIVE As Boolean
End Sub
Fase 3:IDE VBA... cosa hai fatto?!?!
'--== Module1 ==--
Private Function accountIsACTIVE() As Boolean
End Function
Politica di non discriminazione di VBA Case-Obliteration
Non contento di ignorare semplicemente l'ambito, VBA ignora anche le differenze tra i tipi di identificatori nella sua ricerca per imporre la coerenza del case. In altre parole, ogni volta che si dichiara una nuova funzione, subroutine o variabile che utilizza un nome identificatore esistente, tutte le altre istanze di tale identificatore cambiano maiuscolo per corrispondere.
In ciascuno di questi esempi di seguito, l'unica cosa che sto modificando è il primo modulo elencato. L'IDE VBA è responsabile di tutte le altre modifiche ai moduli precedentemente definiti.
Passaggio 1:definisci una funzione
'--== Module1 ==--
Public Function ReloadDBData() As Boolean
End Function
Passaggio 2:definisci un sub con lo stesso nome
NOTA:Questo è perfettamente valido purché le procedure siano in moduli diversi. Detto questo, solo perché *puoi* fare qualcosa, non significa che *dovresti*. E tu *dovresti* evitare questa situazione se possibile.
'--== Module2 ==--
Public Sub ReloadDbData()
End Sub
'--== Module1 ==--
Public Function ReloadDbData() As Boolean
End Sub
Passaggio 3:definisci un tipo con lo stesso nome
NOTA:Anche in questo caso, non definire un sub, una funzione e digitarli tutti con lo stesso nome in un singolo progetto.
'--== Module3 ==--
Private Type ReLoadDBData
Dummy As Variant
End Type
'--== Module2 ==--
Public Sub ReLoadDBData()
End Sub
'--== Module1 ==--
Public Function ReLoadDBData() As Boolean
End Sub
Passaggio 4:definisci un enum con lo stesso nome
NOTA:Per favore, per favore, per favore, per amore di tutte le cose sante...
'--== 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
Passaggio 5:definisci una variabile con lo stesso nome
NOTA:lo stiamo ancora facendo?
'--== 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
Passaggio 6:definisci una costante con lo stesso nome
NOTA:Oh, andiamo. Sul serio?
'--== 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
Passaggio 7:definisci una proprietà di classe con lo stesso nome
NOTA:sta diventando sciocco.
'--== 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
Enumerare elementi?!?!
Per questo terzo punto, è importante distinguere tra un tipo Enum e un elemento Enum .
Enum EnumTypeName ' <-- Enum type
EnumItemAlice ' <-- Enum item
EnumItemBob ' <-- Enum item
End Enum
Abbiamo già mostrato in precedenza che i tipi Enum sono trattati allo stesso modo di altri tipi di dichiarazioni, come sub, funzioni, costanti e variabili. Ogni volta che viene modificata la riga di dichiarazione per un identificatore con quel nome, ogni altro identificatore nel progetto con lo stesso nome viene aggiornato per corrispondere all'ultima modifica.
Enum elementi sono speciali in quanto sono l'unico tipo di identificatore il cui involucro può essere modificato ogni volta che qualsiasi riga di codice che contiene il nome dell'elemento enum viene modificato.
Passaggio 1. Definisci e compila Enum
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemAlice
EnumItemBob
End Enum
Passaggio 2. Fare riferimento agli elementi Enum nel codice
'--== Module8 ==--
Sub TestEnum()
Debug.Print EnumItemALICE, EnumItemBOB
End Sub
Risultato:la dichiarazione del tipo Enum cambia per corrispondere alla normale riga di codice
'--== Module7 ==--
Public Enum EnumTypeName
EnumItemALICE
EnumItemBOB
End Enum
Parte 2:come siamo arrivati qui?
Non ho mai parlato con nessuno nel team di sviluppo VBA interno. Non ho mai visto alcuna documentazione ufficiale sul motivo per cui l'IDE VBA funziona in questo modo. Quindi, quello che sto per scrivere è pura congettura, ma penso che abbia un senso.
Per molto tempo mi sono chiesto perché nel mondo l'IDE VBA avrebbe questo comportamento. Dopotutto, è chiaramente intenzionale. La cosa più semplice da fare per l'IDE sarebbe... niente. Se l'utente dichiara una variabile in maiuscolo, lascia che sia in maiuscolo. Se l'utente fa riferimento a quella variabile in minuscolo poche righe dopo, lascia quel riferimento in minuscolo e la dichiarazione originale in maiuscolo.
Questa sarebbe un'implementazione perfettamente accettabile del linguaggio VBA. Dopotutto, la lingua stessa non fa distinzione tra maiuscole e minuscole. Quindi, perché prendersi tutti i problemi per cambiare automaticamente la maiuscola dell'identificatore?
Ironia della sorte, credo che la motivazione fosse quella di evitare confusione. (Swing e Miss, se me lo chiedi.) Mi fa beffe di questa spiegazione, ma ha un senso.
Contrasto con le lingue con distinzione tra maiuscole e minuscole
Per prima cosa, parliamo di programmatori provenienti da un linguaggio case sensitive. Una convenzione comune nei linguaggi con distinzione tra maiuscole e minuscole, come C#, consiste nel nominare gli oggetti di classe con lettere maiuscole e nel nominare le istanze di tali oggetti con lo stesso nome della classe, ma con una lettera minuscola all'inizio.
Quella convenzione non funzionerà in VBA, perché due identificatori che differiscono solo per le maiuscole sono considerati equivalenti. In effetti, l'IDE VBA di Office non ti consente di dichiarare contemporaneamente una funzione con un tipo di involucro e una variabile locale con un diverso tipo di involucro (ne abbiamo parlato in modo esaustivo sopra). Ciò impedisce allo sviluppatore di presumere che esista una differenza semantica tra due identificatori con le stesse lettere ma caratteri diversi.
Fare sembrare sbagliato il codice sbagliato
La spiegazione più probabile nella mia mente è che questa "funzione" esiste per far sembrare identici gli identificatori equivalenti. Pensaci; senza questa funzione, sarebbe facile che gli errori di battitura si trasformassero in errori di runtime. Non mi credi? Considera questo:
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 dai un'occhiata rapidamente al codice sopra, sembra piuttosto semplice. È una classe con un .MyAccountName proprietà. La variabile membro per la proprietà viene inizializzata su un valore costante quando viene creato l'oggetto. Quando si imposta il nome dell'account nel codice, la variabile membro viene nuovamente aggiornata. Quando si recupera il valore della proprietà, il codice restituisce semplicemente il contenuto della variabile membro.
Almeno, questo è quello che dovrebbe fare. Se copio il codice sopra e lo incollo in una finestra IDE VBA, l'involucro degli identificatori diventa coerente e i bug di runtime si mostrano improvvisamente:
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
Implementazione:è davvero l'approccio migliore?
Ehm, no. Non fraintendermi. In realtà mi piace molto l'idea di cambiare automaticamente la maiuscola degli identificatori per mantenere la coerenza. La mia unica vera lamentela è che la modifica viene apportata a ogni identificatore con quel nome nell'intero progetto. Molto meglio sarebbe cambiare la maiuscola solo di quegli identificatori che si riferiscono alla stessa "cosa" (se quella "cosa" è una funzione, sotto, proprietà, variabile, ecc.).
Allora perché non funziona in questo modo? Mi aspetto che gli sviluppatori dell'IDE VBA siano d'accordo con la mia prospettiva su come dovrebbe funzionare. Ma c'è un ottimo motivo perché l'IDE non funziona in questo modo. In una parola, performance.
Sfortunatamente, esiste un solo modo affidabile per scoprire quali identificatori con lo stesso nome si riferiscono effettivamente alla stessa cosa:analizzare ogni riga di codice. Questo è soooowwwww. Questa è più di una semplice ipotesi da parte mia. Il progetto Rubberduck VBA in realtà fa esattamente questo; analizza ogni riga di codice nel progetto in modo che possa eseguire analisi del codice automatizzate e un sacco di altre cose interessanti.
Il progetto è certamente pesante. Probabilmente funziona benissimo per i progetti Excel. Sfortunatamente, non sono mai stato abbastanza paziente da usarlo in nessuno dei miei progetti di Access. Rubberduck VBA è un progetto tecnicamente impressionante, ma è anche un avvertimento. Sarebbe bello rispettare l'ambito quando si cambiano le maiuscole per gli identificatori, ma non a scapito delle attuali prestazioni incredibilmente veloci di VBA IDE.
Pensieri finali
Capisco la motivazione di questa funzione. Penso di aver persino capito perché è implementato così com'è. Ma per me è la stranezza più esasperante di VBA.
Se potessi fare una singola raccomandazione al team di sviluppo di Office VBA, sarebbe offrire un'impostazione nell'IDE per disabilitare le modifiche automatiche dei casi. Il comportamento corrente potrebbe rimanere abilitato per impostazione predefinita. Tuttavia, per gli utenti esperti che stanno cercando di integrarsi con i sistemi di controllo della versione, il comportamento potrebbe essere completamente disabilitato per evitare che le fastidiose "modifiche al codice" inquinino la cronologia delle revisioni.