Ich genieße ein gutes Puzzle genauso wie jeder andere. Es hat etwas Befriedigendes, mit einem Haufen scheinbar zufälliger Teile zu beginnen und zuzusehen, wie das Bild langsam Leben nimmt, während Sie die Ordnung im Chaos wiederherstellen.
Ich habe jedoch aufgehört, Puzzles zu machen. Es ist jetzt wahrscheinlich, oh, 13 Jahre her. Lass mich rechnen. Ich habe vier Kinder; Die Älteste ist 15. Ja, zwei Jahre alt ist ungefähr die Zeit, in der sie alt genug war, um zu einem unvollendeten Puzzle zu gehen, mit einem der Teile davonzulaufen und es dem Hund, dem Heizregister oder der Toilette zu füttern.
Und so befriedigend es auch ist, das letzte Teil in ein Puzzle zu stecken, es ist genauso zermürbend, das vorletzte Teil in das Puzzle zu stecken und festzustellen, dass das letzte Teil fehlt.
So ging es mir früher mit meinem Fehlerbehandlungscode.
Variablen-Inspektor von vbWatchdog
Wenn Sie vbWatchdog für Ihre Fehlerbehandlung verwenden (das sollten Sie), dann sollten Sie mit einer seiner mächtigsten Funktionen vertraut sein:dem Variablen-Inspektor. Dieses Objekt bietet Zugriff auf jede Variable im Gültigkeitsbereich auf jeder Ebene des Aufrufstapels. Dieser Detaillierungsgrad ist digitales Gold, wenn es darum geht, Fehler zu beheben.
Im Laufe der Jahre habe ich ein erweitertes Fehlerbehandlungsmodul entwickelt, das all diese Informationen protokolliert. Als ich meine Fehlerbehandlung verfeinerte, begann ein Schönheitsfehler aufzufallen. Während ich die Werte der meisten meiner Variablen extrahieren konnte, war alles, was ich jemals aus Objektvariablen herausholen konnte, entweder 'Nothing' oder '{Object}'.
Dies ist kein Klopfen an vbWatchdog. Ein Objekt kann alles sein. Welchen anderen Wert könnte es möglicherweise aufweisen? Trotzdem nagte dieses fehlende Puzzleteil an mir. Ich konnte spüren, wie das Universum über mich lachte, als ich einen Fehler behebte und der Schlüssel zur Behebung des Problems war hinter diesem einen wahnsinnig schüchternen Wort „{Objekt}“ verborgen.
Wenn ich nur eine oder zwei der identifizierenden Eigenschaften dieses Objekts kennen würde, könnte ich genau herausfinden, was los ist.
Erster Versuch
Mein erster Versuch, das Problem zu lösen, ist das Mittel der Wahl für jeden frustrierten Programmierer:Brute Force. In meiner globalen Fehlerbehandlung habe ich einen Select...Case hinzugefügt -Anweisung um die .TypeDesc
.
Ich habe zum Beispiel eine SQL-Builder-Klasse, die ich clsSQL nenne . Eine der Eigenschaften in dieser Klasse ist .LastSQL
. Diese Eigenschaft enthält die letzte SQL-Anweisung, die die Klasse erstellt oder ausgeführt hat. Es könnte eine SELECT-Anweisung oder ein INSERT/UPDATE/DELETE/etc sein. (Ich habe die Idee vom DAL-Objekt von web2py ausgeliehen. )
Hier ist ein Teil meiner globalen Fehlerbehandlung:
Select Case .TypeDesc
Case "clsSQL"
If Not .Value Is Nothing Then
ThisVar = .Name & ".LastSQL = " & .Value.LastSQL
End If
Im Laufe der Zeit fügte ich dieser Liste zusätzliche benutzerdefinierte Objekttypen hinzu. Bei jedem benutzerdefinierten Typ müsste ich eine andere benutzerdefinierte Eigenschaft abrufen.
Ich hatte mein letztes Puzzleteil. Das Problem ist, dass ich es in der Wasserschüssel des Hundes schwimmend gefunden habe, alles auf einer Seite zerkaut. Ich schätze, man könnte sagen, dass mein Puzzle vollständig war, aber es war ein Pyrrhussieg.
Ein Heilmittel, das mehr schadet als nützt
Mir wurde schnell klar, dass diese Lösung nicht skalierbar war. Es gab viele Probleme. Erstens würde mein globaler Fehlerbehandlungscode aufgebläht werden. Ich bewahre meinen Fehlerbehandlungscode in einem einzigen Standardmodul in meiner Codebibliothek auf. Das bedeutet, dass jedes Mal, wenn ich Unterstützung für ein Klassenmodul hinzufügen wollte, dieser Code zu jedem meiner Projekte hinzugefügt wurde. Das galt auch dann, wenn das Klassenmodul nur in einem einzigen Projekt verwendet wurde.
Das nächste Problem ist, dass ich externe Abhängigkeiten in meinen Fehlerbehandlungscode eingeführt habe. Was passiert, wenn ich mein clsSQL geändert habe? Klasse eines Tages und benennen Sie .LastSQL
um oder entfernen Sie sie Methode? Wie hoch sind die Chancen, dass ich feststellen würde, dass eine solche Abhängigkeit bestand, während ich in meinem clsSQL arbeitete Klasse? Dieser Ansatz würde schnell unter seinem eigenen Gewicht zusammenbrechen, wenn ich nicht eine Alternative finde.
Auf der Suche nach einer Lösung in Python
Mir wurde klar, dass ich wirklich eine Möglichkeit wollte, eine kanonische Repräsentation eines Objekts aus diesem Objekt heraus zu bestimmen . Ich wollte diese Darstellung so einfach oder komplex wie nötig umsetzen können. Ich wollte einen Weg, um zu garantieren, dass es zur Laufzeit nicht explodieren würde. Ich wollte, dass es für jedes Klassenmodul komplett optional ist.
Dies scheint eine lange Wunschliste zu sein, aber ich konnte jeden Punkt darauf mit der von mir gefundenen Lösung erfüllen.
Wieder einmal habe ich mir eine Idee von Python ausgeliehen. Alle Python-Objekte haben eine spezielle Eigenschaft namens ._repr
. Diese Eigenschaft ist die Zeichenfolgendarstellung des Objekts. Standardmäßig werden der Typname und die Speicheradresse der Objektinstanz zurückgegeben. Python-Programmierer können jedoch einen .__repr__
definieren -Methode, um das Standardverhalten zu überschreiben. Das ist der saftige Teil, den ich für meine VBA-Klassen wollte.
Ich habe endlich meine ideale Lösung gefunden. Leider habe ich es in einer anderen Sprache gefunden, wo die Lösung eigentlich ein Merkmal der Sprache selbst ist . Wie soll mir das in VBA helfen? Es stellte sich heraus, dass die Idee der wichtige Teil war; Bei der Umsetzung musste ich nur etwas kreativ werden.
Schnittstellen zur Rettung
Um dieses Python-Konzept in VBA zu schmuggeln, wandte ich mich einer selten verwendeten Funktion der Sprache zu:Schnittstellen und dem TypeOf-Operator. So funktioniert es.
Ich habe ein Klassenmodul erstellt, das ich iRepresentation genannt habe . Schnittstellen werden in den meisten Sprachen per Konvention mit einem vorangestellten „i“ benannt. Natürlich können Sie Ihre Module beliebig benennen. Hier ist der vollständige Code für meine iRepresentation Klasse.
iRepresentation.cls
`--== iRepresentation ==-- class module
Option Compare Database
Option Explicit
Public Property Get Repr() As String
End Property
Ich sollte darauf hinweisen, dass ein Klassenmodul, das als Schnittstelle in VBA dient, nichts Besonderes ist. Damit meine ich, dass es kein Schlüsselwort oder verstecktes Attribut auf Modulebene gibt, das wir setzen müssen. Wir können mit diesem Typ sogar ein neues Objekt instanziieren, obwohl es nicht viel Sinn machen würde (eine Ausnahme ist das Testen, aber das ist ein Thema für einen anderen Tag). Der folgende Code wäre beispielsweise gültig:
Dim Representation As iRepresentation
Set Representation = New iRepresentation
Debug.Print Representation.Repr
Nehmen wir nun an, ich habe ein benutzerdefiniertes Klassenmodul namens oJigsawPuzzle . Das Klassenmodul hat mehrere Eigenschaften und Methoden, aber wir wollen eine, die uns hilft, zu identifizieren, mit welchem JigsawPuzzle-Objekt wir es zu tun haben, wenn ein Fehler ausgelöst wird. Ein offensichtlicher Kandidat für einen solchen Job ist die SKU, die das Puzzle eindeutig als Produkt in den Verkaufsregalen identifiziert. Abhängig von unserer Situation möchten wir natürlich auch andere Informationen in unsere Darstellung aufnehmen.
oJigsawPuzzle.cls
'--== oJigsawPuzzle ==-- class module
Option Compare Database
Option Explicit
Implements iRepresentation ' <-- We need this line...
Private mSKU As String
Private mPieceCount As Long
Private mDesigner As String
Private mTitle As String
Private mHeightInInches As Double
Private mWidthInInches As Double
'... and these three lines
Private Property Get iRepresentation_Repr() As String
iRepresentation_Repr = mSKU
End Property
Hier kommt die Magie ins Spiel. Wenn wir uns durch das Variables Inspector-Objekt arbeiten, können wir jetzt jede Objektvariable testen, um zu sehen, ob sie diese Schnittstelle implementiert. Und wenn ja, können wir diesen Wert abrufen und zusammen mit den restlichen Variablenwerten protokollieren.
Fehlerbehandler-Auszug
' --== Global Error Handler excerpt ==--
'Include Repr property value for classes that
' implement the iRepresentation interface
If TypeOf .Value Is iRepresentation Then
Dim ObjWithRepr As iRepresentation
Set ObjWithRepr = .Value
ThisVar = .Name & ".Repr = " & ObjWithRepr.Repr
End If
Und damit ist mein Fehlerbehandlungspuzzle nun vollständig. Alle Stücke werden verrechnet. Bissspuren sind nicht vorhanden. Keines der Stücke blättert ab. Es gibt keine Leerstellen.
Ich habe endlich wieder Ordnung in das Chaos gebracht.