Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Atualmente fala-se muito sobre tratamento de exceções no desenvolvimento de software em geral, independente da linguagem de programação. Estes tratamentos são extremamente úteis para controlar o fluxo de execução do aplicativo ou do script que está sendo ou foi desenvolvido e tratar quando algum erro ocorre, bem como servir como um bom recurso de rastreabilidade.

Porém, em muitos casos, os tratamentos de exceções não são elaborados e utilizados como supostamente deveriam ser. Abaixo algumas premissas relacionadas à exceções no código e algumas dicas para empregar o tratamento.

Page Tree
root@self
expandCollapseAlltrue
spacesDPU
startDepth1
searchBoxtrue

1 - Escreva o esqueleto do bloco antes de codificar

Em primeiro lugar, uma dica importante: quando você iniciar um bloco de tratamento de exceções, escreva todo a estrutura do tratamento antes de continuar a codificação. Essa dica é ainda mais importante para instruções finally, já que nos impede de esquecer de liberar um objeto da memória. Por exemplo, caso seja necessário instanciar um objeto em um método, a estrutura pode ser escrita dessa forma:

Code Block

12345678910var  Objeto: TClasse;begin  Objeto := TClasse.Create;  try   finally    FreeAndNil(Objeto);  end;end;

Somente depois de escrever essa estrutura que a codificação dentro do try deve ser iniciada. Da mesma forma, se um objeto da classe estiver sendo instanciado no evento OnCreate de um formulário, o código para destruí-lo deve ser imediatamente escrito no evento OnDestroy. Se isso não for feito, haverá Memory Leaks na aplicação, comprometendo o desempenho e aumentando a probabilidade de erros consecutivos, também conhecidos como “efeito cascata”.

Use o except exclusivamente para tratamento de exceções

Em segundo lugar, é preciso entender que o except não deve ser empregado como uma condição If. Alguns desenvolvedores pensam da seguinte forma: “Vou tentar executar o plano A (try), e se não der certo, executo o plano B (except)”. Isso não existe. O tratamento de exceções, como o próprio nome diz, deve ser utilizado apenas para exceções, e não para regras de negócio.

Como exemplo, considere o código:

Code Block

12345try  ConsultarPorCodigo;except  ConsultarPorDescricao;end;

O bloco try/except está sendo incorretamente utilizado como uma condição para atender uma funcionalidade. Este código deve ser imeidatamente corrigido com uma condição If, utilizando, por exemplo, um tipo enumerado, dispensando o except de tratar regras de negócio:

Code Block

12345678try  if TipoConsulta = tcCodigo then    ConsultarPorCodigo  else if TipoConsulta = tcDescricao then    ConsultarPorDescricao;except  ShowMessage('Erro ao realizar a consulta.');end;

Faça uso da propriedade Message da classe Exception

A terceira recomendação é utilizar a propriedade Message da classe Exception para apresentar o erro técnico na tela. Claro, o usuário provavelmente não entenderá a descrição da mensagem, mas, quando reportada, será muito útil para ajudar a equipe de suporte, ou até mesmo os desenvolvedores, a identificar a origem do erro. No código abaixo, a mensagem de erro é capturada e exibida na tela:

Code Block

