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.