Ik geniet net zoveel van een goede legpuzzel als de volgende. Het heeft iets bevredigends om te beginnen met een stapel schijnbaar willekeurige stukjes en te kijken hoe de foto langzaam tot leven komt terwijl je de orde in de chaos herstelt.
Ik ben echter gestopt met legpuzzels. Het is nu waarschijnlijk, oh, 13 jaar geleden. Laat me de wiskunde doen. Ik heb vier kinderen; de oudste is 15. Ja, twee jaar oud is ongeveer de tijd dat ze oud genoeg was om naar een onvoltooide puzzel te dwalen, weg te duiken met een van de stukjes en die aan de hond of het warmteregister of het toilet te voeren.
En hoe bevredigend het ook is om het laatste stukje in een puzzel te plaatsen, het is net zo verpletterend om het voorlaatste stukje in de puzzel te plaatsen en te beseffen dat het laatste stukje ontbreekt.
Zo dacht ik vroeger over mijn foutafhandelingscode.
VbWatchdog's Variables Inspector
Als u vbWatchdog gebruikt voor uw foutafhandeling (dat zou u moeten doen), dan zou u bekend moeten zijn met een van de krachtigste functies:de Variables Inspector. Dit object biedt toegang tot elke variabele in het bereik op elk niveau van de call-stack. Dat detailniveau is digitaal goud als het tijd is om bugs op te lossen.
In de loop der jaren heb ik een geavanceerde foutafhandelingsmodule ontwikkeld die al deze informatie vastlegt. Toen ik mijn foutafhandeling verfijnde, begon een smet op te vallen. Hoewel ik de waarden van de meeste van mijn variabelen kon extraheren, was het enige dat ik ooit uit objectvariabelen kon halen 'Niets' of '{Object}'.
Dit is geen klop op vbWatchdog. Een object kan van alles zijn. Welke andere waarde zou het mogelijk kunnen tonen? Toch knaagde dit ontbrekende stukje van de puzzel aan mij. Ik voelde dat het universum me uitlachte toen ik een bug probeerde op te lossen en de sleutel om het op te lossen was verborgen achter dat ene gekmakende terughoudende woord '{Object}'.
Als ik maar een manier had om een of twee van de identificerende eigenschappen van dat object te weten, zou ik precies kunnen achterhalen wat er aan de hand was.
Eerste poging
Mijn eerste poging om het probleem op te lossen is het hulpmiddel van elke gefrustreerde programmeur:brute kracht. In mijn global error handler heb ik een Select...Case . toegevoegd statement rond de .TypeDesc
.
Ik heb bijvoorbeeld een SQL-builderklasse die ik clsSQL . noem . Een van de eigenschappen in die klasse is .LastSQL
. Die eigenschap bevat de laatste SQL-instructie die de klasse heeft gebouwd of uitgevoerd. Het kan een SELECT-instructie zijn of een INSERT/UPDATE/DELETE/etc. (Ik heb het idee geleend van het DAL-object van web2py. )
Hier is een deel van mijn globale foutafhandeling:
Select Case .TypeDesc
Case "clsSQL"
If Not .Value Is Nothing Then
ThisVar = .Name & ".LastSQL = " & .Value.LastSQL
End If
Na verloop van tijd begon ik extra aangepaste objecttypen aan deze lijst toe te voegen. Bij elk aangepast type zou ik een andere aangepaste eigenschap moeten ophalen.
Ik had mijn laatste stukje van de puzzel. Het probleem is dat ik vond dat het in de waterbak van de hond dreef, helemaal opgekauwd aan één kant. Je zou kunnen zeggen dat mijn puzzel compleet was, maar het was een Pyrrusoverwinning.
Een remedie die meer kwaad dan goed doet
Ik realiseerde me al snel dat deze oplossing niet zou schalen. Er waren veel problemen. Ten eerste zou mijn globale foutafhandelingscode opgeblazen worden. Ik bewaar mijn foutafhandelingscode in een enkele standaardmodule binnen mijn codebibliotheek. Dat betekent dat wanneer ik ondersteuning voor een klassenmodule wilde toevoegen, die code aan al mijn projecten zou worden toegevoegd. Dat was zelfs zo als de klassenmodule alleen in een enkel project werd gebruikt.
Het volgende probleem is dat ik externe afhankelijkheden in mijn foutafhandelingscode introduceerde. Wat als ik mijn clsSQL . heb gewijzigd class op een dag en hernoem of verwijder de .LastSQL
methode? Hoe groot is de kans dat ik zou beseffen dat een dergelijke afhankelijkheid bestond terwijl ik aan het werk was in mijn clsSQL klas? Deze aanpak zou snel bezwijken onder zijn eigen gewicht, tenzij ik een alternatief bedacht.
Python zoeken voor een oplossing
Ik realiseerde me dat wat ik echt wilde een manier was om een canonieke representatie van een object te bepalen vanuit dat object . Ik wilde deze voorstelling zo eenvoudig of complex kunnen uitvoeren als nodig was. Ik wilde een manier om te garanderen dat het niet zou ontploffen tijdens runtime. Ik wilde dat het voor elke klasmodule volledig optioneel zou zijn.
Dit lijkt een lange wensenlijst, maar ik heb elk item erop kunnen bevredigen met de oplossing die ik vond.
Nogmaals, ik leende een idee van Python. Python-objecten hebben allemaal een speciale eigenschap die bekend staat als ._repr
. Deze eigenschap is de tekenreeksrepresentatie van het object. Standaard worden de typenaam en het geheugenadres van de objectinstantie geretourneerd. Python-programmeurs kunnen echter een .__repr__
. definiëren methode om het standaardgedrag te overschrijven. Dit is het sappige stukje dat ik wilde voor mijn VBA-lessen.
Ik heb eindelijk mijn ideale oplossing gevonden. Helaas vond ik het in een andere taal waar de oplossing eigenlijk een kenmerk van de taal zelf is . Hoe zou dat me moeten helpen in VBA? Het bleek dat het idee het belangrijkste was; Ik moest gewoon een beetje creatief zijn met de implementatie.
Interfaces om te redden
Om dit Python-concept in VBA te smokkelen, wendde ik me tot een zelden gebruikte functie van de taal:interfaces en de TypeOf-operator. Zo werkt het.
Ik heb een klasmodule gemaakt die ik iRepresentation . heb genoemd . Interfaces worden in de meeste talen genoemd met een leidende "i" volgens afspraak. U kunt uw modules natuurlijk een naam geven die u maar wilt. Hier is de volledige code voor mijn iRepresentation klasse.
iRepresentation.cls
`--== iRepresentation ==-- class module
Option Compare Database
Option Explicit
Public Property Get Repr() As String
End Property
Ik moet erop wijzen dat er niets bijzonders is aan een klassenmodule die als interface in VBA dient. Daarmee bedoel ik dat er geen trefwoord op moduleniveau of verborgen attribuut is dat we moeten instellen. We kunnen zelfs een nieuw object instantiëren met dit type, hoewel het niet veel zin heeft (een uitzondering is testen, maar dat is een onderwerp voor een andere dag). Het volgende zou bijvoorbeeld geldige code zijn:
Dim Representation As iRepresentation
Set Representation = New iRepresentation
Debug.Print Representation.Repr
Laten we nu zeggen dat ik een aangepaste klassenmodule heb met de naam oJigsawPuzzle . De klassenmodule heeft verschillende eigenschappen en methoden, maar we willen er een die ons helpt te identificeren met welk JigsawPuzzle-object we te maken hebben wanneer er een fout wordt gemeld. Een voor de hand liggende kandidaat voor zo'n baan is de SKU, die de puzzel op unieke wijze identificeert als een product in de winkelschappen. Natuurlijk, afhankelijk van onze situatie, willen we misschien ook andere informatie in onze vertegenwoordiging opnemen.
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 komt de magie om de hoek kijken. Als we ons een weg banen door het Variables Inspector-object, kunnen we nu elke objectvariabele testen om te zien of deze interface implementeert. En als dat zo is, kunnen we die waarde pakken en samen met de rest van onze variabele waarden loggen.
Fouthandler-fragment
' --== 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
En daarmee is mijn foutafhandelingspuzzel nu compleet. Alle stukken zijn in rekening gebracht. Er zijn geen bijtsporen. Geen van de stukken pelt uit elkaar. Er zijn geen lege plekken.
Ik heb eindelijk de orde in de chaos hersteld.