Pergunta

Exceções não verificadas são aceitáveis ​​se você quiser tratar todas as falhas da mesma maneira, por exemplo, registrando-as e pulando para a próxima solicitação, exibindo uma mensagem ao usuário e tratando do próximo evento, etc.Se este for o meu caso de uso, tudo o que preciso fazer é capturar algum tipo de exceção geral em alto nível no meu sistema e lidar com tudo da mesma maneira.

Mas quero me recuperar de problemas específicos e não tenho certeza da melhor maneira de abordar isso com exceções não verificadas.Aqui está um exemplo concreto.

Suponha que eu tenha uma aplicação web, construída usando Struts2 e Hibernate.Se uma exceção surgir na minha "ação", eu a registro e exibo um belo pedido de desculpas ao usuário.Mas uma das funções da minha aplicação web é criar novas contas de usuário, que exigem um nome de usuário exclusivo.Se um usuário escolher um nome que já existe, o Hibernate lança um org.hibernate.exception.ConstraintViolationException (uma exceção não verificada) nas entranhas do meu sistema.Eu realmente gostaria de me recuperar desse problema específico, pedindo ao usuário que escolha outro nome de usuário, em vez de fornecer a mesma mensagem "registramos seu problema, mas por enquanto você está com problemas".

Aqui estão alguns pontos a serem considerados:

  1. Há muitas pessoas criando contas simultaneamente.Não quero bloquear toda a tabela de usuários entre um "SELECT" para ver se o nome existe e um "INSERT" se não existir.No caso de bancos de dados relacionais, pode haver alguns truques para contornar isso, mas o que realmente me interessa é o caso geral em que a pré-verificação de uma exceção não funciona devido a uma condição de corrida fundamental.A mesma coisa pode ser aplicada à procura de um arquivo no sistema de arquivos, etc.
  2. Dada a propensão do meu CTO para o gerenciamento induzido pela leitura de colunas de tecnologia em "Inc.", preciso de uma camada indireta em torno do mecanismo de persistência para que eu possa descartar o Hibernate e usar o Kodo, ou qualquer outra coisa, sem alterar nada, exceto o nível mais baixo camada de código de persistência.Na verdade, existem várias dessas camadas de abstração em meu sistema.Como posso evitar que vazem, apesar de exceções não verificadas?
  3. Um dos pontos fracos declarados das exceções verificadas é ter que "tratá-las" em todas as chamadas na pilha - seja declarando que um método de chamada as lança ou capturando-as e manipulando-as.Lidar com eles muitas vezes significa envolvê-los em outra exceção verificada de um tipo apropriado ao nível de abstração.Assim, por exemplo, em terras de exceção verificada, uma implementação baseada em sistema de arquivos do meu UserRegistry pode capturar IOException, enquanto uma implementação de banco de dados capturaria SQLException, mas ambos lançariam um UserNotFoundException que esconde a implementação subjacente.Como tirar proveito das exceções não verificadas, poupando-me do fardo desse empacotamento em cada camada, sem vazar detalhes de implementação?
Foi útil?

Solução

IMO, exceções de empacotamento (verificadas ou não) têm vários benefícios que valem o custo:

1) Incentiva você a pensar sobre os modos de falha do código que você escreve.Basicamente, você deve considerar as exceções que o código que você chama pode lançar e, por sua vez, considerar as exceções que lançará para o código que chama o seu.

2) Dá a você a oportunidade de adicionar informações adicionais de depuração à cadeia de exceções.Por exemplo, se você tiver um método que lança uma exceção para um nome de usuário duplicado, você pode agrupar essa exceção com um que inclua informações adicionais sobre as circunstâncias da falha (por exemplo, o IP da solicitação que forneceu o nome de usuário duplicado) que não estava disponível para o código de nível inferior.A trilha de exceções do cookie pode ajudá-lo a depurar um problema complexo (certamente ajudou para mim).

