Rastreando Erros com TExErrorDialog

Como sabemos é muito importante o modo como tratamos os erros ocorridos nos sistemas, tanto os esperados quanto os inesperados, isso pode fazer a diferença na hora da manutenção de um sistema complexo e também transmite confiabilidade ao usuário final. Um programa é considerado robusto quando tem a capacidade de rastrear todos os seus erros e apresentar uma informação plausível para o usuário tomar alguma decisão ou entrar em contato com o setor de desenvolvimento do sistema.

Criando o pacote

Vamos começar criando um pacote para o componente TExErrorDialog. Vá no menu File|New > Other e escolha a opção Package. Salve o pacote com o nome sugerido em um diretório e clique na opção Options, na descrição do pacote digite o nome que irá aparecer na lista de pacotes instalados (Component|Install Packges). Certifique-se que as opções Designtime and runtime e Explicit rebuild estejam marcadas, caso queira, acesse a aba Version Info e configure as descrições do arquivo.

Criando a unit do componente

Vá ao menu Component > New Component, na caixa de diálogo crie um descendente (Ancestor Type) de TComponent com o nome (Class Name) de “TExErrorDialog” na paleta ExControls e o nome da unit como “untExErrorDialog”, não se esqueça de mudar o diretório da unit, clique em OK. Vamos codificar as propriedades, eventos e tipos do componente conforme a Listagem 1.

 interface
 
 uses Windows, Messages, SysUtils, Classes, Forms;
 
 type
 { Tipos de Erro }
 TErrorType = (etNone = 0, etRunTime = 1, etSQL = 2,
  etValidation = 3, etLogic = 4);
 { Status do Erro }
 TErrorStatus = (esNone = 0, esFatal = 1, 
  esNormal = 2);
 {Tipo de Erro}
 TErrorInfo = record
  ErrorStatus: TErrorStatus;
  ErrorType: TErrorType;
  ErrorSource: string;
  ErrorMsgUser: string;
  ErrorMsgSys: string;
  ErrorClass: string;
  ErrorMethod: string;
  ErrorCode: string;
 end;
 
 { Componente de Erro }
 TExErrorDialog = class(TComponent)
  private
  FErrorInfo: TErrorInfo;
  FErrorStatus: TErrorStatus;
  FErrorType: TErrorType;
  FErrorSource: string;
  FErrorMsgUser: string;
  FErrorMsgSys: string;
  FErrorClass: string;
  FErrorMethod: string;
  FErrorCode: string;
  FShowDetail: Boolean;
  FClearAfterShow: Boolean;
  FOnBeforeShow: TNotifyEvent;
  FOnAfterShow: TNotifyEvent;
  procedure SetErrorInfoValues();
  procedure SetErrorPropValues();
  protected
  public
  procedure Clear();
  procedure ShowErrorMsg(); overload;
  procedure ShowErrorMsg(
  const pErrorInfo: TErrorInfo); overload;
  published
  property ErrorStatus: TErrorStatus
  read FErrorStatus write FErrorStatus;
  property ErrorType: TErrorType
  read FErrorType write FErrorType;
  property ErrorSource: string
  read FErrorSource write FErrorSource;
  property ErrorMsgUser: string
  read FErrorMsgUser write FErrorMsgUser;
  property ErrorMsgSys: string
  read FErrorMsgSys write FErrorMsgSys;
  property ErrorClass: string
  read FErrorClass write FErrorClass;
  property ErrorMethod: string
  read FErrorMethod write FErrorMethod;
  property ErrorCode: string
  read FErrorCode write FErrorCode;
  property ShowDetail: Boolean
  read FShowDetail write FShowDetail;
  property ClearAfterShow: Boolean
  read FClearAfterShow write FClearAfterShow;
  property OnBeforeShow: TNotifyEvent
  read FOnBeforeShow write FOnBeforeShow;
  property OnAfterShow: TNotifyEvent
  read FOnAfterShow write FOnAfterShow;
 end;
 
 procedure Register;
 
 implementation
 
 uses untExGuiErrorDialog;
 
 procedure Register;
 begin
  RegisterComponents('ExControls', [TExErrorDialog]);
 end;
 
 { Mostra a mensagem passando os parametros internos }
 procedure TExErrorDialog.ShowErrorMsg();
 begin
  { Sincroniza as propriedades com o record }
  SetErrorInfoValues;
  { Chama a rotina }
  ShowErrorMsg(FErrorInfo);
 end;
 
 { Mostra a mensagem passando os parametros
  de pErrorInfo }
 procedure TExErrorDialog.ShowErrorMsg(
  const pErrorInfo: TErrorInfo);
 begin
  { Cria o formulario }
  frmExGuiErrorDialog :=
  TfrmExGuiErrorDialog.Create(Self);
  FErrorInfo := pErrorInfo;
  frmExGuiErrorDialog.SetFieldValue(pErrorInfo);
  { Sincroniza o record com as propriedades }
  SetErrorPropValues;
  { Verifica e dispara o evento }
  if Assigned(FOnBeforeShow) then
  FOnBeforeShow(Self);
  { Seta o valor da propriedade ShowDetail }
  frmExGuiErrorDialog.cmdDetalhes.Visible :=
  FShowDetail;
  { Mostra o form Modal }
  frmExGuiErrorDialog.ShowModal;
  if Assigned(FOnAfterShow) then
  FOnAfterShow(Self);
  { Verifica e limpa os parametros }
  if FClearAfterShow then
  Clear();
  { Libera a memória }
  frmExGuiErrorDialog.Free;
  frmExGuiErrorDialog := nil;
 end;
 
 { Seta as propriedades internas do record }
 procedure TExErrorDialog.SetErrorInfoValues();
 begin
  { Seta as propriedades do record }
  FErrorInfo.ErrorType := FErrorType;
  FErrorInfo.ErrorStatus := FErrorStatus;
  FErrorInfo.ErrorSource := FErrorSource;
  FErrorInfo.ErrorMsgUser := FErrorMsgUser;
  FErrorInfo.ErrorMsgSys := FErrorMsgSys;
  FErrorInfo.ErrorClass := FErrorClass;
  FErrorInfo.ErrorMethod := FErrorMethod;
  FErrorInfo.ErrorCode := FErrorCode;
 end;
 
 { Seta as propriedades internas do componente }
 procedure TExErrorDialog.SetErrorPropValues();
 begin
  { Seta as propriedades do record }
  FErrorType := FErrorInfo.ErrorType;
  FErrorStatus := FErrorInfo.ErrorStatus;
  FErrorSource := FErrorInfo.ErrorSource;
  FErrorMsgUser := FErrorInfo.ErrorMsgUser;
  FErrorMsgSys := FErrorInfo.ErrorMsgSys;
  FErrorClass := FErrorInfo.ErrorClass;
  FErrorMethod := FErrorInfo.ErrorMethod;
  FErrorCode := FErrorInfo.ErrorCode;
 end;
 
 { Limpa todas as propriedades internas }
 procedure TExErrorDialog.Clear();
 begin
  { Limpa as propriedades do record }
  with FErrorInfo do
  begin
  ErrorType := etNone;
  ErrorStatus := esNone;
  ErrorSource := EmptyStr;
  ErrorMsgUser := EmptyStr;
  ErrorMsgSys := EmptyStr;
  ErrorClass := EmptyStr;
  ErrorMethod := EmptyStr;
  ErrorCode := EmptyStr;
  end;
  { Limpa as propriedades publicas }
  ErrorType := etNone;
  ErrorStatus := esNone;
  ErrorSource := EmptyStr;
  ErrorMsgUser := EmptyStr;
  ErrorMsgSys := EmptyStr;
  ErrorClass := EmptyStr;
  ErrorMethod := EmptyStr;
  ErrorCode := EmptyStr;
 end;
 end.