123456789try  StrToInt('A');except  On E: Exception do    ShowMessage(      'Ocorreu um erro.' + #13 +      'Por favor, entre em contato com o administrador do sistema.' + #13 +      'Mensagem de erro: ' + E.Message);end;

A mensagem de erro será: ‘A’ is not a valid integer value. Essa descrição servirá de orientação para identificar quando ou como o erro foi produzido, agilizando o processo de manutenção.

Por outro lado, para evitar o “estouro” de erros técnicos na tela do usuário, outra alternativa é gravar a mensagem de erro em um arquivo de log, na qual considero bastante viável. Neste caso, basta substituir a última linha da mensagem de erro por um comando que acesse um arquivo de texto e grave a descrição:

Code Block

123456789101112try  StrToInt('A');except  On E: Exception do  begin    ShowMessage(      'Ocorreu um erro.' + #13 +      'Por favor, entre em contato com o administrador do sistema.');     GravarErroNoArquivoDeLog(E.Message);  end;end;

O método GravarErroNoArquivoDeLog, por sua vez, pode executar a seguinte operação:

Code Block

123456789101112131415161718192021procedure GravarErroNoArquivoDeLog(const sMensagem: string);var  sCaminhoLogErros: string;  ArquivoLog: TextFile;begin  sCaminhoLogErros := 'C:\LogErros.txt';  AssignFile(ArquivoLog, sCaminhoLogErros);   // se o arquivo já existir, será aberto para modificação  // caso contrário, o arquivo será criado  if FileExists(sCaminhoLogErros) then    Append(ArquivoLog)  else    Rewrite(ArquivoLog);   WriteLn(ArquivoLog, 'Data: ' + DateTimeToStr(Now)); // escreve a data e hora  WriteLn(ArquivoLog, 'Mensagem: ' + sMensagem); // escreve a mensagem  WriteLn(ArquivoLog, EmptyStr); // pula uma linha   CloseFile(ArquivoLog);end;

Legal, não é?

Exceções personalizadas

Prosseguindo, uma ótima dica para melhorar o tratamento de exceções é não utilizar exceções genéricas. Por exemplo, considere o tratamento abaixo:

Code Block

12345678try  StrToInt(VariavelX); // gera uma exceção  GravarCliente;  StrToInt(VariavelY); // gera uma exceçãoexcept  On E: Exception do    ShowMessage(E.Message);end;

Embora a mensagem de erro seja bem explicativa, não saberemos exatamente se a exceção ocorreu no primeiro ou no segundo ponto do código, logo, também não saberemos se o método GravarCliente foi executado. Isso acontece porque estamos utilizando a classe genérica de exceções, ou seja, Exception, que não nos permite identificar o ponto em que a exceção ocorreu.

Além disso, não conseguimos aplicar tratamentos específicos se o erro for genérico. Para melhorar a compreensão, suponha que no except há um método para limpar alguns campos da tela caso a exceção tenha sido causada por uma conversão de dados inválida. Mas, e se ocorrer outro tipo de exceção? Imagine, por exemplo, que ocorra uma exceção ao gravar um registro no DataSet. O fluxo irá para o except e executar o método que limpa os campos, indevidamente.

O ideal é criar exceções personalizadas, principalmente para tratar regras de negócio. No Delphi, é possível elaborar esses tipos de exceções ao criar heranças da classe Exception:

Code Block

12type  EMinhaExcecao = class(Exception);

Para utilizá-las, basta chamar o raise:

Code Block

1raise EMinhaExcecao.Create('Mensagem da classe EMinhaExcecao.');

A principal vantagem dessa abordagem é separar diferentes exceções que podem ocorrer dentro de um bloco try e coordenar o fluxo de acordo com o tipo da exceção. Como exemplo, considere um determinado método que pode gerar três tipos de exceções. Se utilizarmos exceções personalizadas, observe como o tratamento fica bem mais claro:

Code Block

123456789101112131415161718192021222324252627282930313233343536373839type  ECamposObrigatorios = class(Exception);  EErroConexao = class(Exception);  EErroGravacao = class(Exception);  { ... }  procedure BotaoGravarClick;begin  try    if not ValidarCamposObrigatorios then      raise ECamposObrigatorios.Create('Campos obrigatórios não preenchidos.');         if not ConectarBancoDeDados then      raise EErroConexao.Create('Erro ao conectar ao banco de dados.');          if not GravarCliente then      raise EErroGravacao.Create('Erro ao gravar os dados.');  except    On E: ECamposObrigatorios do    begin      ShowMessage(E.Message);      DestacarCamposObrigatorios; // tratamento personalizado    end;     On E: EErroConexao do    begin      ShowMessage(E.Message);      CancelarGravacao; // tratamento personalizado      ReconectarBancoDeDados; // tratamento personalizado    end;     On E: EErroGravacao do    begin      ShowMessage(E.Message);      Transacao.RollBack; // tratamento personalizado      GravarErroNoLog(E.Message); // tratamento personalizado    end;  end;end;

Conforme o tipo de exceção, realizamos o tratamento adequado, o que não seria possível com exceções genéricas. Além disso, caso ocorra uma exceção para o usuário em ambiente de produção, a rastreabilidade será mais rápida!

Wrappers

Se o tamanho do código no except for um problema, existe uma técnica conhecida como Wrapper, que consiste em encapsular o tratamento de exceção dentro de um método separado. No cenário do exemplo acima, o Wrapper ficaria dessa forma:

Code Block

1234567891011121314151617181920212223242526private  procedure TratarExcecao(Excecao: Exception); // declaração do wrapper  ... procedure TratarExcecao(Excecao: Exception);begin  if Excecao is ECamposObrigatorios then  begin    ShowMessage(Excecao.Message);    DestacarCamposObrigatorios; // tratamento personalizado  end;   if Excecao is EErroConexao then  begin    ShowMessage(Excecao.Message);    CancelarGravacao; // tratamento personalizado    ReconectarBancoDeDados; // tratamento personalizado  end;   if Excecao is EErroGravacao then  begin    ShowMessage(Excecao.Message);    Transacao.RollBack; // tratamento personalizado    GravarErroNoLog(Excecao.Message); // tratamento personalizado  end;end;

Logo, o código inicial seria reduzido:

Code Block

12345678910111213141516procedure BotaoGravarClick;begin  try    if not ValidarCamposObrigatorios then      raise ECamposObrigatorios.Create('Campos obrigatórios não preenchidos.');         if not ConectarBancoDeDados then      raise EErroConexao.Create('Erro ao conectar ao banco de dados.');          if not GravarCliente then      raise EErroGravacao.Create('Erro ao gravar os dados.');  except    On E: Exception do      TratarExcecao(E);  end;end

Observe que, neste caso, a classe Exception foi utilizada para capturar a exceção de forma geral, mas é tratada dentro do Wrapper empregando RTTI. A vantagem do Wrapper, além do encapsulamento, é a possibilidade de utilizá-lo em vários pontos do software que compartilham as mesmas regras de exceção, como, por exemplo, a exceção de campos obrigatórios, que pode ser aplicada em telas distintas.

Antes de fechar o artigo, mais uma dica rápida. O componente TApplicationEvents traz o evento OnException que faz justamente a função do Wrapper. O benefício deste componente é capturar todas as exceções que ocorrem dentro do software, independente da tela que estiver aberta.