I min artikel igår introducerade jag konceptet "Dependency Train". Det är vad som händer när du importerar en funktion från ditt kodbibliotek, men du måste importera flera ytterligare moduler bara för att tillfredsställa alla beroenden. Du sitter kvar med ett helt "tåg" av kodmoduler när allt du behövde var en enda "säte" (funktion).
Du hamnar i den här situationen när dina moduler är tätt kopplade. Så vad kan du göra åt det? Det finns några sätt att hantera den här situationen.
Överträda "upprepa inte dig själv"
Ett sätt att bevara lös koppling - vilket resulterar i fler "fristående" moduler - är att skapa privata kopior av funktionerna från källmodulen i den anropande modulen. Detta eliminerar beroendet mellan de två modulerna, men det resulterar i repetitiv kod.
Konceptet är enkelt. Du kopierar funktionen från dess primära kodmodul. Du klistrar sedan in den i anropsmodulen men markerar den som privat för att undvika oklarheter.
Detta är vettigt i följande situationer:
- Enkla metoder utan komplex logik.
- Procedurer som sannolikt inte kommer att ändras.
- När rutinen är en del av en mycket större modul och du inte behöver någon av de andra funktionerna i modulen. Genom att kopiera en funktion undviks svullnad.
Detta tillvägagångssätt har följande nackdelar:
- Om det finns en bugg i den kopierade funktionen måste du fixa den på många ställen.
- Du har kodduplicering om du ändå importerar källmodulen.
Välj dina strider
Jag brukade gå långt för att hålla alla mina standardkodbiblioteksmoduler helt fristående. Problemet var att det resulterade i mycket kodduplicering. Anledningen är att de flesta funktionerna jag kopierade till andra moduler för privat bruk kom från moduler som jag ändå importerade till min applikation.
Ett utmärkt exempel på detta var mina StringFunctions modul. Den modulen har flera enkla metoder som finns till stor del för att göra min kod mer läsbar. Till exempel har jag en Conc()
funktion som jag inkluderade som en privat funktion i mer än hälften av mina kodbiblioteksmoduler.
Med tiden insåg jag att jag inkluderade dessa StringFunctions modul i alla mina projekt. Jag introducerade aldrig ett nytt beroende när jag anropade en funktion från den modulen. Jag slösade bort tid och introducerade dubblettkod för liten eller ingen nytta.
Det fanns några kodmoduler som jag säkert kunde anta skulle finnas i varje applikation. Det var de moduler med funktioner som jag använde oftast. Vilket innebar att många av dessa beroenden i princip kunde ignoreras.
Jag har nu ett "Standardbibliotek" med kodmoduler som jag importerar till varje nytt projekt i början. Jag anropar fritt funktioner från dessa moduler nu säker i vetskapen om att jag inte kommer att införa nya beroenden.
Använd en unik kommentarstoken
En av modulerna i mitt "Standardbibliotek" är en klassmodul (clsApp ) som inkluderar egenskaper och metoder på programnivå, som det aktuella användarnamnet och titelfältets text. Jag exponerar även andra klassmoduler från clsApp , till exempel clsStatus och clsRegistry , som ger mer läsbar åtkomst till Access-statusfältet respektive Windows-registret.
Jag behöver dock inte tillgång till statusfältet eller Windows-registret i varje projekt. Så för att undvika att skapa ett beroende av clsStatus eller clsRegistry klasser, skulle jag kommentera koden som hänvisar till dessa klasser med en unik "kommentarstoken."
Detta är lättast att demonstrera med ett exempel:
' 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
Om jag ville aktivera Status
egenskapen för ovanstående klass, kunde jag utföra en global sökning och ersättning på '$$
.
Det här fungerade bra ett tag, men det kändes alltid tråkigt för mig. Förmodligen för att det var det. En annan sak att notera är att kommentarssymbolerna skulle behöva vara globalt unika i hela mitt kodbibliotek. Detta skulle ha varit en underhållsmardröm om jag höll fast vid det här tillvägagångssättet länge.
Använd villkorlig kompilering
Ett mycket renare tillvägagångssätt är att dra fördel av villkorlig kompilering. Det är raderna i VBA som börjar med ett pund/hashtag-tecken ("#"). Rader som börjar med det tecknet är föremål för "förbearbetning."
Vad är förbearbetning? Det är ett steg som programmeringsspråk tar före kompilering. Så, innan någon kompileringstidskontroll inträffar, utvärderas förbearbetningslinjerna. Detta gör att vi kan placera kod som annars inte skulle kunna kompileras i våra projekt.
Hur kan vi dra nytta av detta med våra kodbibliotek? Återigen, detta är enklast att demonstrera med ett exempel:
' 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 bästa av två världar
Som du kan se är detta ett mycket rent sätt att undvika "Dependency Train"-problemet.
Det låter oss skapa valfria beroenden . Varje bit kod som är beroende av en annan kodbiblioteksmodul lindas in i en villkorlig kompilering #If ... Then-sats. De villkorliga kompileringskonstanterna är alla listade överst i kodmodulen.
Nu när vi återimporterar en uppdaterad version av vår kodbiblioteksmodul, måste vi helt enkelt gå igenom och ställa in de villkorliga kompileringsflaggorna till mycket vad som fanns där tidigare. Om vi inte kommer ihåg hur flaggorna sattes borde vi kunna fortsätta att kompilera och justera flaggor tills projektet är helt kompilerat.
Och om vi använder versionskontroll behöver vi inte oroa oss för att glömma vad som fanns där tidigare.