Listagem 1. Código fonte completo

Propriedades do componente

Nessa seção destaco as propriedades ErrorStatus e ErrorType que são de tipos, delarados no inicio da unit e que nos mostram os tipos de erros que vamos apresentar e qual o status desses erros, como vemos no código a seguir:

{ Tipos de Erro }
 TErrorType = (etNone = 0, etRunTime = 1, etSQL = 2,
  etValidation = 3, etLogic = 4);
 { Status do Erro }
 TErrorStatus = (esNone = 0, esFatal = 1, 
  esNormal = 2);

Eventos do componente

Declaramos dois eventos para ajudar a controlar erros fatais e dar um tratamento genérico de erro que são OnBeforeShow e OnAfterShow. As propriedades ClearAfterShow e ErrorStatus trabalham baseadas no seguintes eventos:

property OnBeforeShow: TNotifyEvent read FonBeforeShow
  write FOnBeforeShow;
 property OnAfterShow: TNotifyEvent read FonAfterShow
  write FOnAfterShow;

Codificando o componente

Temos duas rotinas que merecem destaque, pois ambas tem o mesmo nome com parâmetros diferentes. ShowErrorMsg (com a diretiva overload, que permite declarar rotinas com nomes iguais e parâmetros de tipos diferentes), em uma das suas declarações possui um parâmetro que é um record, ou seja, uma estrutura de dados (definida no escopo da unit), para facilitar o intercâmbio de um conjunto de informações entre rotinas e funções. Por causa desse record devemos sincronizar as propriedades do componente com o record recebido como parâmetro e vice-versa, para isso temos as rotinas SetErrorInfoValues e SetErrorPropValues veja na Listagem 2.

 private
  procedure SetErrorInfoValues();
  procedure SetErrorPropValues();
 protected
 public
  procedure Clear();
  procedure ShowErrorMsg(); overload;
  procedure ShowErrorMsg(
  const pErrorInfo: TErrorInfo); overload;
