Aplicações Robustas

O tratamento de exceção é um mecanismo capaz de dar robustez a uma aplicação, permitindo que os erros sejam manipulados de uma maneira consistente e fazendo com que a aplicação possa se recuperar de erros, se possível, ou finalizar a execução quando necessário, sem perda de dados ou recursos.

Para que uma aplicação seja segura, seu código necessita reconhecer uma exceção quando ela ocorrer e responder adequadamente a esta. Se não houver tratamento consistente para uma exceção, será exibida uma mensagem padrão descrevendo o erro e todos os processamentos pendentes não serão executados. Uma exceção deve ser respondida sempre que houver perigo de perda de dados ou de recursos do sistema.

No exemplo acima, poderia-se clicar no botão OK e continuar a executar a aplicação. Mas muitas vezes o erro ocorrido impede a continuação da operação, ou leva a perda de informações valiosas.

O Ideal seria que se pudesse tratar estes erros ocorridos, evitando a perda de dados ou a necessidade de encerrar a aplicação. Além de tratar o erro, a rotina de tratamento de erros poderia enviar ao usuário uma mensagem em português , mais significativa. A forma mais simples para responder a uma exceção é garantir que algum código limpo é executado. Este tipo de resposta não corrige o erro, mas garante que sua aplicação não termine de forma instável. Normalmente, usa-se este tipo de resposta para garantir a liberação de recursos alocados, mesmo que ocorra um erro. O tratamento mais simples seria uma simples mensagem ao usuário com a proposta dele tentar novamente a execução da operação que tenha causado o erro, conforme podemos ver no exemplo abaixo:

Outra forma de se responder a uma exceção é tratando o erro , ou seja, oferecendo uma resposta específica para um erro específico. Ao tratar o erro destrui-se a exceção , permitindo que a aplicação continue a rodar, maas o mais ideal mesmo é que além da mensagem, coloque os procedimentos devidos que sua aplicação deverá fazer caso ocorra uma exceção. Um exemplo disto é uma operação de gravação em uma tabela, caso ocorra um erro, ela deverá ser cancelada.

Existe também a prática da geração de logs que gravam no disco, um arquivo texto contendo todos os dados possíveis do erro ocorrido de forma que possa chegar a causa o mais rapido possível.

Exceções

Exceções são classes definidas pelo Delphi para o tratamento de erros. Quando uma exceção é criada, todos os procedimentos pendentes são cancelados e, geralmente é mostrada uma mensagem de erro para o usuário. As mensagens padrão nem sempre são claras, por isso é indicado criar seus próprios blocos protegidos.

Blocos Protegidos

Bloco protegido é um área em seu código que é encapsulada em uma instrução que ao invés de executar a linha de código ele tentará executá-la. Se conseguir beleza, prossegue com o programa, se não conseguir então ele cria uma resposta a este insucesso em um bloco de código  resguardando de erros que podem parar a aplicação ou perder dados. Um bloco protegido começa com a palavra reservada try e termina com a palavra reservada end. Entre essas palavras determina-se os comandos protegidos e a forma de reação aos erros.

Quando se define um bloco protegido, especifica-se respostas a exceções que podem ocorrer dentro deste bloco. Se a exceção ocorrer, o fluxo do programa pula para a resposta definida, e após executá-la, abandona o bloco.

Um bloco protegido é um grupo de comandos com uma seção de tratamento de exceções.

try
   A := StrToFloat(Edtit.Text);
   B := StrToFloat(Edtit.Text);
   ShowMessage(Format('%f / %f = %f', [A, B, A + B]));
except
   ShowMessage('Números inválidos.');
end;

A aplicação irá executar os comandos na parte except somente se ocorrer um erro. Se na parte try chamar uma rotina que não trata erros, e um erro ocorrer, ao voltar para este bloco a parte except será executada. Uma vez que a aplicação localiza um tratamento para a exceção ocorrida, os comandos são executados, e o objeto exceção é destruído. A execução continua até o fim do bloco. Dentro da parte except define-se um código a ser executado para manipular tipos específicos de exceção.

