I min artikel i går introducerede jeg konceptet "Dependency Train". Det er, hvad der sker, når du importerer en funktion fra dit kodebibliotek, men du ender med at skulle importere flere ekstra moduler bare for at tilfredsstille alle afhængighederne. Du står tilbage med et helt "tog" af kodemoduler, når alt hvad du havde brug for var et enkelt "sæde" (funktion).
Du ender i denne situation, når dine moduler er tæt koblet sammen. Så hvad kan du gøre ved det? Der er et par måder at håndtere denne situation på.
Krænk "gentag ikke dig selv"
En måde at bevare løs kobling - hvilket resulterer i flere "standalone" moduler - er at oprette private kopier af funktionerne fra kildemodulet i det kaldende modul. Dette eliminerer afhængigheden mellem de to moduler, men det resulterer i gentagen kode.
Konceptet er enkelt. Du kopierer funktionen fra dens primære kodemodul. Du indsætter det så i opkaldsmodulet, men markerer det som privat for at undgå tvetydighed.
Dette giver mening i følgende situationer:
- Enkle metoder uden kompleks logik.
- Procedurer, der sandsynligvis ikke ændres.
- Når rutinen er en del af et meget større modul, og du ikke har brug for nogen af de andre funktioner i modulet. Kopiering af den ene funktion undgår oppustethed.
Denne tilgang har følgende ulemper:
- Hvis der er en fejl i den kopierede funktion, bliver du nødt til at rette den mange steder.
- Du har kodeduplikering, hvis du alligevel ender med at importere kildemodulet.
Vælg dine kampe
Jeg plejede at gå meget langt for at holde alle mine standardkodebiblioteksmoduler fuldstændigt selvstændige. Problemet var, at det resulterede i en masse kodeduplikering. Årsagen er, at de fleste af de funktioner, jeg kopierede til andre moduler til privat brug, kom fra moduler, som jeg alligevel importerede til min applikation.
Et godt eksempel på dette var mine StringFunctions modul. Det modul har flere simple metoder, der stort set eksisterer for at gøre min kode mere læsbar. For eksempel har jeg en Conc()
funktion, som jeg inkluderede som en privat funktion i mere end halvdelen af mine kodebiblioteksmoduler.
Med tiden indså jeg, at jeg inkluderede disse StringFunctions modul i alle mine projekter. Jeg introducerede aldrig en ny afhængighed, da jeg kaldte en funktion fra det modul. Jeg spildte tid og introducerede duplikatkode til ringe eller ingen fordel.
Der var et par kodemoduler, som jeg med sikkerhed kunne antage ville være i hver applikation. Det var de moduler med funktioner, jeg brugte oftest. Hvilket betød, at mange af disse afhængigheder i det væsentlige kunne ignoreres.
Jeg vedligeholder nu et "Standardbibliotek" af kodemoduler, som jeg importerer til hvert nyt projekt helt i begyndelsen. Jeg ringer frit til funktioner fra disse moduler nu sikker i vidende om, at jeg ikke vil introducere nye afhængigheder.
Brug et unikt kommentartoken
Et af modulerne i mit "Standardbibliotek" er et klassemodul (clsApp ), der inkluderer egenskaber og metoder på applikationsniveau, såsom det aktuelle brugernavn og titellinjeteksten. Jeg eksponerer også andre klassemoduler fra clsApp , såsom clsStatus og clsRegistry , som giver mere læsbar adgang til henholdsvis Access-statuslinjen og Windows-registreringsdatabasen.
Jeg har dog ikke brug for adgang til statuslinjen eller Windows-registreringsdatabasen i hvert projekt. Så for at undgå at skabe en afhængighed af clsStatus eller clsRegistry klasser, ville jeg kommentere koden, der refererer til disse klasser, ved hjælp af et unikt "kommentartoken."
Dette er nemmest at demonstrere med et eksempel:
' Notes
' - Find and replace '$$ with blank to enable Status property (requires clsStatus)
' - Find and replace '&& with blank to enable Reg property (requires clsRegistry)
'$$Private m_objStatus As clsStatus
'&&Private m_objReg As clsRegistry
'$$Public Property Get Status() As clsStatus
'$$ Set Status = m_objStatus
'$$End Property
'&&Public Property Get Reg() As clsRegistry
'&& Set Reg = m_objReg
'&&End Property
Private Sub Class_Initialize()
'$$ Set m_objStatus = New clsStatus
'&& Set m_objReg = New clsRegistry
End Sub
Hvis jeg ville aktivere Status
egenskab for ovenstående klasse, kunne jeg udføre et globalt find og erstatte på '$$
.
Dette fungerede fint i et stykke tid, men det føltes altid kludgey for mig. Sandsynligvis fordi det var det. Den anden ting at bemærke er, at kommentartokenserne skal være globalt unikke gennem hele mit kodebibliotek. Dette ville have været et vedligeholdelsesmareridt, hvis jeg holdt fast ved denne tilgang længe.
Brug betinget kompilering
En meget renere tilgang er at drage fordel af betinget kompilering. Det er linjerne i VBA, der begynder med et pund/hashtag-tegn ("#"). Linjer, der begynder med det pågældende tegn, er underlagt "forbehandling."
Hvad er forbehandling? Det er et trin, som programmeringssprog tager før kompilering. Så før der foretages kontrol af kompileringstid, evalueres forbehandlingslinjerne. Dette giver os mulighed for at placere kode, som ellers ikke ville kunne kompileres i vores projekter.
Hvordan kan vi drage fordel af dette med vores kodebiblioteker? Igen er dette nemmest at demonstrere med et eksempel:
' Notes
' - Replace the '$$ and '&& kludges with conditional compilation
#Const EnableStatusProperty = True 'If True, requires import of clsStatus class
#Const EnableRegProperty = False 'If True, requires import of clsRegistry class
#If EnableStatusProperty Then
Private m_objStatus As clsStatus
#End If
#If EnableRegProperty Then
Private m_objReg As clsRegistry
#End If
#If EnableStatusProperty Then
Public Property Get Status() As clsStatus
Set Status = m_objStatus
End Property
#End If
#If EnableRegProperty Then
Public Property Get Reg() As clsRegistry
Set Reg = m_objReg
End Property
#End If
Private Sub Class_Initialize()
#If EnableStatusProperty Then
Set m_objStatus = New clsStatus
#End If
#If EnableRegProperty Then
Set m_objReg = New clsRegistry
#End If
End Sub
Det bedste fra begge verdener
Som du kan se, er dette en meget ren måde at undgå problemet med "Dependency Train".
Det giver os mulighed for at oprette valgfrie afhængigheder . Hvert stykke kode, der er afhængig af et andet kodebiblioteksmodul, bliver pakket ind i en betinget kompilering #If ... Then-sætning. Den eller de betingede kompileringskonstanter er alle opført øverst i kodemodulet.
Når vi nu genimporterer en opdateret version af vores kodebiblioteksmodul, skal vi simpelthen gå igennem og indstille de betingede kompileringsflag til meget, hvad der var der før. Hvis vi ikke kan huske, hvordan flagene blev sat, burde vi være i stand til at blive ved med at kompilere og justere flag, indtil projektet er fuldt kompileret.
Og hvis vi bruger versionskontrol, behøver vi ikke bekymre os om at glemme, hvad der var der før.