DEV Community

Stephanie Brito
Stephanie Brito

Posted on

Exceptions são fundamentais, mas não generalize seu uso!

1 - O que é um Exception

No desenvolvimento de software temos que lidar com erros, seja erros de programação, erros de domínio, erros de integrações, erros de recursos, erros de ambientes, erros de processamento de dados principalmente quando depende do usuário, entre muitos outros. Então em nossas aplicações sempre temos algo que pode dar errado, e vai dar errado, por isso temos que lidar com isso.

Por ser tão fundamental, maioria das linguagens hoje em dia são preparadas com recursos para auxiliar quanto a erros inesperados, no java por exemplo, ele cria um novo objeto de exceção, ou exeption, que contém as informações do erro, incluindo seu tipo, estado e local onde ocorreu esse erro, e lança esse objeto para fora da funcionalidade ainda em tempo de execução, para que possamos tratar esse erro de alguma forma. Então é fundamental estar preparado para lidar com exceções na programação.

E é necessário lidar com as exceções porque quando ocorrem, o sistema em tempo de execução pesquisa exaustivamente na pilha de chamadas por um método que contenha um bloco de código que possa manipular essa exceção, se não, o sistema de tempo de execução e o programa terminam.

2 - Porque não generalizar o uso de exceptions

Obs.: Aqui o intuito nao é abolir o uso dos exceptions, sua utilização é super importante, e fundamental, mas o intuito é reduzir sua utilização em casos que não são necessários ou que não sejam a melhor opção.

Até aqui sabemos que exception são utilizadas em comportamentos inesperados, e que interrompem o fluxo, logo não possibilitam assim que a funcionalidade do serviço seja concluída. Porém, algo que muito se esquece é que o uso de exception vem com um alto custo!

Exception traz mais complexidade para o código:

"Lançar exceções é fácil, lidar com elas que é difícil." (A Filosofia de Design de Software, John Ousterhout)

Escrever código para um fluxo que lide com possíveis erros é mais difícil do que escrever um código para um fluxo normal e funcional, ou o "caminho feliz", porque o tratamento de erros traz complexidade. Por isso, acabamos caindo na tentação de usar exceções quando temos que lidar com erros ou qualquer possibilidade que coloque nossa funcionalidade em risco. Quem nunca utilizou exception para evitar lidar com uma situação difícil, que atire a primeira pedra!

Porém gerar uma exceção na sua funcionalidade apenas passa o seu problema para outra pessoa, e expande a complexidade da sua funcionalidade para todo o sistema. Ainda alguém vai ter que lidar com isso, e além do fluxo inicial da funcionalidade específica, a pessoa terá que estar ciente de todos os fluxos de seus chamadores que também são impactados, e do Exception Handler ou do mecanismo que terá que lidar com isso de fato.

Ademais, apesar de cada funcionalidade chamadora ter a possibilidade de também tratar essa exceção de maneira independente e diferente, é provável que também não consigam corrigir o erro, já que a funcionalidade mais específica e próxima do contexto do erro nao conseguiu. Na verdade, é mais provável que essa liberdade de cada chamador poder modificar ou tratar a exceção inicial possa ocasionar a perda da exceção original. Um estudo recente descobriu que mais de 90% das falhas catastróficas em sistemas distribuídos com uso intensivo de dados foram causadas por tratamentos incorretos de erros.

Logo, lançar uma exceção implica em aumentar a complexidade não apenas da funcionalidade local onde ocorreu, mas também em todas suas funcionalidades chamadoras, assim a exception se propaga por vários níveis da pilha de funcionalidades antes de ser capturado pelo Exception Handler ou o outro mecanismo responsável, e por tal impacta várias funcionalidades fora do contexto do erro. Por isso, lançar exceções é fácil, mas lidar com elas é difícil porque a complexidade das exceções vem do código responsável por tratar essas exceções. E até o momento, a melhor maneira de reduzir a complexidade causada pelo tratamento de exceções é, por si só, reduzir o número de lugares onde ocorre o lançamento de exceções para serem tratadas.

3 - Formas de diminuir o uso de exceptions:

“Um dos problemas das exceções é saber quando usá-las." (O programador pragmático, Andrew Hunt e David Thomas)

3.1. Inexistência, por passagem de parâmetro, nao deve ser caso de lançamento de exceção:

“Use exceções para problemas excepcionais." (Programador Pragmático, Andrew Hunt e David Thomas)

