sql >> Databasteknik >  >> RDS >> Access

Graduate Level Felhantering

Jag tycker lika mycket om ett bra pussel som nästa kille. Det är något tillfredsställande med att börja med en hög med till synes slumpmässiga bitar och se bilden sakta ta liv när du återställer ordningen i kaoset.

Jag slutade dock lägga pussel. Det har nog gått, åh, 13 år nu. Låt mig räkna ut. Jag har fyra barn; den äldsta är 15.  Japp, två år gammal är ungefär den tiden då hon var gammal nog att vandra fram till ett oavslutat pussel, fly med en av bitarna och mata den till hunden eller värmeregistret eller toaletten.

Och lika tillfredsställande som det är att lägga den sista biten i ett pussel, lika själakrossande är det att placera den näst sista biten i pusslet och inse att den sista biten saknas.

Det var så jag brukade känna om min felhanteringskod.

vbWatchdogs variabelinspektör

Om du använder vbWatchdog för din felhantering (du borde), bör du känna till en av dess mest kraftfulla funktioner:Variables Inspector. Detta objekt ger åtkomst till varje variabel i omfattning på varje nivå i anropsstacken. Den detaljnivån är digitalt guld när det är dags att felsöka buggar.

Under åren har jag utvecklat en avancerad felhanteringsmodul som loggar all denna information. När jag finjusterade min felhantering började en fläck sticka ut. Även om jag kunde extrahera värdena för de flesta av mina variabler, var allt jag någonsin kunde få ut av objektvariabler antingen 'Ingenting' eller '{Object}'.

Detta är ingen knackning på vbWatchdog. Ett föremål kan vara vad som helst. Vilket annat värde kan det visa? Ändå gnagde den här saknade pusselbiten i mig. Jag kunde känna hur universum skrattade åt mig när jag felsökte någon bugg och nyckeln till att reda ut det gömdes bakom det där ena vansinnigt töntiga ordet '{Object}'.

Om jag bara hade något sätt att känna till en eller två av de identifierande egenskaperna för det objektet, skulle jag kunna ta reda på exakt vad som pågick.

Första försöket

Mitt första försök att lösa problemet är varje frustrerad programmerares go-to-verktyg:brute force. I min globala felhanterare lade jag till ett Välj...fall uttalande kring .TypeDesc .

Till exempel har jag en SQL-byggarklass som jag kallar clsSQL . En av egenskaperna i den klassen är .LastSQL . Den egenskapen innehåller den sista SQL-satsen som klassen byggde eller körde. Det kan vara en SELECT-sats eller en INSERT/UPDATE/DELETE/etc. (Jag lånade idén från web2pys DAL-objekt. )

Här är en del av min globala felhanterare:

Select Case .TypeDesc
Case "clsSQL"
    If Not .Value Is Nothing Then
        ThisVar = .Name & ".LastSQL = " & .Value.LastSQL
    End If

Med tiden började jag lägga till ytterligare anpassade objekttyper till den här listan. Med varje anpassad typ skulle jag behöva hämta en annan anpassad egenskap.

Jag hade min sista pusselbit. Problemet är att jag hittade det flytande i hundens vattenskål, allt tuggat på ena sidan. Jag antar att man kan säga att mitt pussel var komplett, men det var en pyrrhusseger.

En kur som gör mer skada än nytta

Jag insåg snabbt att den här lösningen inte skulle skala. Det var många problem. Först skulle min globala felhanteringskod bli uppsvälld. Jag förvarar min felhanteringskod i en enda standardmodul i mitt kodbibliotek. Det betyder att varje gång jag ville lägga till stöd för en klassmodul så skulle den koden läggas till i alla mina projekt. Det gällde även om klassmodulen bara användes i ett enda projekt.

