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

image

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:

imagehttps://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:

image
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.

image

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.

image
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.

image
https://github.com/DXBrazil/Arda/issues/4

Entretanto, veja que ainda há casos importantes de try-catch que devem ser mantidos.

image

Na grande maioria dos casos, usamos try-catch com exceções específicas.

Check transient errors (RedisConnectionException)
https://github.com/DXBrazil/Arda/commit/b10a4fcf9f68001b7f2f2f92974ad3b4802a3d71

Removed try/catch from VerifyUserAccessToResource exception
https://github.com/DXBrazil/Arda/commit/229acae260ce727a2cf7f9aa4eb389c73b00dbaa

Catch RedisConnectionException thrown in GetUserMenuSerialized
https://github.com/DXBrazil/Arda/commit/b3eb3c2c2a40d0aff3352b15278528ff62aedfe9

Remove generic exceptions from GetUserPhoto
https://github.com/DXBrazil/Arda/commit/d4555d22021dc05a18ca0f6a222293498a6fb863

Remove 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:

  1. Na dúvida, evite o uso do try-catch
  2. Use no Program.cs para evitar o crash do processo
  3. 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”.