Granularidade de exceções

9

Entrei em um debate entre alguns amigos e eu. Eles preferem exceções gerais como ClientErrorExceptione ServerErrorExceptioncom detalhes como campos da exceção, enquanto eu prefiro tornar as coisas mais específicas. Por exemplo, eu posso ter algumas exceções, como:

  • BadRequestException
  • AuthenticationFailureException
  • ProductNotFoundException

Cada uma delas foi criada com base no código de erro retornado da API.

Após as vantagens das exceções, isso parece idiomático para Java. No entanto, a opinião dos meus amigos não é exatamente incomum.

Existe uma maneira preferida em termos de legibilidade do código e usabilidade da API, ou realmente se resume apenas à preferência?


fonte
A página que você vinculou é provavelmente a melhor resposta definitiva que podemos obter. Você está pedindo uma opinião, realmente. Posso responder com qual é minha experiência e opinião, mas essa não é uma resposta objetiva.
Marstato 12/0518
@ marstato isso é justo. Acho que estou procurando justificativa na minha posição. Prefiro manter o que as pessoas esperam nas bibliotecas que escrevo, em vez de seguir um guia, se isso facilitar as minhas coisas, sabe?
Eu concordo absolutamente. Também tenho minhas classes de exceção granulares. Além disso, você pode definir abstracte generalizar classes de exceção com os métodos getter e, em seguida, fazer com que as granulares estendam as gerais. Por exemplo AuthenticationFaliureException extends ClientErrorException. Dessa forma, todo usuário pode escolher como gostaria de lidar com as exceções. É mais trabalho, obviamente. No entanto, ao escrever um aplicativo (em vez de uma biblioteca), é uma situação diferente IMHO. Nesse caso, eu não tornaria as exceções mais granulares do que você precisa, por uma questão de simplicidade.
Marstato 12/0518
@ Marstato, é assim que eu o implemento agora. Estou feliz que você concorda. Eu vou deixar a questão em aberto a noite acabou, mas por favor consolidar isso em um post para que eu possa, pelo menos, verde verificação você

Respostas:

15

A principal diferença entre ter muitas classes de exceção diferentes e ter apenas algumas, com informações mais detalhadas no texto do erro (por exemplo), é que muitas classes de exceção diferentes permitem que o código de chamada reaja de maneira diferente a diferentes tipos de erros, enquanto possui apenas algumas classes facilitam o tratamento de todos os tipos de exceções de maneira uniforme.

Isso geralmente é uma troca. Pode ser atenuado até certo ponto usando herança (uma classe de exceção básica geral para os chamadores que desejam capturar e registrar tudo genericamente, e exceções derivadas dessa classe base para os chamadores que precisam de reações diferentes), mas mesmo isso pode produzir um muita complexidade desnecessária se você não tomar cuidado e seguir o princípio YAGNI. Portanto, a pergunta norteadora deve estar aqui:

  • Você realmente espera que o chamador do seu código reaja de maneira diferente, com fluxo de controle diferente, a esses tipos diferentes de erros?

Não existe uma solução única para isso, nenhuma "prática recomendada" que você possa aplicar em todos os lugares. A resposta a esta pergunta depende fortemente de que tipo de software ou componente você está projetando:

  • Em algum aplicativo, onde você ou a equipe tem toda a base de código sob seu controle?

  • ou algum componente reutilizável para terceiros, onde você não conhece todos os potenciais chamadores?

  • um aplicativo de servidor de execução longa, em que diferentes tipos de erros não devem interromper todo o sistema imediatamente e podem exigir diferentes tipos de mitigação de erros?

  • um processo de aplicação de curta duração em que é suficiente, em caso de erro, exibir uma mensagem de erro para o usuário e reiniciar o processo?

Portanto, quanto mais você souber sobre os potenciais chamadores do seu componente, melhor poderá decidir sobre o nível correto de detalhes para suas exceções.

Doc Brown
fonte
3
"Você realmente espera que o chamador do seu código reaja de maneira diferente, com fluxo de controle diferente, a esses tipos diferentes de erros?" Esta é uma ótima pergunta a ser feita. Obrigado, vou lembrar disso.
Esta é uma boa resposta, eu acho, enfatizaria ainda mais a necessidade de permitir que o aplicativo cliente conduza o projeto de lançamento de exceção. Mesmo as generalizações que você pode achar sensato construir na hierarquia de exceções podem não corresponder às maneiras pelas quais seu aplicativo cliente (se não for você mesmo que o escreve) pode optar por generalizar um manipulador. Se você tiver mais de um aplicativo cliente com necessidades diferentes, é claro que cabe a você equilibrá-los.
Magicduncan
1

A resposta depende de qual nível de relatório de erros estamos falando.

Em geral, concordo com seus amigos, você não deve torná-los mais granulares do que o necessário para transmitir a causa do problema.

Se houver uma exceção comum e bem conhecida para fazer referência a nulo (NullReferenceException), você não deve criar sua própria MyObjectIsNullException. Isso apenas acrescentaria uma camada de confusão para o intérprete humano, uma coisa adicional a aprender que não esclarece nada.

Somente quando sua exceção é tão especial que não existe uma predefinida que cubra a causa raiz, você deve criar sua própria.

No entanto, você não precisa parar por aí. O erro comum pode ocorrer em um dos seus componentes e você pode querer transmitir que houve um problema no seu componente . Portanto, não apenas o que deu errado, mas também onde. Em seguida, seria apropriado agrupar a primeira exceção em um MyComponentException. Isso lhe dará o melhor dos dois mundos.

Primeiro, ficará claro que seu componente teve problemas. Em uma alavanca mais baixa, a causa específica estaria na exceção interna.

Martin Maat
fonte
Isso é justo. Então você está sugerindo não inventar uma nova exceção quando uma boa já existe?
@rec Sim, porque todo mundo já conhece os predefinidos. É para isto que eles estão lá.
Martin Maat