Programadores e Exceptions: relação entre tapas e beijos?


Tratar exceções é fácil.

Tão fácil que muitos programadores deixam passar batido ou não têm cautela ao fazer isso.

Aliás, cautela é a palavra chave aqui: ou você é cauteloso ou sai duplicando logs, piorando a responsividade do sistema, dificultando futuras depurações, “engolindo” erros importantes ou gerando muitos side-effects (o que acaba sendo bem comum e traumático).

Como quase tudo, não se trata apenas de frameworks, application blocks ou camadas de infra-estrutura recheadas de configurações e políticas de exception handling. O lance está muito mais nos fatores humanos (as vezes psicológicos): nas boas práticas de quem escreve o código.

E foi pensando nisso que algumas pessoas escreveram sobre esse assunto, inclusive eu.

Primeiro erro, o mais clássico e tentador: silenciar as exceções.

DON’T

 try
 {
     this.FacaAlgoPerigoso();
 }
 catch { }

Há inclusive quem chame isso de silenciator anti-pattern. Se a exceção ocorre, é por algum motivo, e ele precisa ser notificado, ou no mínimo, logado. Logar em um flat file? banco de dados? Event Viewer? Resposta óbvia: depende da necessidade e do contexto. O importante é não deixar que a vida siga sem tratar a exceção.

Para falar a verdade, existem raríssimos casos em que colocar um bloco catch vazio vai lhe conduzir ao sucesso, e trabalhar com políticas de tratamento (e.g: System.ServiceModel.IErrorHandler) onde seu código pode entrar em recursão e gerar StackOverflowException ou OutOfMemoryException pode ser um deles.

Segundo erro: propagar exceções por descargo de consciência.

Se você não sabe o que fazer para tratar exceções, não trate-as.

DON’T

 try
 {
     this.FacaAlgoPerigoso();
 }
 catch (Exception)
 {
     throw;
 }

É feio, redundante, polui o código e não agrega valor ao resultado final.

Terceiro erro: tratar exceções por descargo de consciência.

Parecido com o erro anterior, porém mais crítico, pois pode afetar o resultado final. Imagine que em cada camada da sua aplicação n-tier, onde n > 10, você resolve logar a exceção ou mandar um SMS para o usuário com a mensagem de erro. Além de desperdiçar espaço para log, não parece ser algo user-friendly. Em vez de fazer isso, pergunte-se:

Para evitar este erro comum, além de responder as questões acima, lembre-se:

  • Se você não sabe o que fazer para tratar exceções, não trate-as.
  • D.R.Y (Don’t Repeat Yourself).

Quarto erro: não usar, ou usar errado hierarquia nas exceções.

Caso não possua, crie exceções extremamente específicas a trechos dentro do seu código que poderão gerar erros. Mas quando for tratá-las, nunca trate exceções mais genéricas antes.

DON’T

 IMeuProviderDeAutenticacao provider =
 this.container.Resolve<IMeuProviderDeAutenticacao>();
 try
 {
     bool result = provider.Autenticar(umUsuario,umaSenha);
 }
 catch (Exception)
 {
     // Tratar exceção
     throw;
 }
 catch (UsuarioInexistenteException)
 {
     // Tratar exceção
     throw;
 }
 catch (SenhaInvalidaException)
 {
     // Tratar exceção
     throw;
 }

Esse é um erro básico que muitas vezes não chega a ser provocado, pois não criamos hierarquia para as exceções.

Quinto erro: controlar fluxos de negócio em blocos catch.

Jamais controle fluxos com blocos catch. O código escrito em um catch deve exclusivamente tratar o erro ocorrido, e isso inclui apenas orquestrar toda infra-estrutura utilizada para que o erro seja registrado e devidamente exibido ao usuário. Exceptions não são if statements.

DON’T

 try
 {
     decimal valorFrete = meuPedido.CalcularFrete();
 }
 catch (CEPInvalidoException)
 {
     meuPedido.ResetarFrete();
     IPedidoService service = this.container.Resolve<IPedidoService>();
     service.CancelarPedido(meuPedido);
     OnPropertyChanged("ResetarCarrinho");
 }

Sexto erro: sobrepor informações de uma exceção (throw; vs throw ex; ).

Quase imperceptível. No .NET, toda vez que um código usa throw ex; a exceção tem seu stacktrace sobre-escrito e a linha inicial do erro passa a ser a que contém a cláusula throw ex;. Isso dificulta muito a depuração (para aqueles que não usam TDD 🙂), pois a sensação que temos é que a história não nos foi contada por completo, ou pelo menos alguns passos foram pulados.

Aprendi com meu colega de trabalho Tiago Dondé que a solução mais simples para isso é usar throw; :-). Existem inúmeros artigos falando desse truque, o mais sucinto e famoso – com um código bem fácil de entender – é o do Oleg Tkachenko.

Estes erros que cometemos ao tratar exceções, assim como muitos outros, já foram identificados e escritos em outros blogs e artigos. Eu apenas condensei os mais importantes por já ter tido experiências em que a boa prática (ou falta de) tenha influenciado bastante na performance do software e na produtividade em correção de bugs (principalmente).

Fica uma lista de referências:

http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx – Apesar de antigo e desatualizado, contém muitas dicas e code-samples para tratar exceções, principalmente em sistemas concorrentes.

http://www.codedwarf.com/cs.html – Pequena lista de convenções de C#, que inclui tratamento de exceções.

http://msdn.microsoft.com/en-us/library/seyhszts.aspx – Dicas do MSDN para tratamento de exceções, principalmente na customização de exceções.

O importante é que com cautela e um pouco de estudo, códigos que tratam exceções sejam eficazes e evitem futuros desastres. Evitem, mas não impeçam.

2 respostas em “Programadores e Exceptions: relação entre tapas e beijos?

  1. Ótimo post como sempre! Gostei mesmo da referência ao "silenciator pattern", infelizmente ainda muito utilizado por programadores menos avisados.Apenas para registro, muitas vezes já peguei alguns dos anti-patterns mencionados em códigos de produtos MS, por exemplo, o que comprova que são comuns e mesmo empresas que utilizam processos de controle de qualidade (questionáveis eu sei) estão sujeitas a cometerem estes erros.

Deixe um comentário