Existem situações que de início já sabemos que vão ocorrer, porque damos a possibilidade, o exemplo é a funcionalidade com parâmetro, que também engloba por sí só a passagem de parâmetros errados ou inexistentes. A melhor maneira de eliminar a complexidade de tratamento de exceções de inexistência, quando ocorre a passagem de parâmetro, é não lançar nenhuma exceção, já que prevê que pode ocorrer a passagem de parâmetros que nao existam. Por exemplo, em uma funcionalidade void ao ser chamada para excluir uma variável desconhecida, ela deve simplesmente retornar sem fazer nada. Ou se for no caso de uma consulta, deve apenas retornar vazio se não for encontrado.

Obs.: Em caso de inexistência, em um Domínio que prevê uma regra fixa, em que não possa ocorrer inexistência, mas ocorre, ou seja é excepcional, então faz sentido retornar uma exceção de inexistência. Por exemplo, no sistema Windows se a bios nao detectar o HD entao retorna Operating System Not Found Exception.
Enter fullscreen mode Exit fullscreen mode

3.2. Erro de domínio não deve ser uma exceção:

“Se uma falha é um comportamento esperado, então você não deveria usar exceções." (Martin Fowler)

Exceptions são comportamentos inesperados, que não possibilitam o fluxo do serviço ser concluído, indicam assim um erro na funcionalidade. Por isso temos que tentar diminuir o número de exceções, ao mapear todos os possíveis fluxos da funcionalidade, para que em caso de ocorrer uma exception realmente seja um caso de exceção. Logo, regras de negócio não devem ser potenciais para exceptions, porque fazem parte do funcionamento normal do sistema, assim devem ser tratadas, ou informado se necessário, de outra forma, mas não por exceção. Exemplos:

  • Validação de entidades de domínio: em um request em que tem que ser feitas algumas validações de regras de negócios, se for usado o exception então o fluxo será interrompido na primeira inconsistência de validação encontrada, logo em caso de 2 ou mais inconsistência nas informações, então será necessário consumir o serviço 2 ou mais vezes pelo menos para o usuário ser informado, e então ajustar as informações uma por uma. Enquanto se considerar essas validações de inconsistências como mais um fluxo possível do serviço, é possível manipular de uma forma mais eficiente. Por exemplo, uma solução viável é realizar todas as validações primeiro, e guardar o resultado de cada em uma lista de validações, em caso de haver pelo menos uma inconsistência nessa lista de validações, então retornar para o usuário, com algum mecanismo de notificação com todas as inconsistências encontradas. Dessa forma o usuário com um request seria informado de todas as inconsistências de suas informações.

  • Serviços de domínio: por exemplo, o fluxo de exclusão de arquivos no Windows e Unix fornece diferentes maneiras de como lidar com o erro de excluir um arquivo em uso. O Windows lança uma exception, interrompendo o fluxo, ele nao permite que um arquivo seja excluído se estiver em aberto por outro processo. Enquanto que o sistema Unix, acrescenta esse fluxo a funcionalidade de uma forma mais elegante, no qual se o arquivo estiver em aberto por outro processo, então o Unix apenas nao exclui o arquivo imediatamente. Resumidamente, ele marca o arquivo para exclusão, e remove o seu diretório para nenhum outro novo processo abrir o arquivo, e depois que o processo atual finalizar de utilizar o arquivo então o mesmo é deletado de fato. Assim o Unix evita de lançar 2 exceptions, uma para o usuário que tentou deletar o arquivo, e outro exception para o processo que consome o arquivo.

3.3. Níveis mais altos de software não precisam estar cientes de toda exceção de níveis mais baixos:

A segunda técnica para reduzir o número de lugares onde exceções devem ser tratadas, é utilizar de outros mecanismos de manipulação de exceções de forma interna. Existem exceções que podem ser tratadas localmente, e nao tem a necessidade de subir a pilha de chamadas.

Por exemplo em um projeto que processa itens de um lote, ao tentar processar um item inconsistente da lista de itens, é possível aplicar um try catch e tratar localmente o erro, de tal forma que apenas este item não tenha o processamento finalizado, e é marcado com erro e as informações do exception, depois continua o processamento dos demais itens normalmente. Ao finalizar, além do item também podemos marcar o lote com seu total de itens processados com sucesso e total de itens processados com erros. Assim esperamos que erros possam ocorrer ao processar o itens do lote, mas adicionamos seu controle como mais um fluxo possível do serviço, logo não lançará exceções para camadas superiores, e também não interrompe o processamento dos demais itens do lote.

3.4. Centralizar o controle de exceções em uma única estrutura, use Exception Handler:

"Trate muitas exceções com um único pedaço de código; em vez de escrever manipuladores distintos para muitas exceções individuais, trate todas elas em um só lugar com um único manipulador." (A Filosofia de Design de Software, John Ousterhout)

