C# — 18 x mais rápido evitanto Exception

Elvis Fernandes Dias
5 min readJun 19, 2023

--

Aplicações, de maneira geral, são processos responsáveis por aplicar determinadas regras de negócio à um conjunto de dados dentro de um contexto, ou seja, realizam validações a respeito da formatação dos dados, consistências, batimentos entre fontes diferentes, entre outras operações, e a maneira como essas regras são implementadas pode ter impacto significativo na performance do sistema e vamos demonstrar nesse exemplo.

Caso

Temos uma WEB API, mas poderia ser um serviço backend qualquer pois o conceito seria o mesmo, que realiza um cadastro de usuário, recebendo como input os dados abaixo:

Usuário JSON

E para o processamento do cadastro devemos aplicar as seguintes regras, na seguinte ordem:

  1. Não deve existir outro usuário com o mesmo nome;
  2. Não deve existir outro usuário com o mesmo email;
  3. O nome da cidade deve ser localizado em algum cadastro de cidades;
  4. O CEP deve passar por um processo de validação.

Se alguma das validações falhar, o cadastro não deve ser realizado. Sendo assim, o código dessa implementação ficaria mais ou menos assim:

Obs.: Os pontos comentados devem ser implementados para impedir que o objeto usuario seja salvo.

Soluções

Em situações semelhantes a que foi descrita acima, basicamente já me deparei com dois tipos de implementações e vamos abordá-las e validar a que possuir melhor desempenho, levando em conta o tempo de resposta.

Solução 1: Lançar exceptions para interromper o fluxo

Nessa solução, nos pontos comentados onde precisamos inserir alguma implementação para impedir que o cadastro seja realizado, podemos lançar exceptions, desta forma o fluxo do cadastro é interrompido. Para exemplificação, a exception pode ser a classe base “System.Exception” ou alguma exception customizada. Abaixo temos o exemplo de como a implementação ficaria, aqui com exceptions customizadas, derivadas de “System.Exception”.

Obs.: para efeitos didáticos, estou descartando o tempo de execução do método “usuarioRepo.Criar(usuario)”, nos testes apenas é realizado um “return”, sem persistência efetiva dos dados.

Solução 2: Alterar a assinatura do método retornando o resultado

Nessa abordagem alteramos a assinatura do método para retornarmos algum objeto que indique sucesso ou falha na execução, assim não lançamos Exception para interromper o fluxo, mas um return do objeto Result.

Apenas para exemplo, utilizo uma classe simples chamada “Resultado” que contém apenas uma propriedade que indica sucesso ou falha (bool), contudo ela poderia possuir outros atributos indicando a razão da falha e outras informações necessárias para melhorar o tratamento do erro, ou a utilização de algum componente que possua essa funcionalidade, por exemplo FluentResults.

Comparações

Para realizarmos a comparação criei uma WEB API com duas rotas para o cadastro de usuário, uma com a validações retornando Exceptions e outra rota retornando um objeto com o resultado da operação. Ocorrento falha em alguma das validações, retornaremos HTTP 409. Fica como abaixo a controller.

Para executarmos os testes e avaliarmos qual implementação obtém melhor resultado utilizaremos o k6, ferramenta para exceção de testes de carga.

Não detalharei o funcionamento do k6, mas basicamente você cria um script JS com as configurações do teste a ser exeutado. Neste caso, criei um script para realizarmos 1.000 requests contra a API, sendo que a cada 5 requisições 4 ocrrerão falha e 1 retornará sucesso, ou seja, 80% dos requests falharão.

Executando o teste temos os seguintes resultados.

Rota com exception

Rota sem exception

Podemos observar que as 1.000 requisições foram finalizadas em 19,10 segundos na rota que realiza o tratamento com o lançamento de exceção, o que nos da uma vazão de aproximadamente 52 req/segundo. Já a rota que não realiza o lançamento de exceção levou 1,06 seguntos para completar o mesmo trabalho, vazão de aproximadamente 900 req/segundo. O fato de evitarmos o lançamento de exceções nos rendeu uma economia de tempo de aproximadamente 18x em relação a outra abordagem.

Conclusão

Ficou evidente que o lançamento de exception para o controle do fluxo de uma aplicação não é uma prática recomendada. No lançamento de uma exception existe uma carga de processamento para se identicar o StackTrace, verificar se na Stack existe algum Handler para o tratamento daquela exception, entre outras coisas. O comentário abaixo de uma questão no StackOverflow expõe basicamente isso.

https://stackoverflow.com/questions/17379589/why-is-throwing-exceptions-so-slow/17381857#17381857

Também podemos encontrar referência na documentação Microsoft sobre quando devemos evitar o lançamento de exceptions, e um dos itens é justamente sobre o controle do fluxo da aplicação.

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/creating-and-throwing-exceptions#things-to-avoid-when-throwing-exceptions

Projeto

O projeto utilizado neste artigo está disponível no Github abaixo.

Até mais!

--

--

Elvis Fernandes Dias

.Net Developer Graduado em Ciência da Computação e Pós Graduado em Engenharia de Software