Fui designado para manter um aplicativo escrito há algum tempo por desenvolvedores mais qualificados. Me deparei com este pedaço de código:
public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
try {
return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
rethrow(e);
}
throw new RuntimeException("cannot reach here");
}
Estou curioso para saber se jogar RuntimeException("cannot reach here")
é justificado. Provavelmente estou perdendo algo óbvio, sabendo que esse código vem de um colega mais experiente.
EDIT: Aqui está o corpo de retrocesso a que algumas respostas se referem. Considero que não é importante nesta questão.
private void rethrow(Exception e) throws MailException {
if (e instanceof InvalidDataException) {
InvalidDataException ex = (InvalidDataException) e;
rethrow(ex);
}
if (e instanceof EntityAlreadyExistsException) {
EntityAlreadyExistsException ex = (EntityAlreadyExistsException) e;
rethrow(ex);
}
if (e instanceof EntityNotFoundException) {
EntityNotFoundException ex = (EntityNotFoundException) e;
rethrow(ex);
}
if (e instanceof NoPermissionException) {
NoPermissionException ex = (NoPermissionException) e;
rethrow(ex);
}
if (e instanceof ServiceUnavailableException) {
ServiceUnavailableException ex = (ServiceUnavailableException) e;
rethrow(ex);
}
LOG.error("internal error, original exception", e);
throw new MailUnexpectedException();
}
private void rethrow(ServiceUnavailableException e) throws
MailServiceUnavailableException {
throw new MailServiceUnavailableException();
}
private void rethrow(NoPermissionException e) throws PersonNotAuthorizedException {
throw new PersonNotAuthorizedException();
}
private void rethrow(InvalidDataException e) throws
MailInvalidIdException, MailLoginNotAvailableException,
MailInvalidLoginException, MailInvalidPasswordException,
MailInvalidEmailException {
switch (e.getDetail()) {
case ID_INVALID:
throw new MailInvalidIdException();
case LOGIN_INVALID:
throw new MailInvalidLoginException();
case LOGIN_NOT_ALLOWED:
throw new MailLoginNotAvailableException();
case PASSWORD_INVALID:
throw new MailInvalidPasswordException();
case EMAIL_INVALID:
throw new MailInvalidEmailException();
}
}
private void rethrow(EntityAlreadyExistsException e)
throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
switch (e.getDetail()) {
case LOGIN_ALREADY_TAKEN:
throw new MailLoginNotAvailableException();
case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
throw new MailEmailAddressAlreadyForwardedToException();
}
}
private void rethrow(EntityNotFoundException e) throws
MailAccountNotCreatedException,
MailAliasNotCreatedException {
switch (e.getDetail()) {
case ACCOUNT_NOT_FOUND:
throw new MailAccountNotCreatedException();
case ALIAS_NOT_FOUND:
throw new MailAliasNotCreatedException();
}
}
java
programming-practices
Sok Pomaranczowy
fonte
fonte
rethrow
não for realmentethrow
uma exceção. (o que pode acontecer algum dia, se a implementação for alterada)Respostas:
Primeiro, obrigado por atualizar sua pergunta e nos mostrar o que
rethrow
faz. Então, de fato, o que ele faz é converter exceções com propriedades em classes de exceções mais refinadas. Mais sobre isso mais tarde.Como eu realmente não respondi a pergunta principal originalmente, aqui vai: sim, geralmente é um estilo ruim lançar exceções de tempo de execução em código inacessível; é melhor usar afirmações ou, melhor ainda, evitar o problema. Como já apontado, o compilador aqui não pode ter certeza de que o código nunca sai do
try/catch
bloco. Você pode refatorar seu código aproveitando que ...Erros são valores
(Sem surpresa, é bem conhecido em movimento )
Vamos usar um exemplo mais simples, o que eu usei antes da sua edição: imagine que você esteja registrando algo e criando uma exceção de invólucro, como na resposta do Konrad . Vamos chamá-lo
logAndWrap
.Em vez de lançar a exceção como um efeito colateral de
logAndWrap
, você poderia deixá-lo fazer seu trabalho como efeito colateral e fazê-lo retornar uma exceção (pelo menos, a que foi fornecida na entrada). Você não precisa usar genéricos, apenas funções básicas:Então, você
throw
explicitamente, e seu compilador está feliz:E se você esquecer
throw
?Como explicado no comentário de Joe23 , uma maneira defensiva de programação para garantir que a exceção seja sempre lançada consistiria em fazer explicitamente um
throw new CustomWrapperException(exception)
no final delogAndWrap
, como é feito pelo Guava.Throwables . Dessa forma, você sabe que a exceção será lançada e seu analisador de tipos ficará feliz. No entanto, suas exceções personalizadas precisam ser desmarcadas, o que nem sempre é possível. Além disso, eu classificaria o risco de um desenvolvedor ausente gravarthrow
muito baixo: o desenvolvedor deve esquecê-lo e o método circundante não deve retornar nada, caso contrário, o compilador detectaria um retorno ausente. Esta é uma maneira interessante de combater o sistema de tipos, e funciona, no entanto.Rethrow
O real também
rethrow
pode ser escrito como uma função, mas tenho problemas com sua implementação atual:Existem muitos elencos inúteis comoElencos são de fato necessários (ver comentários):Ao lançar / retornar novas exceções, a antiga é descartada; no código a seguir, a
MailLoginNotAvailableException
não me permite saber qual login não está disponível, o que é inconveniente; além disso, os traços de pilha serão incompletos:Por que o código de origem não lança essas exceções especializadas em primeiro lugar? Eu suspeito que
rethrow
seja usado como uma camada de compatibilidade entre um subsistema (de correspondência) e a lógica de negócios (talvez a intenção seja ocultar detalhes de implementação, como exceções lançadas, substituindo-as por exceções personalizadas). Mesmo se eu concordar que seria melhor ter um código sem captura, como sugerido na resposta de Pete Becker , não acho que você terá a oportunidade de remover o códigocatch
erethrow
aqui sem grandes refatorações.fonte
logAndWrap
método, você pode lançar a exceção em vez de retorná-la, mas mantenha o tipo de retorno(Runtime)Exception
. Dessa forma, o bloco catch pode permanecer como você o escreveu (sem o lançamento de código inacessível). Mas tem a vantagem adicional de que você não pode esquecer othrow
. Se você retornar a exceção e esquecer o lançamento, isso fará com que a exceção seja silenciosamente engolida. Lançar enquanto tiver um tipo de retorno RuntimeException fará o compilador feliz, além de evitar esses erros. É assim que o GuavaThrowables.propagate
é implementado.logAndWrap
mas não faz nada com a exceção retornada? Isso é interessante. Dentro de uma função que deve retornar um valor, o compilador observaria que há um caminho sem nenhum valor retornado, mas isso é útil para métodos que não retornam nada. Obrigado novamente.Essa
rethrow(e);
função viola o princípio que afirma que, em circunstâncias normais, uma função retornará, enquanto em circunstâncias excepcionais, uma função lançará uma exceção. Essa função viola esse princípio lançando uma exceção em circunstâncias normais. Essa é a fonte de toda a confusão.O compilador assume que essa função retornará em circunstâncias normais; portanto, até onde o compilador pode dizer, a execução pode chegar ao final da
retrieveUserMailConfiguration
função; nesse momento, é um erro não ter umareturn
instrução. ARuntimeException
jogada lá deve aliviar essa preocupação do compilador, mas é uma maneira bastante desajeitada de fazê-lo. Outra maneira de evitar ofunction must return a value
erro é adicionar umareturn null; //to keep the compiler happy
declaração, mas isso é igualmente desajeitado na minha opinião.Então, pessoalmente, eu substituiria isso:
com isso:
ou, melhor ainda, (como sugerido pelo coredump ), com isso:
Assim, o fluxo de controle seria tornado óbvio para o compilador, para que sua final
throw new RuntimeException("cannot reach here");
se tornasse não apenas redundante, mas na verdade nem compilável, pois seria sinalizada pelo compilador como código inacessível.Essa é a maneira mais elegante e também mais simples de sair dessa situação feia.
fonte
throw reportAndTransform(e)
um par de minutos depois que eu postei a minha própria resposta. Talvez você tenha modificado sua pergunta independentemente, e peço desculpas antecipadamente, se assim for, mas, caso contrário, dar um pouco de crédito é algo que as pessoas costumam fazer.throw reportAndTransform(e)
. Muitos padrões de design são patches sem recursos de idioma. Este é um deles.O
throw
provavelmente foi adicionado para contornar o "método deve retornar um valor" erro que ocorreria de outra maneira - os dados Java fluir analisador é bastante inteligente para compreender que nãoreturn
é necessário depois de umthrow
, mas não após o seu costumerethrow()
método, e não há nenhuma@NoReturn
anotação que você poderia usar para corrigir isso.No entanto, a criação de uma nova exceção em código inacessível parece supérflua. Eu simplesmente escreveria
return null
, sabendo que, de fato, nunca acontece.fonte
throw new...
linha e ela reclama de nenhuma declaração de retorno. É uma programação ruim? Existe alguma convenção sobre a substituição de uma declaração de retorno inacessível? Deixarei esta pergunta sem resposta por um tempo para incentivar mais comentários. No entanto, obrigado pela sua resposta.rethrow()
método oculta o fato de quecatch
está repetindo a exceção. Eu evitaria fazer algo assim. Além disso, é claro,rethrow()
na verdade , pode falhar em repetir a exceção; nesse caso, algo mais acontece.return null
. Com a exceção, se alguém no futuro quebrar o código para que a última linha se torne acessível, ficará imediatamente claro quando a exceção for lançada. Se preferirreturn null
, agora você tem umnull
valor flutuando no aplicativo que não era esperado. Quem sabe onde na aplicaçãoNullPointerException
surgirá? A menos que você tenha sorte e seja imediatamente após a chamada para essa função, será muito difícil rastrear o problema real.return null
é provável que oculte o bug porque você não pode distinguir os outros casos em que ele retornanull
desse bug.o
A declaração deixa claro para uma PESSOA que está lendo o código, o que está acontecendo, por isso é muito melhor do que retornar nulo, por exemplo. Também facilita a depuração se o código for alterado de maneira inesperada.
No entanto,
rethrow(e)
apenas parece errado! Então, no seu caso , acho que refatorar o código é uma opção melhor. Veja as outras respostas (eu gosto mais do coredump) para saber como resolver seu código.fonte
Não sei se existe uma convenção.
De qualquer forma, outro truque seria fazer o seguinte:
Permitindo isso:
E não
RuntimeException
é mais necessário artificial . Mesmo querethrow
nunca realmente retorne qualquer valor, é bom o suficiente para o compilador agora.Ele retorna um valor na teoria (assinatura do método) e, em seguida, fica isento de fazê-lo, pois gera uma exceção.
Sim, pode parecer estranho, mas, novamente - jogar um fantasma
RuntimeException
ou retornar nulos que nunca serão vistos neste mundo - também não é exatamente uma coisa de beleza.Buscando a legibilidade, você pode renomear
rethrow
e ter algo como:fonte
Se você remover o
try
bloco completamente, não precisará dorethrow
ou dothrow
. Este código faz exatamente a mesma coisa que o original:Não deixe que o fato de ser de desenvolvedores mais experientes o engane. Isso é código podre, e acontece o tempo todo. Apenas conserte.
Edição: Eu li errado
rethrow(e)
como simplesmente re-lançando a exceçãoe
. Se esserethrow
método realmente faz algo diferente de repetir a exceção, livrar-se dela altera a semântica desse método.fonte
catch(Exception)
cheiro odioso do código.rethrow
seja inútil (e esse não é o ponto aqui), mas você não pode simplesmente se livrar do código que não gosta e dizer que é equivalente ao original quando claramente não é. Existem efeitos colaterais narethrow
função personalizada .