3) Permite que você se torne independente da implementação do código de nível inferior.Se você estiver agrupando exceções e precisar trocar o Hibernate por algum outro ORM, basta alterar o código de tratamento do Hibernate.Todas as outras camadas de código ainda usarão com êxito as exceções agrupadas e as interpretarão da mesma maneira, mesmo que as circunstâncias subjacentes tenham mudado.Observe que isso se aplica mesmo se o Hibernate mudar de alguma forma (ex:eles trocam exceções em uma nova versão);não se trata apenas de substituição de tecnologia no atacado.

4) Incentiva você a usar diferentes classes de exceções para representar diferentes situações.Por exemplo, você pode ter uma DuplicateUsernameException quando o usuário tenta reutilizar um nome de usuário e uma DatabaseFailureException quando você não pode verificar nomes de usuário duplicados devido a uma conexão de banco de dados interrompida.Isso, por sua vez, permite que você responda à sua pergunta (“como faço para me recuperar?”) de maneira flexível e poderosa.Se você receber uma DuplicateUsernameException, poderá decidir sugerir um nome de usuário diferente ao usuário.Se você receber uma DatabaseFailureException, poderá deixá-la borbulhar até o ponto em que exibirá uma página "inativo para manutenção" para o usuário e enviará um e-mail de notificação para você.Depois de ter exceções personalizadas, você terá respostas personalizáveis ​​- e isso é bom.

Outras dicas

Eu gosto de reembalar exceções entre as "camadas" do meu aplicativo, então, por exemplo, uma exceção específica do banco de dados é reembalada dentro de outra exceção que seja significativa no contexto do meu aplicativo (é claro, deixo a exceção original como membro, então Eu não destruo o rastreamento de pilha).

Dito isto, acho que um nome de usuário não exclusivo não é uma situação "excepcional" o suficiente para justificar um lançamento.Eu usaria um argumento de retorno booleano.Sem saber muito sobre sua arquitetura, é difícil dizer algo mais específico ou aplicável.

Ver Padrões para geração, manuseio e gerenciamento de erros

Do padrão de domínio dividido e erros técnicos

Um erro técnico nunca deve causar um erro de domínio a ser gerado (nunca o Twain deve atender).Quando um erro técnico deve fazer com que o processamento de negócios falhe, ele deve ser envolvido como um SystemError.

Os erros de domínio devem sempre iniciar um problema de domínio e ser tratados pelo código do domínio.

Os erros de domínio devem passar "perfeitamente" através dos limites técnicos.Pode ser que esses erros devam ser serializados e constitutados para que isso aconteça.Proxies e fachadas devem assumir a responsabilidade de fazer isso.

Os erros técnicos devem ser tratados em pontos específicos no aplicativo, como limites (consulte o log no limite de distribuição).

A quantidade de informações de contexto passadas com o erro dependerá de quão útil isso será para o diagnóstico e manuseio subsequentes (descobrindo uma estratégia alternativa).Você precisa questionar se o rastreamento da pilha de uma máquina remota é totalmente útil para o processamento de um erro de domínio (embora a localização do código do erro e dos valores variáveis ​​naquele momento possa ser útil)

Portanto, envolva a exceção de hibernação no limite para hibernar com uma exceção de domínio não verificada, como "UniqueUsernameException", e deixe que ela chegue até o manipulador dela.Certifique-se de javadoc a exceção lançada, mesmo que não seja uma exceção verificada!

Como você está usando o hibernate, a coisa mais fácil a fazer é apenas verificar essa exceção e envolvê-la em uma exceção personalizada ou em um objeto de resultado personalizado que você possa ter configurado em sua estrutura.Se você quiser abandonar o hibernate mais tarde, apenas certifique-se de agrupar essa exceção em apenas um lugar, o primeiro lugar em que você captura a exceção do hibernate, esse é o código que você provavelmente terá que alterar ao fazer uma troca de qualquer maneira, então se o problema está em um só lugar, então a sobrecarga adicional é quase nula.

ajuda?

Eu concordo com Nick.A exceção que você descreveu não é realmente uma "exceção inesperada", portanto, você deve projetar seu código de acordo, levando em consideração possíveis exceções.

Também recomendo dar uma olhada na documentação da Microsoft Enterprise Library Bloco de tratamento de exceções ele tem um bom esboço de padrões de tratamento de erros.

