Access
 sql >> Base de données >  >> RDS >> Access

Gestion des erreurs de niveau supérieur

J'apprécie un bon puzzle autant que les autres. Il y a quelque chose de satisfaisant à commencer avec une pile de pièces apparemment aléatoires et à regarder l'image prendre lentement vie alors que vous rétablissez l'ordre dans le chaos.

Mais j'ai arrêté de faire des puzzles. Cela fait probablement, oh, 13 ans maintenant. Laissez-moi faire le calcul. J'ai quatre enfants; la plus âgée a 15 ans.  Oui, deux ans, c'est à peu près le moment où elle était assez âgée pour se promener jusqu'à un puzzle inachevé, s'enfuir avec l'une des pièces et la donner au chien, au registre de chaleur ou aux toilettes.

Et aussi satisfaisant que de placer la dernière pièce dans un puzzle, il est tout aussi écrasant de placer l'avant-dernière pièce dans le puzzle et de réaliser que la dernière pièce est manquante.

C'est ce que je pensais de mon code de gestion des erreurs.

Inspecteur des variables de vbWatchdog

Si vous utilisez vbWatchdog pour votre gestion des erreurs (vous devriez), alors vous devriez être familiarisé avec l'une de ses fonctionnalités les plus puissantes :l'inspecteur de variables. Cet objet permet d'accéder à chaque variable dans la portée à chaque niveau de la pile des appels. Ce niveau de détail est de l'or numérique quand vient le temps de résoudre les bogues.

Au fil des ans, j'ai développé un module avancé de gestion des erreurs qui enregistre toutes ces informations. Au fur et à mesure que je peaufinais ma gestion des erreurs, un défaut a commencé à se démarquer. Alors que je pouvais extraire les valeurs de la plupart de mes variables, tout ce que je pouvais obtenir des variables d'objet était soit 'Nothing' ou '{Object}'.

Ce n'est pas un coup porté à vbWatchdog. Un objet peut être n'importe quoi. Quelle autre valeur pourrait-il éventuellement montrer? Pourtant, cette pièce manquante du puzzle me rongeait. Je pouvais sentir l'univers se moquer de moi lorsque je résolvais un bogue et que la clé pour le résoudre était cachée derrière ce mot exaspérant, '{Object}'.

Si seulement j'avais un moyen de connaître une ou deux des propriétés d'identification de cet objet, je pourrais comprendre exactement ce qui se passe.

Première tentative

Ma première tentative pour résoudre le problème est l'outil incontournable de tout programmeur frustré :la force brute. Dans mon gestionnaire d'erreur global, j'ai ajouté un Select...Case déclaration autour du .TypeDesc .