Algumas vezes você pode precisar especificar quais exceções quer tratar, como mostrado abaixo.

try
   try
   Soma := StrToFloat(EdtSoma.Text);
   NumAlunos := StrToInt(EdtNum.Text);
   ShowMessage(Format('Média igual a %f.', [Soma / NumAlunos]));
except
   on EConvertError do
     ShowMessage('Valor inválido para soma ou número de alunos.');
   on EZeroDivide do
     ShowMessage('O número de alunos tem que ser maior que zero.');
   else
     ShowMessage('Erro na operação, verifique os valores digitados.');
end;

Principais Exceções

O Delphi define muitas exceções, para cada erro existe uma exceção correspondente.

Classe Descrição
Exception Exceção genérica, usada apenas como ancestral de todas as outras exceções
EAbort Exceção silenciosa, pode ser gerada pelo procedimento Abort e não mostra nenhuma mensagem
EAccessViolation Acesso inválido à memória, geralmente ocorre com objetos não inicializados
EConvertError Erro de conversão de tipos
EDivByZero Divisão de inteiro por zero
EInOutError Erro de Entrada ou Saída reportado pelo sistema operacional
EIntOverFlow Resultado de um cálculo inteiro excedeu o limite
EInvalidCast TypeCast inválido com o operador as
EInvalidOp Operação inválida com número de ponto flutuante
EOutOfMemory Memória insuficiente
EOverflow Resultado de um cálculo com número real excedeu o limite
ERangeError Valor excede o limite do tipo inteiro ao qual foi atribuída
EUnderflow Resultado de um cálculo com número real é menor que a faixa válida
EVariantError Erro em operação com variant
EZeroDivide Divisão de real por zero
EDatabaseError Erro genérico de banco de dados, geralmente não é usado diretamente
EDBEngineError Erro da BDE, descende de EDatabaseError e traz dados que podem identificar o erro

Blocos de Finalização

Blocos de finalização são executados sempre, haja ou não uma exceção. Geralmente os blocos de finalização são usados para liberar recursos. Entre estes recursos estão : arquivos, memória, recursos do windows, objetos.

FrmSobre := TFrmSobre.Create(Application);
try
  FrmSobre.Img.LoadFromFile('Delphi.bmp');
  FrmSobre.ShowModal;
finally
  FrmSobre.Release;
end;

Você pode usar blocos de proteção e finalização aninhados

FrmOptions := TFrmOptions.Create(Application);
try
  FrmOptions.ShowModal;
  try
    Tbl.Edit;
    TblValor.AsString := EdtValor.Text;
  except
    on EDBEngineError do
      ShowMessage('Alteração não permitida.');
    on EConvertError do
      ShowMessage('Valor inválido.');
  end;
finally
   FrmOptions.Release;
end;

A aplicação sempre executará os comandos inseridos na parte finally do bloco, mesmo que uma exceção ocorra. Quando um erro ocorre no bloco protegido, o programa pula para a parte finally, chamada de código limpo, que é executado. Mesmo que não ocorra um erro, estes comandos são executados.

No código a seguir, foi alocada memória e gerado um erro, ao tentar-se a divisão por 0 (zero). Apesar do erro, o programa libera a memória alocada:

Procedure Tform1.Button1click (Sender : Tcomponent );
Var
  Ponteiro : Pointer;
  Inteiro, Dividendo : Integer;
Begin
  Dividendo:= 0;
  GetMem(Ponteiro, 1024);
  Try
    Inteiro: = 10 div dividendo;
  Finally
    FreeMem(Ponteiro, 1024);
  End;
End;

Geração de Exceções

Você pode provocar uma exceção usando a cláusula raise.

raise EDatabaseError.Create('Erro ao alterar registro.');

Também é possível criar seus próprios tipos de exceções.