Listagem 2. Rotinas do componente

Formulário do componente

Sei que vocês estão tentados a compilar o componente, mas ainda falta a parte mais importante: o formulário de apresentação, ou seja, onde todas as informações serão exibidas para o usuário final.

Adicione um novo formulário (File|New > Form) no pacote e salve sua unit como “untGuiExErrorDialog.pas” e o nome do formulário como “frmExGuiErrorDialog”. Por que Gui?, Gui é a abreviação de Graphic User Interface nada mais conveniente.

Adicione no formulário os seguintes componentes: um StaticText, dois Buttons, um Memo e quatro Image. Disponha os componentes para que fiquem parecido com a Figura 1.

Formulário de apresentação dos erros
Figura 1. Formulário de apresentação dos erros

Altere as propriedades dos componentes conforme a Tabela 1.

Componente Nome Valor
StaticText Name lblErrMsg
BevelKind BkTile
Button Name cmdDetalhes
Caption <<&Detalhes
Default True
Button Name cmdOk
Caption &Ok
Cancel True
Memo Name txtErrorDescription
ScrollBars ssVertical
Image Name imgValidation
Image Name imgLogic
Image Name imgInformation
Image Name imgRunTime
Tabela 1. Propriedade dos componentes no formulário

Escolha uma imagem de 32x32 para cada um dos Images (altere para False a propriedade Visible dos Images), cada imagem representa um tipo de erro, e deixe todos exatamente sobrepostos, ao terminar lembre-se de voltar o tamanho do seu formulário para que fique sem aparecer o Memo (altere para bsSingle a propriedade BorderStyle do formulário). Insira na seção public do formulário o seguite código:

procedure SetFieldValue(const pErrorInfo: TErrorInfo); 

E implemente-o conforme a Listagem 3.

 procedure TfrmExGuiErrorDialog.SetFieldValue(
  const pErrorInfo: TErrorInfo);
 begin
  with pErrorInfo do 
  begin
  { Verifica qual o tipo de erro }
  case ErrorType of
  etNone:
  begin
  Caption := 'Tipo de Erro Desconhecido.';
  imgRunTime.Visible := True;
  end;
  etValidation:
  begin
  Caption := 'Erro de Validação.';
  imgValidation.Visible := True;
  end;
  etSQL:
  begin
  Caption := 'Erro de Comando SQL.';
  imgRunTime.Visible := True;
  end;
  etRunTime:
  begin
  Caption := 'Erro de Run-Time.';
  imgRunTime.Visible := True;
  end;
  etLogic:
  begin
  Caption := 'Erro de Lógica de Programação.';
  imgLogic.Visible := True;
  end;
  end;
  lblErrMsg.Caption := ErrorMsgUser;
  txtErrorDescription.Text :=
  'Code: ' + ErrorCode + #13#10 +
  'Source: ' + ErrorSource + #13#10 +
  'Class: ' + ErrorClass + #13#10 +
  'Method: ' + ErrorMethod + #13#10 +
  'Description: ' + ErrorMsgSys;
  end;
 end;
