sql >> Base de Datos >  >> RDS >> Access

Manejo de errores de nivel de posgrado

Disfruto de un buen rompecabezas tanto como cualquiera. Hay algo satisfactorio en comenzar con una pila de piezas aparentemente aleatorias y ver cómo la imagen cobra vida lentamente a medida que restableces el orden en el caos.

Sin embargo, dejé de hacer rompecabezas. Probablemente han pasado, oh, 13 años ahora. Déjame hacer los cálculos. Tengo cuatro hijos; la mayor tiene 15 años. Sí, dos años es el momento en que tuvo la edad suficiente para deambular hasta un rompecabezas sin terminar, fugarse con una de las piezas y dársela al perro o al calentador o al inodoro.

Y tan satisfactorio como es colocar la pieza final en un rompecabezas, es igual de desgarrador colocar la penúltima pieza en el rompecabezas y darse cuenta de que falta la pieza final.

Así es como solía sentirme acerca de mi código de manejo de errores.

Inspector de variables de vbWatchdog

Si usa vbWatchdog para su manejo de errores (debería), entonces debería estar familiarizado con una de sus características más poderosas:el Inspector de variables. Este objeto proporciona acceso a todas las variables del ámbito en cada nivel de la pila de llamadas. Ese nivel de detalle es oro digital cuando llega el momento de solucionar errores.

A lo largo de los años, he desarrollado un módulo avanzado de manejo de errores que registra toda esta información. Mientras afinaba mi manejo de errores, una imperfección comenzó a sobresalir. Si bien pude extraer los valores de la mayoría de mis variables, todo lo que pude obtener de las variables de objeto fue 'Nada' o '{Objeto}'.

Esto no es un golpe para vbWatchdog. Un objeto puede ser cualquier cosa. ¿Qué otro valor podría mostrar? Aún así, esta pieza faltante del rompecabezas me carcomía. Podía sentir que el universo se reía de mí cuando estaba solucionando un error y la clave para resolverlo estaba escondida detrás de esa palabra enloquecedoramente tímida, '{Objeto}'.

Si tan solo tuviera alguna forma de conocer una o dos de las propiedades de identificación de ese objeto, podría averiguar exactamente qué estaba pasando.

Primer intento

Mi primer intento de resolver el problema es la herramienta de referencia de todo programador frustrado:la fuerza bruta. En mi controlador de errores global, agregué Select...Case declaración alrededor del .TypeDesc .

Por ejemplo, tengo una clase de generador de SQL a la que llamo clsSQL . Una de las propiedades de esa clase es .LastSQL . Esa propiedad contiene la última instrucción SQL que la clase creó o ejecutó. Podría ser una instrucción SELECCIONAR o INSERTAR/ACTUALIZAR/ELIMINAR/etc. (Tomé prestada la idea del objeto DAL de web2py. )

Aquí hay una parte de mi controlador de errores global:

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

Con el tiempo, comencé a agregar tipos de objetos personalizados adicionales a esta lista. Con cada tipo personalizado, necesitaría obtener una propiedad personalizada diferente.

Tenía mi última pieza del rompecabezas. El problema es que lo encontré flotando en el tazón de agua del perro, todo masticado por un lado. Supongo que se podría decir que mi rompecabezas estaba completo, pero fue una victoria pírrica.

Una cura que hace más daño que bien

Rápidamente me di cuenta de que esta solución no iba a escalar. Hubo muchos problemas. Primero, mi código global de manejo de errores se iba a inflar. Mantengo mi código de manejo de errores en un solo módulo estándar dentro de mi biblioteca de códigos. Eso significa que cada vez que quisiera agregar soporte para un módulo de clase, ese código se agregaría a cada uno de mis proyectos. Eso era cierto incluso si el módulo de clase solo se usaba en un solo proyecto.

El siguiente problema es que estaba introduciendo dependencias externas en mi código de manejo de errores. ¿Qué pasa si cambio mi clsSQL? class algún día y cambie el nombre o elimine el .LastSQL ¿método? ¿Cuáles son las posibilidades de que me diera cuenta de que existía tal dependencia mientras trabajaba en mi clsSQL? ¿clase? Este enfoque colapsaría rápidamente por su propio peso a menos que encontrara una alternativa.

Buscando una solución en Python

Me di cuenta de que lo que realmente quería era alguna forma de determinar una representación canónica de un objeto desde dentro de ese objeto . Quería poder implementar esta representación tan simple o complejamente como fuera necesario. Quería una forma de garantizar que no explotaría en tiempo de ejecución. Quería que fuera completamente opcional para cada módulo de clase.

Esto parece una larga lista de deseos, pero pude satisfacer cada elemento con la solución que encontré.

Una vez más, tomé prestada una idea de Python. Todos los objetos de Python tienen una propiedad especial conocida como ._repr . Esta propiedad es la representación de cadena del objeto. De forma predeterminada, devolverá el nombre del tipo y la dirección de memoria de la instancia del objeto. Sin embargo, los programadores de Python pueden definir un .__repr__ método para anular el comportamiento predeterminado. Esta es la parte jugosa que quería para mis clases de VBA.

Finalmente encontré mi solución ideal. Desafortunadamente, lo encontré en otro idioma donde la solución es en realidad una característica del propio idioma . ¿Cómo se supone que eso me ayude en VBA? Resulta que la idea era la parte importante; Solo tenía que ser un poco creativo con la implementación.

Interfaces para el rescate

Para pasar de contrabando este concepto de Python a VBA, recurrí a una característica del lenguaje que rara vez se usa:las interfaces y el operador TypeOf. Así es como funciona.

Creé un módulo de clase al que denominé iRepresentation . Las interfaces en la mayoría de los idiomas se nombran con una "i" inicial por convención. Por supuesto, puedes nombrar tus módulos como quieras. Aquí está el código completo para mi iRepresentation clase.

iRepresentación.cls

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

Public Property Get Repr() As String
End Property

Debo señalar que no hay nada especial en un módulo de clase que sirve como interfaz en VBA. Con eso quiero decir que no hay una palabra clave de nivel de módulo o un atributo oculto que debamos configurar. Incluso podemos instanciar un nuevo objeto usando este tipo, aunque no tendría mucho sentido (una excepción es la prueba, pero ese es un tema para otro día). Por ejemplo, el siguiente sería un código válido:

Dim Representation As iRepresentation
Set Representation = New iRepresentation

Debug.Print Representation.Repr

Ahora, digamos que tengo un módulo de clase personalizado llamado oJigsawPuzzle . El módulo de clase tiene varias propiedades y métodos, pero queremos uno que nos ayude a identificar con qué objeto JigsawPuzzle estamos tratando cuando se genera un error. Un candidato obvio para tal trabajo es el SKU, que identifica de manera única el rompecabezas como un producto en los estantes de las tiendas. Por supuesto, dependiendo de nuestra situación, es posible que también deseemos incluir otra información en nuestra representación.

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

Aquí es donde entra la magia.  Cuando estamos trabajando en el objeto Inspector de variables, ahora podemos probar cada variable de objeto para ver si implementa esta interfaz. Y, si lo hace, podemos tomar ese valor y registrarlo junto con el resto de nuestros valores de variables.

Extracto del controlador de errores

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

Y con eso, mi rompecabezas de manejo de errores ahora está completo. Todas las piezas están contabilizadas. No hay marcas de mordeduras. Ninguna de las piezas se está despegando. No hay espacios vacíos.

Finalmente he restaurado el orden en el caos.