Exceptions: Evite o try-catch
Nesse artigo vou falar para evitar o uso do try-catch e try-catch-finally.
No artigo anterior sobre try-catch-finally, comentei sobre o tratamento de exceptions usando o try-catch ao invés de try-catch-finally.
Pense duas vezes antes de adicionar um bloco de catch para capturar o erro.
Na dúvida, evite o uso do try-catch.
O que é uma Exception?
Uma Exception corresponde a uma situação de exceção, que não deveria acontecer em situações normais. Veja esses exemplos:
- Chipset pegando fogo porque a máquina está operando no deserto
- Placa de memória em curto circuito por causa de uma chuva inundando o data center
- Sistema operacional escrevendo aleatoriamente no disco por conta de um ransomware
Esses foram exemplos exagerados de exceções que podem acontecer. Por que você faria o tratamento de erro? Afinal, o que você pode fazer em um bloco try-catch quando nem a CPU ou a memória RAM funciona direito?
Entretanto, existem situações de Exception que encontramos no dia a dia:
- Stack overflow
- Falta de memória
- Kernel panic
Novamente, por que fazer um try-catch? O ideal é deixar que o processo falhe completamente e seja reiniciado do zero. Esse é o chamado processo de fail fast: ao encontrar uma situação inesperada, o processo reinicia ao invés de tentar tratar o problema.
Portanto, evite o uso do try-catch e prefira o fail-fast.
Engolindo Exceptions
Não engula exceções. Em inglês seria “DO NOT swallow exceptions”.
No nosso código do ARDA, temos um problema reportado pelo @lucaslra:
Issue #4 - Hidden Exceptions
https://github.com/DXBrazil/Arda/issues/4
Imagine a situação de falta de memória ou algo mais crítico. “Engolir a exceção” e retornar valores nulos não vai ajudar em nada. Existem algumas opções para tratamento (ex: gravar em um log), mas minha sugestão é sempre evitar o try-catch.
Tratamento de Exceção
A correção para o problema reportado foi remover o try-catch. Veja o trecho original abaixo:
https://github.com/DXBrazil/Arda/blob/771695ec90b651098f43dd6296930682a285fd5f/src/Arda.Kanban/Controllers/ActivityController.cs
Esse código é ruim porque “engole a exception”: a requisição retorna o status HTTP 200 (sucesso) com conteúdo nulo (No content).
Compare com a versão sem try-catch, que simplifica a lógica:
https://github.com/DXBrazil/Arda/blob/2c9e79f84f1e00a3c75d5e4964bd8a28bc834318/src/Arda.Kanban/Controllers/ActivityController.cs
Se uma exceção acontecer, um midlleware do ASP.NET intercepta a Exception e transforma em uma resposta HTTP 500 (erro interno) . Acredito que o status HTTP 500 é mais adequado que o HTTP 200.
Usando Try-Catch em Console Application
Uma exception não tratada força o cancelamento da thread que, por sua vez, pode derrubar o processo inteiro. Portanto, existem casos que queremos evitar que uma exception cancele o processo inteiro.
Nesse caso, voce deve usar um try-catch no método estático Main do arquivo Program.cs.
Note que estou sendo bastante específico com relação ao try-catch: dentro do método Main do Program.cs. Em qualquer outra situação, recomendo que pense duas vezes.
Quando usar try-catch
Infelizmente ainda existem casos que precisam do try-catch: determinados trechos do código não devem disparar Exceptions e, por isso, devemos adicionar um bloco de tratamento de erro.
Um exemplo é o ARDA, que acessa dados do SQL Server e mantém um cache no Redis. Quando o serviço do RedisCache fica indisponível, a requisição deve ignorar o cache e consultar diretamente ao banco de dados. No nosso caso, implementamos um try-catch especificando a captura somente da exceção RedisConnectionException.
https://github.com/DXBrazil/Arda/blob/0dcc8f283d52e85d3ba47f3529ffb0fa9f06c93b/src/Arda.Permissions/Repositories/PermissionRepository.cs
Nesse caso, usamos o try-catch para evitar que a falha do Cache impacte na disponibilidade do serviço. O mais importante é que somos bastante específico com relação ao tipo de exceção, pois não é a genérica System.Exception.
Usando Try-Catch no ASP.NET
Se você usa o ASP.NET, então não há motivo para usar try-catch porque o middleware faz esse trabalho por você. Observamos isso ao longo do trabalho com o ARDA, quando limpamos os try-catch do código.
https://github.com/DXBrazil/Arda/issues/4
Entretanto, veja que ainda há casos importantes de try-catch que devem ser mantidos.
Na grande maioria dos casos, usamos try-catch com exceções específicas.
Check transient errors (RedisConnectionException)
https://github.com/DXBrazil/Arda/commit/b10a4fcf9f68001b7f2f2f92974ad3b4802a3d71Removed try/catch from VerifyUserAccessToResource exception
https://github.com/DXBrazil/Arda/commit/229acae260ce727a2cf7f9aa4eb389c73b00dbaaCatch RedisConnectionException thrown in GetUserMenuSerialized
https://github.com/DXBrazil/Arda/commit/b3eb3c2c2a40d0aff3352b15278528ff62aedfe9Remove generic exceptions from GetUserPhoto
https://github.com/DXBrazil/Arda/commit/d4555d22021dc05a18ca0f6a222293498a6fb863Remove unused function CallMicrosoftGraph
https://github.com/DXBrazil/Arda/commit/84b1086a0f828e004fbae94496e87e059da635eb
Conclusão
Nesse artigo, falei que minhas regras com relação ao try-catch são:
- Na dúvida, evite o uso do try-catch
- Use no Program.cs para evitar o crash do processo
- Se for necessário tratar a exceção, especifique o tipo de exceção
Na dúvida, pense no fail-fast e evite “engolir exceptions”.