Listagem 3. Código do método SetFieldValue do formulário

No botão OK apenas indicamos a propriedade ModalResult como mrOK e fechamos o formulário (Close).

Devemos declarar na cláusula uses do formulário a unit do componente (untExErrorDialog), o que já nos permite tirar vantagem do record, facilitando o intercâmbio de informações entre o componente e o formulário.

Outro detalhe, é o tipo de erro etSQL não possuir uma imagem em particular, ele utiliza a mesma imagem do erro de run-time. Já podemos compilar e instalar o componente.

Alterando a imagem que representa o componente

Escolha uma imagem que representa bem o componente no formato .BMP com o tamanho 24x24, vá no menu Tools e abra o Image Editor, crie um novo Resource files (.dcr), adicione um novo bitmap, mude seu nome para o nome do componente (TEXERRORDIALOG) e cole a imagem desenhada na área em branco, salve o arquivo com o nome “untExErrorDialog.dcr”.

Para que a figura apareça na IDE do Delphi, vá no menu Project|View Source e adicione a seguinte linha de código:

{$R *.res}
 {$R untExErrorDialog.dcr} <- adicione aqui
 ...

Veja na Figura 2, como ficará o componente registrado na IDE do Delphi.

Componente ExErrorDialog instalado na IDE do Delphi
Figura 2. Componente ExErrorDialog instalado na IDE do Delphi

Utilizando o ExErrorDialog

Vamos finalmente testar todas as funcionalidades do componente. Crie uma nova aplicação no Delphi e configure o formulário como mostra a Figura 3.

Utilizando o componente
Figura 3. Utilizando o componente

O código completo do componente e do exemplo está para download no endereço do artigo. Para testar, temos na Listagem 4, o código do botão Run-Time, onde utilizamos constantes (declaradas no formulário) para mostrar as mensagens de erros ao usuário (propriedade ErrorMsgUser).

 procedure TForm1.Button1Click(Sender: TObject);
 begin
  try
  { Forçando o Erro }
  Button1.Tag := StrToInt('X');
  except
  { Seta as informaçõe do erro }
  with ExErrorDialog1 do
  begin
  ErrorType := etRunTime;
  ErrorStatus := esNormal;
  ErrorClass := 'TButton';
  ErrorMethod := 'Tag';
  ErrorSource := 'cmdRunTimeClick';
  ErrorMsgUser := ErrConvert; <- constante
  ErrorMsgSys := 'X is not a valid integer value.';
  ShowErrorMsg;
  end;
  end;
 end;
Listagem 4. Utilizando o componente de Erro

No exemplo, um erro do tipo fatal pode causar o fechamento do seu programa com uma mensagem elegante (Figura 4), podemos ainda mostrar o último erro ocorrido sem nenhum esforço e ainda tratar mensagens de erro no evento OnBeforeShow. Podemos chamar rotinas passando nosso TErrorInfo como parâmetro e verificar o código retornado na propriedade ErrorCode, o que torna fácil o intercâmbio de informações de erro.

Componente em execução
Figura 4. Componente em execução

Aconselho a colocar apenas um componente em seu DataModule e utilizá-lo em todo o seu programa, tornando fácil e prático a exibição de uma mensagem de erro. Podemos chamar funções que já preenchem as propriedades de erro e depois avaliar se mostramos o erro ou não. Os erros de lógica, como por exemplo, pode alertar programadores sobre valores inválidos em suas chamadas de funções e rotinas.

Outro aspecto que devemos destacar é a necessidade que alguns sistemas tem de fazer um log de erro, com o evento AfterShow você poderá chamar sua rotina de log e passar um TErrorInfo para ser gravado em um arquivo.

Se o leitor gostar dessa brincadeira poderá estender o componente e adicionar as funcionalidades de log embutidas no componente e até uma opção de envio de e-mail.

Conclusões

Utilizando corretamente esse componente você será capaz de identificar as seguintes informações sobre o erro: em qual função ocorreu o erro, por que, qual componente foi o responsável pelo erro, qual medida a ser tomada, qual o código, quem disparou a função. Isso reduz seu tempo de manutenção praticamente em 50% pois agora você já sabe tudo sobre o erro e basta somente corrigi-lo e não procurá-lo.