Você pode capturar exceções não verificadas sem precisar envolvê-las.Por exemplo, o seguinte é Java válido.

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

Portanto, em sua ação/controlador você pode ter um bloco try-catch em torno da lógica onde a chamada do Hibernate é feita.Dependendo da exceção, você pode renderizar mensagens de erro específicas.

Mas acho que no seu hoje poderia ser o framework Hibernate e amanhã o framework SleepLongerDuringWinter.Nesse caso, você precisa fingir ter sua própria estrutura ORM que envolve a estrutura de terceiros.Isso permitirá que você envolva quaisquer exceções específicas da estrutura em exceções mais significativas e/ou verificadas que você saiba entender melhor.

  1. A questão não está realmente relacionada a verificado vs.debate não controlado, o mesmo se aplica a ambos os tipos de exceção.

  2. Entre o ponto onde ConstraintViolationException é lançada e o ponto onde queremos lidar com a violação exibindo uma boa mensagem de erro, há um grande número de chamadas de método na pilha que devem ser abortadas imediatamente e não devem se preocupar com o problema.Isso torna o mecanismo de exceção a escolha certa, em vez de redesenhar o código de exceções para valores de retorno.

  3. Na verdade, usar uma exceção não verificada em vez de uma exceção verificada é uma opção natural, já que queremos que todos os métodos intermediários na pilha de chamadas sejam ignorar a exceção e não lidar com isso .

  4. Se quisermos lidar com a "violação de nome exclusivo" apenas exibindo uma bela mensagem de erro (página de erro) para o usuário, não há realmente necessidade de uma DuplicateUsernameException específica.Isso manterá baixo o número de classes de exceção.Em vez disso, podemos criar uma MessageException que pode ser reutilizada em muitos cenários semelhantes.

    Assim que possível, capturamos ConstraintViolationException e o convertemos em MessageException com uma mensagem legal.É importante convertê-lo logo, quando tivermos certeza de que é realmente a "restrição de nome de usuário exclusivo" que foi violada e não alguma outra restrição.

    Em algum lugar próximo ao manipulador de nível superior, apenas manipule MessageException de uma maneira diferente.Em vez de "registramos seu problema, mas por enquanto você está perdido", simplesmente exiba a mensagem contida em MessageException, sem rastreamento de pilha.

    A MessageException pode receber alguns parâmetros adicionais do construtor, como uma explicação detalhada do problema, próxima ação disponível (cancelar, ir para uma página diferente), ícone (erro, aviso)...

O código pode ficar assim

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

Em um lugar totalmente diferente, há um manipulador de exceções superior

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}

@janeiro Verificado versus não verificado é uma questão central aqui.Questiono sua suposição (# 3) de que a exceção deva ser ignorada nos quadros intermediários.Se eu fizer isso, acabarei com uma dependência específica de implementação no meu código de alto nível.Se eu substituir o Hibernate, os blocos catch em todo o meu aplicativo terão que ser modificados.No entanto, ao mesmo tempo, se eu capturar a exceção em um nível inferior, não receberei muitos benefícios ao usar uma exceção não verificada.

Além disso, o cenário aqui é que desejo capturar um erro lógico específico e alterar o fluxo do aplicativo solicitando novamente ao usuário um ID diferente.Simplesmente alterar a mensagem exibida não é suficiente, e a capacidade de mapear diferentes mensagens com base no tipo de exceção já está incorporada nos Servlets.

@erikson

Apenas para adicionar comida aos seus pensamentos:

Marcado versus desmarcado também é debatido aqui

O uso de exceções não verificadas é compatível com o fato de serem usadas IMO para exceção causado pelo chamador da função (e o chamador pode estar várias camadas acima dessa função, daí a necessidade de outros quadros ignorarem a exceção)

Em relação ao seu problema específico, você deve capturar a exceção não verificada em alto nível e encapsulá-la, como dito por @Kanook em sua própria exceção, sem exibir a pilha de chamadas (conforme mencionado por @Jan Soltis)

Dito isto, se a tecnologia subjacente mudar, isso realmente terá um impacto sobre aqueles catch() já presentes no seu código, e isso não responde ao seu cenário mais recente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top