In meinem gestrigen Artikel habe ich das „Dependency Train“-Konzept vorgestellt. Das passiert, wenn Sie eine Funktion aus Ihrer Codebibliothek importieren, aber am Ende mehrere zusätzliche Module importieren müssen, nur um alle Abhängigkeiten zu erfüllen. Sie haben einen ganzen "Zug" von Codemodulen übrig, wenn Sie nur einen einzigen "Platz" (Funktion) brauchten.
Sie landen in dieser Situation, wenn Ihre Module eng gekoppelt sind. Was können Sie dagegen tun? Es gibt einige Möglichkeiten, mit dieser Situation umzugehen.
Verletze "nicht wiederholen"
Eine Möglichkeit, eine lose Kopplung beizubehalten – was zu mehr „eigenständigen“ Modulen führt – besteht darin, private Kopien der Funktionen aus dem Quellmodul im aufrufenden Modul zu erstellen. Dadurch wird die Abhängigkeit zwischen den beiden Modulen beseitigt, aber es führt zu sich wiederholendem Code.
Das Konzept ist einfach. Sie kopieren die Funktion aus ihrem primären Codemodul. Sie fügen es dann in das aufrufende Modul ein, markieren es aber als privat, um Mehrdeutigkeiten zu vermeiden.
Dies ist in folgenden Situationen sinnvoll:
- Einfache Methoden ohne komplexe Logik.
- Verfahren, die sich wahrscheinlich nicht ändern werden.
- Wenn die Routine Teil eines viel größeren Moduls ist und Sie keine der anderen Funktionen im Modul benötigen. Das Kopieren der einen Funktion vermeidet Aufblähen.
Dieser Ansatz hat die folgenden Nachteile:
- Wenn es gibt B. ein Fehler in der kopierten Funktion, müssen Sie ihn an vielen Stellen beheben.
- Sie haben eine Code-Duplizierung, wenn Sie am Ende trotzdem das Quellmodul importieren.
Wählen Sie Ihre Schlachten aus
Früher habe ich große Anstrengungen unternommen, um alle meine Standard-Codebibliotheksmodule vollständig eigenständig zu halten. Das Problem war, dass es zu einer Menge Codeduplizierung führte. Der Grund dafür ist, dass die meisten Funktionen, die ich für den privaten Gebrauch in andere Module kopiert habe, aus Modulen stammen, die ich sowieso in meine Anwendung importiert habe.
Ein Paradebeispiel dafür waren meine StringFunctions Modul. Dieses Modul verfügt über mehrere einfache Methoden, die hauptsächlich dazu dienen, meinen Code lesbarer zu machen. Zum Beispiel habe ich ein Conc()
Funktion, die ich als private Funktion in mehr als die Hälfte meiner Codebibliotheksmodule aufgenommen habe.
Mit der Zeit wurde mir klar, dass ich diese StringFunctions eingefügt hatte Modul in all meinen Projekten. Ich habe nie eine neue Abhängigkeit eingeführt, wenn ich eine Funktion aus diesem Modul aufgerufen habe. Ich habe Zeit verschwendet und doppelten Code für wenig oder keinen Nutzen eingeführt.
Es gab ein paar Codemodule, von denen ich sicher annehmen konnte, dass sie in jeder Anwendung vorhanden sein würden. Das waren die Module mit Funktionen, die ich am häufigsten benutzt habe. Das bedeutete, dass viele dieser Abhängigkeiten im Wesentlichen ignoriert werden konnten.
Ich pflege jetzt eine "Standardbibliothek" von Codemodulen, die ich in jedes neue Projekt ganz am Anfang importiere. Ich rufe jetzt frei Funktionen aus diesen Modulen auf, sicher in dem Wissen, dass ich keine neuen Abhängigkeiten einführen werde.
Verwenden Sie ein eindeutiges Kommentar-Token
Eines der Module in meiner "Standardbibliothek" ist ein Klassenmodul (clsApp ), die Eigenschaften und Methoden auf Anwendungsebene enthält, z. B. den aktuellen Benutzernamen und den Titelleistentext. Ich stelle auch andere Klassenmodule innerhalb von clsApp bereit , wie zum Beispiel clsStatus und clsRegistry , die besser lesbaren Zugriff auf die Access-Statusleiste bzw. die Windows-Registrierung bieten.
Ich benötige jedoch nicht in jedem Projekt Zugriff auf die Statusleiste oder die Windows-Registrierung. Um also zu vermeiden, dass eine Abhängigkeit vom clsStatus entsteht oder clsRegistry Klassen würde ich den Code, der auf diese Klassen verweist, mit einem eindeutigen „Kommentar-Token“ auskommentieren.
Dies lässt sich am einfachsten an einem Beispiel demonstrieren:
' 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
Wenn ich den Status
aktivieren wollte -Eigenschaft der obigen Klasse, könnte ich ein globales Suchen und Ersetzen für '$$
durchführen .
Das funktionierte eine Weile gut, aber es fühlte sich für mich immer klumpig an. Wahrscheinlich, weil es so war. Die andere Sache, die zu beachten ist, ist, dass die Kommentar-Token in meiner gesamten Codebibliothek global eindeutig sein müssten. Dies wäre ein Albtraum für die Wartung gewesen, wenn ich lange bei diesem Ansatz geblieben wäre.
Bedingte Kompilierung verwenden
Ein viel saubererer Ansatz besteht darin, die bedingte Kompilierung zu nutzen. Das sind die Zeilen in VBA, die mit einem Pfund/Hashtag-Zeichen ("#") beginnen. Zeilen, die mit diesem Zeichen beginnen, werden einer "Vorverarbeitung" unterzogen.
Was ist Vorverarbeitung? Das ist ein Schritt, den Programmiersprachen vor der Kompilierung ausführen. Bevor also eine Kompilierzeitüberprüfung stattfindet, werden die Vorverarbeitungszeilen ausgewertet. Dadurch können wir Code platzieren, der sonst nicht in unsere Projekte kompiliert werden könnte.
Wie können wir dies mit unseren Codebibliotheken nutzen? Auch dies lässt sich am einfachsten anhand eines Beispiels demonstrieren:
' 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
Das Beste aus beiden Welten
Wie Sie sehen können, ist dies ein sehr sauberer Weg, um das "Dependency Train"-Problem zu vermeiden.
Es ermöglicht uns, optionale Abhängigkeiten zu erstellen . Jedes Stück Code, das auf einem anderen Codebibliotheksmodul beruht, wird in eine bedingte Kompilierung #If ... Then-Anweisung eingeschlossen. Die Konstante(n) für die bedingte Kompilierung werden alle oben im Codemodul aufgelistet.
Wenn wir jetzt eine aktualisierte Version unseres Codebibliotheksmoduls erneut importieren, müssen wir einfach durchgehen und die Flags für die bedingte Kompilierung so setzen, wie es vorher war. Wenn wir uns nicht erinnern, wie die Flags gesetzt wurden, sollten wir in der Lage sein, Flags weiter zu kompilieren und anzupassen, bis das Projekt vollständig kompiliert ist.
Und wenn wir die Versionskontrolle verwenden, müssen wir uns keine Sorgen machen, dass wir vergessen, was vorher da war.