Agregar as manipulações de todos os tipos de exceções no mesmo local, ao em vez de capturá-las de formas individuais, irá diminuir a complexidade, já que vai ser capaz de lidar com todas exceções através da mesma estrutura, ou seja, encapsula as informações de como lidar com erros de todo o resto do código do sistema. Com isso também adquire todas as vantagens do encapsulamento: controle do contexto, manutenção, reutilização, flexibilidade, debugging, logging e melhor controle do fluxo de exceção.

Essa estrutura de manipulações é denominada Exception Handler, é um mecanismo que lida com as exceções de forma centralizada, mas que para isso deve ser posta no nível mais alto da estrutura do seu sistema, pois assim permite que mais exceções de métodos, abaixo dela, sejam manipuladas por ela. Ou seja, o Exception Handler funciona e performa melhor localizada acima de toda a pilha de funcionalidades.

Esse mecanismo visa trazer todos os benefícios do encapsulamento, ao centralizar a manipulação de exceções. Com essa estrutura nao teremos manipulações de exceptions espalhados pelo sistema, ou manipulações duplicadas para os mesmos tipos de exceptions, ou ainda pior, perder exceptions que nao foram manipuladas levando o sistema a falha. Além dos benefícios do encapsulamento, como facilitar a manutenção, reutilização, escalabilidade, flexibilidade, debugging e melhor controle das exceções.

É tão vantajoso que às vezes Exception Handler podem ser usados até mesmo em linguagens (como em Java), que têm um bom esquema interno de manipulação de exceções, por facilitar a classificar e manipular de forma customizada o que fazer depois que ocorrer a exceção sem interferir ou adicionar mais complexidade ainda nos fluxos normais da funcionalidade. Por exemplo, em um projeto de api ao dar qualquer erro do tipo timeout, você quer que mostre para o usuário uma mensagem customizada informando para tentar novamente mais tarde, em vez de retornar erro interno diretamente para o usuário. Ou em um projeto de processador de mensagens, em que cada mensagem é uma linha de um excel, e que possua dependência externa, ao ocorrer uma exceção de infraestrutura, faz sentido para você salvar o erro e enviar a mensagem para fila para tentar ser reprocessada, com no máximo 3 tentativas antes de marcar esse item com erro de fato. Ou em um job de auditoria, ao dar o erro em um item, qualquer tipo de erro que impeça adicionar esse item na auditoria, para suas regras de negócios é um caso super sensível, então além de logar o erro especificamente e marcar a auditoria com erro, você também quer que envie um email para o responsável diretamente com os dados dessa exceção. Logo, com o exception handler você pode classificar as exceções, e tentar tratar essa exceção da melhor forma para seu negócio sem impactar, ou impactando o menos possível os fluxos normais do sistema.

4 - Exception Handling Pattern + Notification Pattern

Existem outras formas de lidar com erros de forma customizada, além do Exception Handler, uma delas é o Notification Pattern. Esse padrão nos apresenta a utilização de um mecanismo que é incorporado ao fluxo normal do domínio, logo não tem a complexidade do Exception Handler de interromper os fluxos, por isso se tornou popular para notificar erros de domínio.

Além de notificar erros e validações de regras de negócio, também é possível utilizar o Notification Pattern como auxiliar para levar qualquer mensagem referente ao fluxo, que seja de interesse, para os níveis superiores. Com isso é possível classificar notificações, de tal forma a precaver que erros aconteçam, ao utilizar notificações de avisos para o usuário, por exemplo. Logo, Notification Pattern pode também evitar erros futuros de domínio.

Ademais, utilizar o Notification Pattern para notificar erros de regras de negócios nos permite reduzir o número de exceções no sistema, e assim reduzir a complexidade do sistema. Quanto maior for o domínio, mais vantajoso é aplicar esse padrão. Mas, com destaque que, Notification Pattern não exclui a necessidade de usar o Error Handler ou outro mecanismo para lidar com exceções excepcionais, mas a própria estrutura do Exception Handler deve ficar mais simples quando usada junto com o Notification Pattern, já que não será mais responsável por identificar erros de domínio e seus subtipos.

Github com exemplos (em Java e C#):

https://github.com/sbrito2/exception-handler-examples/tree/main

Referências:
Clean Code - Robert C. Martin - Chapter 7 - Exception Handling
A Philosophy of Software Design - John Ousterhout. Martin - Chapter 10 - Define Errors Out Of Existence
Programador Pragmático - Andrew Hunt e David Thomas - Chapter 4 - Quando usar exceções
https://martinfowler.com/articles/replaceThrowWithNotification.html

Top comments (0)