ppersWraMuito se fala sobre tratamento de exceções no desenvolvimento de software. Estes tratamentos são extremamente úteis para controlar a fluxo de execução do aplicativo 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. Por esse motivo, o artigo de hoje apresenta algumas premissas relacionadas à exceções no código e algumas dicas para empregar o tratamento.
Antes de iniciar o artigo, vale realçar que a palavra reservada except
do Delphi que utilizarei nos exemplos é o mesmo que o catch
de outras linguagens, como Java e C#, portanto, a ideia é a mesma.
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:
1 2 3 4 5 6 7 8 9 10 |
var 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:
1 2 3 4 5 |
try 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:
1 2 3 4 5 6 7 8 |
try 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:
1 2 3 4 5 6 7 8 9 |
try 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:
1 2 3 4 5 6 7 8 9 10 11 12 |
try 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure 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:
1 2 3 4 5 6 7 8 |
try StrToInt(VariavelX); // gera uma exceção GravarCliente; StrToInt(VariavelY); // gera uma exceção except 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
:
1 2 |
type EMinhaExcecao = class(Exception); |
Para utilizá-las, basta chamar o raise
:
1 |
raise 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
type 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
private 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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: 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.
Bom, pessoal, por hoje é só! Espero que as dicas acima tenham sido produtivas!
Há também um artigo aqui no blog sobre como criar uma rotina básica de captura de exceções. Confira!
Até a próxima semana!
Excelente artigo, muito bem explicado… Eu já usava try..except da maneira adequada mas não sabia do E: Exception e da propriedade Message, bem útil. Legal;
Legal, Gabriel! Fico contente que o artigo tenha lhe trazido conhecimento!
Abraço!
Muito bom o artigo, esclarece bem o conceito de exceções! Acompanho o blog há algum tempo e tem sido muito útil no meu dia-a-dia. Abraços!
Olá, Luiz Paulo! Fico muito contente em saber que está acompanhando o blog!
Obrigado pela motivação! Abraço!
Artigo excelente, André!
Muito didático e interessante.
Parabéns!