Exceções são apenas para casos excepcionais?
TLDR; Talvez não
Por muito tempo eu fui completamente desfavorável ao uso de exceptions no decorrer do código. Nunca entendi muito bem, mas sempre assimilei meio que como se fossem os famosos e pré-históricos GOTOs. Porém, hoje me ocorreu um estalo que mudou um pouco essa visão.
Pensamos em uma aplicação com camadas bem definidas. Vamos pegar a arquitetura limpa como exemplo já que na minha bolha anda em alta asuhaus.
Temos a camada de domínio e a camada de infraestrutura como principais atores. A camada de aplicação, ao meu entendimento é só um orquestrador tendo a camada de infraestrutura injetada e orquestrando a camada de negócio com base nisso.
Vamos navegar um pouco pelo caminho que eu via antes do estalo.
Nossa regra de negócio NUNCA poderia lançar uma exceção. Por que?! Basicamente tudo que receberíamos já deveria ser conhecido, ou seja, não deveria chegar valor inválido na nossa camada de domínio (volto nesse ponto mais pra frente).
As únicas exceções que teríamos realmente, seriam casos realmente excepcionais. Um banco de dados inacessível, uma api crítica fora do ar, um serviço com timeout e assim vai.
Com isso, apenas deveríamos nos preocupar em retornar valores válidos dessas camadas em caso de exceções. Banco secundário inacessível fora no momento de uma query? Sem problemas, devolve um valor conhecido e trata ele, devidamente registrando a exceção que ocorreu antes, obviamente.
Com isso, nossa camada de aplicação e domínio nunca precisaria se preocupar com exceções, porque, por mas que sejam situações excepcionais, elas são esperadas, podem ser de N tipos, mas sempre são (ou deveriam ser) esperadas.
A questão que fez com que acontecesse o estalo que fez com que eu mudasse de ideia foi: O que eu realmente preciso validar do dado inputado na camada de infraestrutura, como controller por exemplo?
A partir disso vi dois caminhos:
- faço uma validação simples olhando se o dado é do tipo que espero e possui valor
- faço uma validação complexa, para que meu domínio seja completamente blindado contra valores inesperados
Qual dos dois está certo? Diria que depende, mas como não sou sênior vou me contentar com um simples "talvez ambos" auehauhe.
Mas o que mais me fez pensar que talvez a segunda possibilidade fosse a MENOS adequada de se implementar na camada de infraestrutura foi, e só contextualizando um pouco, criar um objeto de valor na minha camada de domínio, e completamente no automático implementar uma validação lançando uma exceção de domínio, e depois disso instanciar ele direto na camada de infraestrutura apenas para validar se o dado inputado pelo usuário estava válido para ser passado para a camada de aplicação.
Nisso eu me dei de conta que eu estava usando um objeto de domínio que lançava uma exceção na camada de infraestrutura, para que se não lançasse eu passasse o valor inputado e não a instância do objeto de valor para dentro do meu caso de uso para que depois eu reinstanciasse ele novamente, sendo que eu havia acabado de fazer isso.
Foi nesse momento que eu percebi que o caminho que eu estava tomando como melhor até o momento, de evitar que o domínio lançasse exceção, não estava tão adequado. Eu estava implementando uma validação de regra de negócio no controlador (normalmente eu fazia diretamente no input e não instanciando um objeto de valor, isso foi totalmente por acaso) pois além disso enfraquecer o domínio, a regra de negócio estaria espalhada.
O domínio só pode existir em um estado válido, mas isso não significa que um valor inválido não possa tentar chegar até ele, e pra mitigar erros ele precisa saber qual é seu estado inválido, para que informe a quem precisar, que ele tentou ser instanciado com um valor que fere a regra.
A partir dali o estalo fez com que eu passasse a achar o primeiro caminho o ideal, pois só preciso saber que existe um valor que tente se encaixar na regra e que também seja do tipo esperado para compor o domínio e instanciar alguma entidade ou objeto de valor.