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.
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:
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:
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:
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:
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:
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:
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:
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
:
12type EMinhaExcecao = class(Exception);
Para utilizá-las, basta chamar o raise
:
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:
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:
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:
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.