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:
- A mensagem está corretamente formatada, de modo que possa ser lida pelo usuário?
- O usuário deve, ou merece, ler a mesma mensagem N vezes?
- Não seria mais coeso tratar a exceção apenas nos limites da aplicação (boundary layers, e.g: fachada de serviços, camada de visualização ou nos repositórios de dados) ?
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.
Ó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.
Parabéns, Muito bom o Post.
Esse link: http://www.tkachenko.com/blog/archives/000352.html
Mudou minha vida, não sabia disso haha.
Abraços!