Nästa problem är att jag införde externa beroenden i min felhanteringskod. Vad händer om jag ändrade min clsSQL klass en dag och byt namn på eller ta bort .LastSQL metod? Vilka är chanserna att jag skulle inse att ett sådant beroende existerade medan jag arbetade i min clsSQL klass? Detta tillvägagångssätt skulle snabbt kollapsa under sin egen tyngd om jag inte kom på ett alternativ.

Söker efter en lösning till Python

Jag insåg att det jag verkligen ville ha var något sätt att bestämma en kanonisk representation av ett objekt inifrån det objektet . Jag ville kunna implementera denna representation så enkelt eller komplext som det behövs. Jag ville ha ett sätt att garantera att det inte skulle sprängas vid körning. Jag ville att det skulle vara helt valfritt för varje klassmodul.

Det här verkar vara en lång önskelista, men jag kunde tillfredsställa alla objekt på den med den lösning jag hittade.

Återigen lånade jag en idé från Python. Python-objekt har alla en speciell egenskap som kallas ._repr . Den här egenskapen är strängrepresentationen av objektet. Som standard returnerar den typnamnet och minnesadressen för objektinstansen. Däremot kan Python-programmerare definiera en .__repr__ metod för att åsidosätta standardbeteendet. Det här är den saftiga biten jag ville ha till mina VBA-klasser.

Jag hittade äntligen min perfekta lösning. Tyvärr hittade jag det på ett annat språk där lösningen faktiskt är en funktion i själva språket . Hur ska det hjälpa mig i VBA? Det visar sig att idén var den viktiga delen; Jag var bara tvungen att vara lite kreativ med implementeringen.

Gränssnitt till räddningen

För att smuggla in detta Python-koncept till VBA vände jag mig till en sällan använd funktion i språket:gränssnitt och TypeOf-operatören. Så här fungerar det.

Jag skapade en klassmodul som jag döpte till iRepresentation . Gränssnitt på de flesta språk har ett ledande "i" enligt konventionen. Naturligtvis kan du namnge dina moduler vad du vill. Här är hela koden för min iRepresentation klass.

iRepresentation.cls

`--== iRepresentation ==-- class module
Option Compare Database
Option Explicit

Public Property Get Repr() As String
End Property

Jag bör påpeka att det inte finns något speciellt med en klassmodul som fungerar som ett gränssnitt i VBA. Med det menar jag att det inte finns något nyckelord på modulnivå eller ett dolt attribut som vi behöver ställa in. Vi kan till och med instansiera ett nytt objekt med den här typen, även om det inte skulle vara någon mening (ett undantag är att testa, men det är ett ämne för en annan dag). Till exempel skulle följande vara giltig kod:

Dim Representation As iRepresentation
Set Representation = New iRepresentation

Debug.Print Representation.Repr

Låt oss nu säga att jag har en anpassad klassmodul som heter oJigsawPuzzle . Klassmodulen har flera egenskaper och metoder, men vi vill ha en som hjälper oss att identifiera vilket JigsawPuzzle-objekt vi har att göra med när ett fel uppstår. En uppenbar kandidat för ett sådant jobb är SKU, som unikt identifierar pusslet som en produkt på butikshyllorna. Beroende på vår situation kanske vi också vill inkludera annan information i vår representation.

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

Här kommer magin in.  När vi arbetar oss igenom Variables Inspector-objektet kan vi nu testa varje objektvariabel för att se om den implementerar detta gränssnitt. Och om det gör det kan vi ta det värdet och logga det tillsammans med resten av våra variabelvärden.

Felhanterarens utdrag

' --== 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

Och med det är mitt felhanteringspussel nu klart. Alla bitar redovisas. Det finns inga bitmärken. Ingen av bitarna skalar isär. Det finns inga tomma utrymmen.

Jag har äntligen återställt ordningen i kaoset.


  1. Hur man kommer igång med SQL Server på Azure

  2. Hur man implementerar prioriteringar i SQL (postgres)

  3. Hur du ändrar din sessions valutasymbol i Oracle

  4. Postgis installation:typ geometri existerar inte