Access
 sql >> Database >  >> RDS >> Access

Gestione degli errori di livello laureato

Mi piace un buon puzzle tanto quanto il prossimo ragazzo. C'è qualcosa di soddisfacente nell'iniziare con una pila di pezzi apparentemente casuali e guardare l'immagine prendere lentamente vita mentre ristabilisci l'ordine nel caos.

Ho smesso di fare puzzle, però. Probabilmente sono passati, oh, 13 anni ormai. Fammi fare i conti. Ho quattro figli; la più grande ha 15 anni.  Sì, due anni ha ragione quando era abbastanza grande da vagare fino a un puzzle incompiuto, fuggire con uno dei pezzi e darlo da mangiare al cane o alla caldaia o al gabinetto.

E per quanto sia soddisfacente inserire l'ultimo pezzo in un puzzle, è altrettanto struggente inserire il penultimo pezzo nel puzzle e rendersi conto che manca l'ultimo pezzo.

Ecco come mi sentivo riguardo al mio codice di gestione degli errori.

Ispettore variabili di vbWatchdog

Se usi vbWatchdog per la gestione degli errori (dovresti), dovresti avere familiarità con una delle sue funzionalità più potenti:l'ispettore variabili. Questo oggetto fornisce l'accesso a ogni variabile nell'ambito a ogni livello dello stack di chiamate. Quel livello di dettaglio è l'oro digitale quando arriva il momento di risolvere i bug.

Nel corso degli anni, ho sviluppato un modulo avanzato di gestione degli errori che registra tutte queste informazioni. Mentre perfezionavo la gestione degli errori, un difetto ha cominciato a emergere. Sebbene potessi estrarre i valori della maggior parte delle mie variabili, tutto ciò che potevo ottenere dalle variabili oggetto era "Niente" o "{Oggetto}".

Questo non è un colpo su vbWatchdog. Un oggetto può essere qualsiasi cosa. Quale altro valore potrebbe mai mostrare? Tuttavia, questo pezzo mancante del puzzle mi ha rosicchiato. Potevo sentire l'universo ridere di me mentre stavo risolvendo un bug e la chiave per risolverlo era nascosta dietro quella parola esasperatamente timida, '{Oggetto}'.

Se solo avessi un modo per conoscere una o due delle proprietà identificative di quell'oggetto, potrei capire esattamente cosa sta succedendo.

Primo tentativo

Il mio primo tentativo di risolvere il problema è lo strumento preferito di ogni programmatore frustrato:la forza bruta. Nel mio gestore degli errori globale, ho aggiunto un Seleziona... Caso istruzione intorno a .TypeDesc .

Ad esempio, ho una classe builder SQL che chiamo clsSQL . Una delle proprietà di quella classe è .LastSQL . Tale proprietà contiene l'ultima istruzione SQL creata o eseguita dalla classe. Potrebbe essere un'istruzione SELECT o un INSERT/UPDATE/DELETE/etc. (Ho preso in prestito l'idea dall'oggetto DAL di web2py. )

Ecco una parte del mio gestore di errori globale:

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

Nel tempo ho iniziato ad aggiungere ulteriori tipi di oggetti personalizzati a questo elenco. Con ogni tipo personalizzato, dovrei recuperare una proprietà personalizzata diversa.

Avevo il mio ultimo pezzo del puzzle. Il problema è che l'ho trovato galleggiare nella ciotola dell'acqua del cane, tutto masticato su un lato. Immagino che tu possa dire che il mio puzzle era completo, ma è stata una vittoria di Pirro.

Una cura che fa più male che bene

Mi sono subito reso conto che questa soluzione non sarebbe stata scalabile. C'erano molti problemi. Innanzitutto, il mio codice di gestione degli errori globale si sarebbe gonfiato. Conservo il mio codice di gestione degli errori in un unico modulo standard all'interno della mia libreria di codici. Ciò significa che ogni volta che volevo aggiungere il supporto per un modulo di classe, quel codice veniva aggiunto a tutti i miei progetti. Questo era vero anche se il modulo di classe era usato solo in un singolo progetto.

