Eu gosto de um bom quebra-cabeça tanto quanto qualquer outro cara. Há algo de satisfatório em começar com uma pilha de peças aparentemente aleatórias e ver a imagem ganhar vida lentamente enquanto você restaura a ordem no caos.
Eu parei de fazer quebra-cabeças, no entanto. Faz provavelmente, oh, 13 anos agora. Deixe-me fazer as contas. Eu tenho quatro filhos; a mais velha tem 15 anos. Sim, dois anos está certo sobre a época em que ela tinha idade suficiente para vagar até um quebra-cabeça inacabado, fugir com uma das peças e alimentá-la para o cachorro ou o registro de calor ou o banheiro.
E por mais satisfatório que seja colocar a peça final em um quebra-cabeça, é tão esmagador quanto colocar a penúltima peça no quebra-cabeça e perceber que a peça final está faltando.
Era assim que eu me sentia em relação ao meu código de tratamento de erros.
Inspetor de variáveis do vbWatchdog
Se você usa o vbWatchdog para o tratamento de erros (você deveria), então você deve estar familiarizado com um de seus recursos mais poderosos:o Inspetor de Variáveis. Esse objeto fornece acesso a todas as variáveis no escopo em cada nível da pilha de chamadas. Esse nível de detalhe é ouro digital quando chega a hora de solucionar bugs.
Ao longo dos anos, desenvolvi um módulo avançado de tratamento de erros que registra todas essas informações. Enquanto eu ajustava meu tratamento de erros, um defeito começou a se destacar. Embora eu pudesse extrair os valores da maioria das minhas variáveis, tudo o que eu conseguia obter das variáveis de objeto era 'Nada' ou '{Objeto}'.
Este não é um golpe no vbWatchdog. Um objeto pode ser qualquer coisa. Que outro valor poderia mostrar? Ainda assim, essa peça que faltava no quebra-cabeça me atormentava. Eu podia sentir o universo rindo de mim quando eu estava solucionando algum bug e a chave para resolvê-lo estava escondida atrás daquela palavra irritantemente tímida, '{Object}'.
Se ao menos eu tivesse alguma forma de conhecer uma ou duas das propriedades identificadoras daquele objeto, poderia descobrir exatamente o que estava acontecendo.
Primeira tentativa
Minha primeira tentativa de resolver o problema é a ferramenta preferida de todo programador frustrado:força bruta. Em meu manipulador de erros global, adicionei um Select...Case declaração em torno do
.TypeDesc
. Por exemplo, tenho uma classe de construtor SQL que chamo de clsSQL . Uma das propriedades dessa classe é
.LastSQL
. Essa propriedade contém a última instrução SQL que a classe construiu ou executou. Pode ser uma instrução SELECT ou um INSERT/UPDATE/DELETE/etc. (Eu peguei emprestada a ideia do objeto DAL do web2py. ) Aqui está uma parte do meu manipulador de erros global:
Select Case .TypeDesc
Case "clsSQL"
If Not .Value Is Nothing Then
ThisVar = .Name & ".LastSQL = " & .Value.LastSQL
End If
Com o tempo, comecei a adicionar tipos de objetos personalizados adicionais a essa lista. Com cada tipo personalizado, eu precisaria buscar uma propriedade personalizada diferente.
Eu tinha minha última peça do quebra-cabeça. O problema é que eu o encontrei flutuando na tigela de água do cachorro, todo mastigado de um lado. Acho que você poderia dizer que meu quebra-cabeça estava completo, mas foi uma vitória de Pirro.
Uma cura que faz mais mal do que bem
Percebi rapidamente que essa solução não seria dimensionada. Havia muitos problemas. Primeiro, meu código global de tratamento de erros ia ficar inchado. Eu mantenho meu código de tratamento de erros em um único módulo padrão dentro da minha biblioteca de código. Isso significa que sempre que eu quiser adicionar suporte para um módulo de classe, esse código será adicionado a cada um dos meus projetos. Isso era verdade mesmo se o módulo de classe fosse usado apenas em um único projeto.
O próximo problema é que eu estava introduzindo dependências externas no meu código de tratamento de erros. E se eu alterar meu clsSQL class algum dia e renomeie ou remova o
.LastSQL
método? Quais são as chances de eu perceber que tal dependência existia enquanto eu trabalhava no meu clsSQL aula? Essa abordagem entraria em colapso rapidamente sob seu próprio peso, a menos que eu descobrisse uma alternativa. Procurando uma solução em Python
Percebi que o que eu realmente queria era alguma forma de determinar uma representação canônica de um objeto de dentro desse objeto . Eu queria ser capaz de implementar essa representação de forma simples ou complexa, conforme necessário. Eu queria uma maneira de garantir que não iria explodir em tempo de execução. Eu queria que fosse completamente opcional para cada módulo de classe.
Parece uma longa lista de desejos, mas consegui satisfazer todos os itens com a solução que encontrei.
Mais uma vez, peguei emprestada uma ideia do Python. Todos os objetos Python têm uma propriedade especial conhecida como
._repr
. Esta propriedade é a representação em string do objeto. Por padrão, ele retornará o nome do tipo e o endereço de memória da instância do objeto. No entanto, os programadores Python podem definir um .__repr__
método para substituir o comportamento padrão. Esta é a parte suculenta que eu queria para minhas aulas de VBA. Finalmente encontrei minha solução ideal. Infelizmente, encontrei em outro idioma onde a solução é na verdade um recurso do próprio idioma . Como isso pode me ajudar em VBA? Acontece que a ideia era a parte importante; Eu só tive que ser um pouco criativo com a implementação.
Interfaces para o resgate
Para contrabandear esse conceito de Python para o VBA, recorri a um recurso raramente usado da linguagem:interfaces e o operador TypeOf. Aqui está como funciona.
Criei um módulo de classe que chamei de iRepresentation . As interfaces na maioria dos idiomas são nomeadas com um "i" inicial por convenção. Claro, você pode nomear seus módulos como quiser. Aqui está o código completo para minha iRepresentation aula.
iRepresentation.cls
`--== iRepresentation ==-- class module
Option Compare Database
Option Explicit
Public Property Get Repr() As String
End Property
Devo salientar que não há nada de especial em um módulo de classe que serve como interface no VBA. Com isso, quero dizer que não há palavra-chave de nível de módulo ou atributo oculto que precisamos definir. Podemos até instanciar um novo objeto usando esse tipo, embora não haja muito sentido (uma exceção é testar, mas isso é um tópico para outro dia). Por exemplo, o seguinte seria um código válido:
Dim Representation As iRepresentation
Set Representation = New iRepresentation
Debug.Print Representation.Repr
Agora, digamos que eu tenha um módulo de classe personalizado chamado oJigsawPuzzle . O módulo de classe tem várias propriedades e métodos, mas queremos um que nos ajude a identificar com qual objeto JigsawPuzzle estamos lidando quando um erro é gerado. Um candidato óbvio para esse trabalho é o SKU, que identifica exclusivamente o quebra-cabeça como um produto nas prateleiras das lojas. Claro, dependendo de nossa situação, podemos querer incluir outras informações em nossa representação também.
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
É aqui que entra a mágica. Quando estamos trabalhando no objeto Variables Inspector, agora podemos testar cada variável de objeto para ver se ela implementa essa interface. E, se isso acontecer, podemos pegar esse valor e registrá-lo junto com o restante de nossos valores de variáveis.
Trecho do manipulador de erros
' --== 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 com isso, meu quebra-cabeça de tratamento de erros está completo. Todas as peças são contabilizadas. Não há marcas de mordida. Nenhuma das peças está descascando. Não há espaços vazios.
Finalmente restaurei a ordem ao caos.