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.
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;
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.
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 |
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;
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.
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.
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;
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.
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.