Il problema successivo è che stavo introducendo dipendenze esterne nel mio codice di gestione degli errori. E se cambiassi il mio clsSQL class un giorno e rinominare o rimuovere il .LastSQL metodo? Quali sono le possibilità che mi renda conto che esisteva una tale dipendenza mentre lavoravo nel mio clsSQL classe? Questo approccio crollerebbe rapidamente sotto il suo stesso peso a meno che non trovassi un'alternativa.

Cercando una soluzione in Python

Mi sono reso conto che quello che volevo veramente era un modo per determinare una rappresentazione canonica di un oggetto dall'interno di quell'oggetto . Volevo essere in grado di implementare questa rappresentazione nel modo più semplice o complesso necessario. Volevo un modo per garantire che non esplodesse in fase di esecuzione. Volevo che fosse completamente opzionale per ogni modulo di classe.

Sembra una lunga lista dei desideri, ma sono riuscito a soddisfare ogni articolo con la soluzione che ho trovato.

Ancora una volta, ho preso in prestito un'idea da Python. Gli oggetti Python hanno tutti una proprietà speciale nota come ._repr . Questa proprietà è la rappresentazione di stringa dell'oggetto. Per impostazione predefinita, restituirà il nome del tipo e l'indirizzo di memoria dell'istanza dell'oggetto. Tuttavia, i programmatori Python possono definire un .__repr__ metodo per sovrascrivere il comportamento predefinito. Questa è la parte succosa che volevo per le mie lezioni VBA.

Ho finalmente trovato la mia soluzione ideale. Sfortunatamente, l'ho trovato in un'altra lingua in cui la soluzione è in realtà una caratteristica della lingua stessa . Come dovrebbe aiutarmi in VBA? Si scopre che l'idea era la parte importante; Dovevo solo essere un po' creativo con l'implementazione.

Interfacce in soccorso

Per contrabbandare questo concetto Python in VBA, mi sono rivolto a una caratteristica del linguaggio usata raramente:le interfacce e l'operatore TypeOf. Ecco come funziona.

Ho creato un modulo di classe che ho chiamato iRepresentation . Le interfacce nella maggior parte delle lingue sono denominate con una "i" iniziale per convenzione. Naturalmente, puoi nominare i tuoi moduli come preferisci. Ecco il codice completo per la mia iRepresentation classe.

iRepresentation.cls

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

Public Property Get Repr() As String
End Property

Dovrei sottolineare che non c'è niente di speciale in un modulo di classe che funge da interfaccia in VBA. Con ciò, intendo dire che non esiste una parola chiave a livello di modulo o un attributo nascosto che dobbiamo impostare. Possiamo anche creare un'istanza di un nuovo oggetto usando questo tipo, anche se non avrebbe molto senso (un'eccezione è il test, ma questo è un argomento per un giorno diverso). Ad esempio, il seguente sarebbe un codice valido:

Dim Representation As iRepresentation
Set Representation = New iRepresentation

Debug.Print Representation.Repr

Ora, supponiamo che io abbia un modulo di classe personalizzato chiamato oJigsawPuzzle . Il modulo di classe ha diverse proprietà e metodi, ma ne vogliamo uno che ci aiuti a identificare con quale oggetto JigsawPuzzle abbiamo a che fare quando viene generato un errore. Un candidato ovvio per un tale lavoro è la SKU, che identifica in modo univoco il puzzle come un prodotto sugli scaffali dei negozi. Naturalmente, a seconda della nostra situazione, potremmo voler includere anche altre informazioni nella nostra rappresentazione.

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

È qui che entra in gioco la magia.  Quando ci stiamo facendo strada attraverso l'oggetto Variables Inspector, ora possiamo testare ogni variabile oggetto per vedere se implementa questa interfaccia. E, se lo fa, possiamo prendere quel valore e registrarlo insieme al resto dei nostri valori variabili.

Estratto del gestore errori

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

E con ciò, il mio puzzle di gestione degli errori è ora completo. Tutti i pezzi sono contabilizzati. Non ci sono segni di morsi. Nessuno dei pezzi si sta staccando. Non ci sono spazi vuoti.

Ho finalmente riportato l'ordine nel caos.