Par exemple, j'ai une classe de constructeur SQL que j'appelle clsSQL . L'une des propriétés de cette classe est .LastSQL . Cette propriété contient la dernière instruction SQL que la classe a créée ou exécutée. Il peut s'agir d'une instruction SELECT ou d'un INSERT/UPDATE/DELETE/etc. (J'ai emprunté l'idée de l'objet DAL de web2py. )

Voici une partie de mon gestionnaire d'erreurs global :

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

Au fil du temps, j'ai commencé à ajouter des types d'objets personnalisés supplémentaires à cette liste. Avec chaque type personnalisé, je devrais récupérer une propriété personnalisée différente.

J'avais ma dernière pièce au puzzle. Le problème est que je l'ai trouvé flottant dans le bol d'eau du chien, tout mâché d'un côté. Je suppose que vous pourriez dire que mon puzzle était complet, mais c'était une victoire à la Pyrrhus.

Un remède qui fait plus de mal que de bien

Je me suis vite rendu compte que cette solution n'allait pas à l'échelle. Il y avait beaucoup de problèmes. Tout d'abord, mon code global de gestion des erreurs allait être gonflé. Je conserve mon code de gestion des erreurs dans un seul module standard au sein de ma bibliothèque de codes. Cela signifie que chaque fois que je voulais ajouter la prise en charge d'un module de classe, ce code était ajouté à chacun de mes projets. Cela était vrai même si le module de classe n'était utilisé que dans un seul projet.

Le problème suivant est que j'introduisais des dépendances externes dans mon code de gestion des erreurs. Et si je changeais mon clsSQL classez un jour et renommez ou supprimez le .LastSQL méthode? Quelles sont les chances que je me rende compte qu'une telle dépendance existait pendant que je travaillais dans mon clsSQL classe? Cette approche s'effondrerait rapidement sous son propre poids à moins que je ne trouve une alternative.

Cherchez Python pour une solution

J'ai réalisé que ce que je voulais vraiment, c'était un moyen de déterminer une représentation canonique d'un objet à partir de cet objet . Je voulais pouvoir mettre en œuvre cette représentation de manière aussi simple ou complexe que nécessaire. Je voulais un moyen de garantir qu'il n'exploserait pas à l'exécution. Je voulais qu'il soit complètement facultatif pour chaque module de classe.

Cela semble être une longue liste de souhaits, mais j'ai pu satisfaire chaque élément avec la solution que j'ai trouvée.

Encore une fois, j'ai emprunté une idée à Python. Les objets Python ont tous une propriété spéciale appelée ._repr . Cette propriété est la représentation sous forme de chaîne de l'objet. Par défaut, il renverra le nom du type et l'adresse mémoire de l'instance de l'objet. Cependant, les programmeurs Python peuvent définir un .__repr__ méthode pour remplacer le comportement par défaut. C'est le morceau juteux que je voulais pour mes cours VBA.

J'ai enfin trouvé ma solution idéale. Malheureusement, je l'ai trouvé dans une autre langue où la solution est en fait une caractéristique de la langue elle-même . Comment est-ce censé m'aider dans VBA? Il s'avère que l'idée était la partie importante; Je devais juste faire preuve d'un peu de créativité avec la mise en œuvre.

Interfaces à la rescousse

Pour introduire ce concept Python en contrebande dans VBA, je me suis tourné vers une fonctionnalité rarement utilisée du langage :les interfaces et l'opérateur TypeOf. Voici comment cela fonctionne.

J'ai créé un module de classe que j'ai nommé iRepresentation . Les interfaces dans la plupart des langages sont nommées avec un "i" de tête par convention. Bien sûr, vous pouvez nommer vos modules comme bon vous semble. Voici le code complet de mon iRepresentation classe.

iReprésentation.cls

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

Public Property Get Repr() As String
End Property

Je dois souligner qu'il n'y a rien de spécial dans un module de classe qui sert d'interface dans VBA. Par cela, je veux dire qu'il n'y a pas de mot-clé au niveau du module ou d'attribut caché que nous devons définir. Nous pouvons même instancier un nouvel objet en utilisant ce type, même si cela n'aurait pas beaucoup d'intérêt (une exception est le test, mais c'est un sujet pour un autre jour). Par exemple, le code suivant serait valide :

Dim Representation As iRepresentation
Set Representation = New iRepresentation

Debug.Print Representation.Repr

Maintenant, disons que j'ai un module de classe personnalisé nommé oJigsawPuzzle . Le module de classe a plusieurs propriétés et méthodes, mais nous en voulons une qui nous aidera à identifier l'objet JigsawPuzzle auquel nous avons affaire lorsqu'une erreur se produit. Un candidat évident pour un tel travail est le SKU, qui identifie de manière unique le puzzle comme un produit sur les étagères des magasins. Bien sûr, selon notre situation, nous pouvons également inclure d'autres informations dans notre déclaration.

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

C'est là que la magie entre en jeu. Lorsque nous parcourons l'objet Inspecteur de variables, nous pouvons maintenant tester chaque variable d'objet pour voir si elle implémente cette interface. Et, si c'est le cas, nous pouvons saisir cette valeur et l'enregistrer avec le reste de nos valeurs variables.

Extrait du gestionnaire d'erreurs

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

Et avec cela, mon puzzle de gestion des erreurs est maintenant terminé. Toutes les pièces sont comptabilisées. Il n'y a pas de marques de morsures. Aucun des morceaux ne se décolle. Il n'y a pas d'espaces vides.

J'ai enfin remis de l'ordre dans le chaos.