type
 EInvalidUser = class (Exception);

raise EInvalidUser.Create('Você não tem acesso a essa operação.');

Se você quiser que uma exceção continue ativa, mesmo depois de tratada, use a cláusula raise dentro do bloco de tratamento da exceção. Geralmente isso é feito com exceções aninhadas.


try
  Tbl.Edit;
  TblContador.Value := TblContador.Value + 1;
  Tbl.Post;
except
  ShowMessage(''Erro ao alterar contador.');
  raise;
end;

Erros de Bancos de Dados

A exceção EDBEngineError permite a identificação de erros de bancos de dados gerados pela BDE.


try
  TblCli.Post;
except
  on E:EDBEngineError do
    if E.Errors[0].ErrorCode = DBIERR_KEYVIOL then
      ShowMessage('Cliente já cadastrado.');
end;

Note que a variável E, que vai identificar o erro, só precisa ser declarada no bloco de tratamento da exceção. No help você pode consultar outras propriedades de EDBEngineError que podem ser importantes.

Você também pode usar os eventos de erro do componente Table, sem precisar de blocos de tratamento.

procedure TFrmCadCli.TblCliPostError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
begin
if(E is EDBEngineError) then
   with EDBEngineError(E) do
     case Errors[0].ErrorCode of
       DBIERR_KEYVIOL: ShowMessage('Cliente já cadastrado.');
       DBIERR_REQDERR: ShowMessage('Campo obrigatório não preenchido.');
     end;
else
  ShowMessage('Erro no banco de dados:' + #13#13 + E.Message);
Action := daAbort;
end;

Alguns códigos de erro da BDE estão listados abaixo. Todas as constantes e funções relacionadas à API da BDE no Delphi 3 estão na Unit BDE, que deve ser adicionada à cláusula uses. No BDE API Help você pode encontrar referência sobre as funções nativas da BDE, como também alguns exemplos em Delphi.

Constante Descrição
DBIERR_KEYVIOL Violação de chave primária
DBIERR_MAXVALERR Valor máximo excedido
DBIERR_FORIEGNKEYERR Erro de chave externa, como em integridade referencial
DBIERR_LOCKED Registro travado
DBIERR_FILELOCKED Arquivo travado
DBIERR_NETMULTIPLE Mais de um diretório usado como NetFileDir
DBIERR_MINVALERR Campo com valor mais baixo que valor mínimo
DBIERR_REQDERR Campo obrigatório faltando
DBIERR_LOOKUPTABLEERR Erro em tabela Lookup

Se você quiser mais informações a respeito do erro pode usar o procedimento DBIGetErrorContext, como na função mostrada abaixo que retorna determinadas informações sobre o erro.

function GetErrorInfo(Context: SmallInt): string;
begin
SetLength(Result,DBIMAXMSGLEN + 1);
try
   DbiGetErrorContext(Context,PChar(Result));
   SetLength(Result,StrLen(PChar(Result)));
except
   Result := '';
end;
end;

No evento OnEditError, usado no exemplo abaixo, se ocorrer um erro ao tentar alterar um registro, podemos identificar o usuário da rede que está alterando esse registro usando a função criada anteriormente.

if Pos('locked', E.Message) > 0 then
  ShowMessage('Usuário ''' + GetErrorInfo(ecUSERNAME) + ''' está alterando o registro.');

Note que foi usada uma outra técnica de identificação do erro, usando a própria mensagem de erro e não o código, como mostrado anteriormente. Você pode usar a função criada acima mandando como parâmetro os valores mostrados abaixo, que podem ser encontrados no help da BDE.

Constante Descrição
ecTABLENAME Nome da Tabela
ecFIELDNAME Nome do campo
ecUSERNAME Nome do usuário, muito usado para identificar qual usuário travou o registro
ecFILENAME Nome do arquivo
ecINDEXNAME Nome do índice
ecDIRNAME Pasta
ecKEYNAME Chave primária
ecALIAS Alias
ecDRIVENAME Drive
ecNATIVECODE Código de erro nativo
ecNATIVEMSG Mensagem de erro nativa
ecLINENUMBER Número da linha, usado em instruções SQL

Para desenvolver um sistema genérico de tratamento de erros, considere a opção de criar esse tratamento em um DataModule genérico para ser usado como ancestral por todos os DataModules do sistema, utilizando a herança visual.

Se o único problema for traduzir as mensagens, localize os arquivos CONSTS.INT e DBCONSTS.INT e crie uma nova Unit de definição de strings com uma estrutura semelhante a mostrada abaixo e juntando todas as definições das constantes das duas Units devidamente traduzidas. Depois, basta usar essa Unit em seus projetos que as novas mensagens irão sobrepor as anteriores.

unit NewConsts;

interface

resourcestring

SAssignError = 'Não é possível atribuir %s a %s';
SFCreateError = 'Não é possível criar arquivo %s';
SFOpenError = 'Não é possível abrir arquivo %s';
SInvalidFieldSize = 'Tamanho de campo inválido';
SInvalidFieldRegistration = 'Registro de campo inválido';
SUnknownFieldType = 'Campo ''%s'' tem um tipo desconhecido';

implementation

end.

Uma outra opção seria criar um método para o evento OnException do objeto Application, esse método seria chamado sempre que houvesse uma exceção em qualquer parte do sistema.

Exceções da RTL

Quando se escreve código chama rotinas da biblioteca de run-time ( RTL, run-time library), como funções matemáticas ou de manipulação de arquivos, os erros aparecem na forma de exceções. Por padrão, a RTL manda uma mensagem para o usuário, mas pode-se tratar estes erros de outra forma.

As exceções geradas pela RTL são definidas na unit SysUtils, e todas descendem da classe mais geral Exception, que provê a string que aparece no quando de mensagem da exceção.

Há sete tipos de exceções geradas pela RTL :

- Exceções de entrada e saída;
- Exceções de memória heap;
- Exceções matemáticas envolvendo inteiros;
- Exceções matemáticas envolvendo pontos flutuantes;
- Exceções de typecast;
- Exceções de conversão;
- Exceções de Hardware;

Observa-se estas exceções com mais detalhes através do OBJECTBROWSER, ativado através do comando View/Browser:

Exceções de Entrada e Saída

Ocorrem quando RTL tenta acessar arquivos ou dispositivos de entrada e saída.

A Unit SysUtils define uma exceção genérica de entrada e saída chama EinOutError, que contém um atributo chamado ErrorCode que indica o erro ocorrido.

Exceções de Memória

Ocorrem quando se tenta alocar ou acessar memória dinâmica. São definidos dois tipos de exceção : EoutofMemory (indica que não há memória suficiente) e EinvalidePointer (Ponteiro Inválido).

Exceções Matemáticas para Inteiros

Ocorrem quando se realiza operações com números inteiros. A Unit SysUtils define uma exceção genérica chamada EintError, com três tipos de exceção derivadas : EdividByZero (Divisão por zero), ErangeError (Número fora da região), e EintOverFlow (Estouro na operação com inteiro).

Exceções de Pontos Flutuantes

Ocorrem quando se realiza operações com dados do tipo real. A Unit bSysUtils define uma exceção genérica chamada EMathError, com as seguintes exceções derivadas : EinvalidOp ( Processador encontrou uma instrução indefinida), EZeroDivide (Divisão por zero), EOverFlow, EUnderFlow.

Exceções de TypeCast

Ocorrem quando você tenta atribuir um objeto a um tipo inválido usando o operados as. Gera-se a exceção EinvalidCast quando isto ocorre.

Exceções de Conversão

Ocorrem quando se convertem dados de um tipo para outro. A Unit SysUtils define uma exceção chamada EconvertError quando isto ocorre, avisando que a conversão não pode ser feita.

Exceções de Hardware

Podem ocorrer em dois tipos de situação: quando o processador detecta uma falha, ou quando a aplicação gera uma interrupção intencional. A Unit SysUtils define uma exceção genérica chamada EprocessorException, com as exceções derivada: Efault (Exceção Base), EGPFault (Falha Geral de Proteção), EstackedFault (acesso ilegal ao seguimento stack do processador), EpageFault (o gerenciador de memória do windows não está conseguindo realizar o arquivo de swap), EinvalidOpCode (o processador encontra uma instrução indefinida), EbreakPoint (a aplicação gerou uma interrupção breakpoint), Esinglestep (a aplicação gera uma interrupção single-step).

Exceções da VCL

Os componentes da VCL (Visual Component Library) também geram exceções diversas relacionadas com cada controle em particular. Como a VCL é constituída de componentes que nada mais são do que classes, a exceção mais comun nos VCL's é a famosa Access Violation, que nada mais é do que uma exceção ocorrida quando a sua aplicação tenta acessar ou usar o objeto inválido. Mas este "objeto inválido" pode ser muitas coisas:

Classe inválida

Ocorre quando você destroi um objeto, mas no fluxo em sua aplicação ele será acessado posteriormente. Ai ocorre o erro que costuma abortar a aplicação.

Ponteiro inválido

Ocorre quando você cria uma variável do tipo ponteiro e que está apontando para um endereço referente à uma área protegida da memoria ou um endereço inválido. Esta invalidez do endereço pode ser causada pelo fato de outra aplicação estar usando-o ou então pelo fato dele não existir mesmo.

A mensagem de erro da Access Violation é mais detalhada pois além de trazer a mensagem ainda traz tanto o endereço lido como o endereço onde ocorreu a falha, como podemos ver na figura abaixo:

Convem salientar também que a VCL faz uso das Exceções da RTL. Sempre que uma exceção em um componente ocorrer e esta for catalogada na RTL ela automaticamente será chamada.

Você pode tratar as exceções da VCL da mesma forma que as demais exceções, e seu programa não precisa ser abortado para tal. Basta usar os blocos protegidos nos pontos críticos. Normalmente estes pontos costumam ser operações que envolvam mudanças em estados de componentes em Run-Time, ou mudança ou passagem de valores em variáveis do tipo ponteiro.

Ex.1 - Tratando uma mudança no estado de um componente em Run-Time

procedure TForm1.Button2Click(Sender: TObject);
 try
   Button1.Enabled := true;
 except
   on E:Exception do
     ShowMessage(E.Message]);
 end;
end;

Ex.2 - Tratando uma passagem de valor em um ponteiro

procedure TForm1.Button2Click(Sender: TObject);
var
  X, Y: Integer;
  P: ^Integer;
begin
  try
    X := 17;
    P := @X;
    Y := P^;
  except
    on E:Exception do
      ShowMessage(E.Message]);
  end;
end;

Exceções Silenciosas

Pode-se definir exceções que não mostrem um quadro de mensagem para o usuário quando aparecem. São chamadas exceções Sileciosas. O caminho mais curto para criar esta exceção é através da procedure Abort. Esta procedure automaticamente gera uma exceção do tipo Eabort, que abortará a operação sem mostrar uma mensagem.

O exemplo abaixo aborta a operação de inclusão de itens em um Listbox quando tentamos inserir o terceiro elemento:

Begin
For i := 1 to 10 do
Begin
  Listbox.Items.Add(Inttostr(i));
  If i = 3 then
    Abort;
End;
End;

Manipulando Exceções Globais

O Delphi oferece um evento chamado OnException, ligado à a classe Tapplication, que permite manipular qualquer exceção que ocorra em seu programa, mesmo que não sabendo em que parte do programa ela ocorreu. Inicialmente, deve-se criar manualmente, como um método de Tform1, a chamada para o método que tratará os erros :

Tform1 = Class (Tform)
Procedure Trata_Erros (Sender : Tobject ; E : Exception );
End;

Depois de declarar a procedure, deve-se atribuí-la ao evento OnException de Tapplication. A atribuição é feita no evento OnCreate do formulário principal :

Procedure Tform1.FormCreate ( Sender : Tobject );
Begin
Application.OnException := Trata_Erros;
End;

O método Trata_Erros será agora chamado quando qualquer exceção ocorrer. Pode-se determinar mensagens para erros específicos, ou mensagens gerais :

Procedure Tform1.Trata_Erros (Sender : Tobject; E : Exception);
Begin
If E is EdatabaseError then
    Showmessage('Erro no banco de dados');
Else
    Showmessage('Há erros no programa');
End;

Entendendo um pouco sobre o que é um GPF

A bem da verdade as exceções não tratadas ou mal tratadas em uma aplicação são as uma das causas do GPF (Falha geral de proteção) que ocorrem no Windows e que geralmente o derrubam. Como isto acontece? Se seu programa conseguir sobreviver à uma exceção sem que ele tenha caído (O que não é muito difícil em se tratando de exceções silenciosas), ele pode perder o seu endereçamento da memória. Daí começar a acessar uma área protegida, ou uma área que está sendo usada por outro programa, é questão de tempo. Um problema destes, normalmente acaba com uma mensagem de erro do Windows que fecharia tanto este aplicativo como os outros envolvidos no problema e em seguida, uma simples reinicialização da máquina volta ao normal. Mas podem ocorrer problemas mais sérios como uma colizão com a FAT, o que resultaria na perda de todo ou parte do conteúdo do HD. Obviamente que um caso destes é muito raro de acontecer, mas que existe a probabilidade existe!

O GPF (General Protection Faults) é um evento de hardware, causado pela própria CPU. No Windows ocorrem basicamente por culpa dos aplicativos, que não possuem um tratamento consistente de suas exceções internas. Como consequência deste problema pode ocorrer os seguintes eventos:

- Tentativa, pela aplicação,de acessar memória fora da área determinada pelo sistema operacional;
- Tentativa de acessar um ponteiro inválido, podem fazer com que a aplicação acesse uma área protegida;
- Tentativa de acessar uma área de memória que está sendo usada por outro aplicativo ou biblioteca (dll);
- Tentativa de execução de dados, ao invés de código (programa);
- Tentativa de efetuar uma operação binária inválida (Não confunda com operação aritmética);
- Tentativa de acesso indevido ao hardware;

Conclusão:

Depois de ler este artigo, voce pode concluir que não deve, sob nenhuma circunstância, negligenciar o tratamento de exceções dentro de sua aplicação uma vez que que sempre que elas ocorrem, acabam afetando não só a esta referida aplicação mas como outras ainda. Um exemplo disto é o caso de uma aplicação que dá um lock em uma tabela no banco de dados e em seguida tentará abrí-la. Caso ocorra uma exceção que aborte o programa, a aplicação fecha sem que nada ocorra com o Windows. Mas a Tabela está lá no banco bloqueada e, conforme for o SGDB, nem um Restart ou na maquina ou no banco conseguirão desbloqueá-la, causando transtornos e perda de tempo tentando solucionar um problema que poderia ter sido evitado perfeitamente. Outro exemplo disto, é o caso de exceções silenciosas que foram criadas e consistidas incorretamente. Aí elas se tornarão uma mina enterrada dentro de seu executável. Uma exceção destas pode danificar um banco de dados inteiro ou uma partição do Disco Rígido, conforme a operação que se tentou fazer mas que não foi bem sucedida e foi mal consistida. Pior do que isto, sem que o usuário perceba o que está acontecendo.

Se o autor deste software tivesse olhado com mais atenção as possíveis exceções que esta aplicação poderia causar, talvez a incidência destes imprevistos seriam bem menores ou quase que nulos.



E-Mail: walterchagas@yahoo.com Clique aqui para cair fora desta pagina.