É uma má prática pegar lançáveis?

Respostas:

104

Você precisa ser o mais específico possível. Caso contrário, bugs imprevistos podem se espalhar dessa maneira.

Além disso, Throwablecobre Errortambém e isso geralmente não tem ponto de retorno . Você não quer pegar / lidar com isso, você quer que seu programa morra imediatamente para que você possa corrigi-lo adequadamente.

BalusC
fonte
38
Existem situações em que capturar Error e continuar é apropriado. Ex: Em um servlet, se você enconter um OutOfMemoryError porque uma solicitação específica consumiu toda a memória, você pode tentar continuar, pois os objetos serão GC depois que a solicitação for tratada. O mesmo vale para um erro de asserção. Você não fecha um aplicativo porque algo deu errado em uma solicitação.
gawi
7
Como você sabe o que foi alocado e o que não foi antes do OOME? Todas as apostas estão canceladas assim que você conseguir isso, mesmo dentro de um contêiner J2EE como Tomcat ou JBoss.
bmauter
10
Tivemos NoSuchMethodError e felizmente não eliminamos todos os nossos clientes desligando o servidor, como aconteceu duas semanas após a implantação. ie. Sempre temos um catchall pegando o Throwable e fazendo o melhor para tratar e enviar o erro ao cliente. Afinal, existem muitos tipos de erros que podem ser recuperados, pois podem afetar apenas 1 em cada 1000 clientes.
Dean Hiller
10
" você deseja que o seu programa morra imediatamente para que possa corrigi-lo corretamente " => se o seu programa morrer, como você sabe o que aconteceu? Catching Throwable / Error para registrar o problema é uma coisa razoável a se fazer ...
assylias
3
@assylias Um aplicativo independente lançará um erro fatal para o stderr.
Philip Whitehouse
36

Esta é uma má ideia. Na verdade, mesmo pegar Exceptioné geralmente uma má ideia. Vamos considerar um exemplo:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(Throwable e) {
    inputNumber = 10; //Default, user did not enter valid number
}

Agora, digamos que getUserInput () bloqueie por um tempo e outra thread interrompa sua thread da pior maneira possível (ela chama thread.stop ()). Seu bloco catch irá pegar um ThreadDeatherro. Isso é muito ruim. O comportamento do seu código após capturar essa exceção é amplamente indefinido.

Um problema semelhante ocorre com a captura de exceção. Talvez tenha getUserInput()falhado devido a uma InterruptException ou uma exceção de permissão negada ao tentar registrar os resultados, ou todos os tipos de outras falhas. Você não tem ideia do que deu errado, por isso também não tem ideia de como consertar o problema.

Você tem três opções melhores:

1 - Capture exatamente a (s) exceção (ões) que você sabe como lidar:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(ParseException e) {
    inputNumber = 10; //Default, user did not enter valid number
}

2 - Relembre qualquer exceção que você encontrar e não souber como lidar:

try {
    doSomethingMysterious();
} catch(Exception e) {
    log.error("Oh man, something bad and mysterious happened",e);
    throw e;
}

3 - Use um bloco finally para que você não precise se lembrar de relançar:

 Resources r = null;
 try {
      r = allocateSomeResources();
      doSomething(r);
 } finally {
     if(r!=null) cleanUpResources(r);
 }
Brandon Yarbrough
fonte
4
+1 por afirmar que mesmo capturar Exceção não é bom. Pelo menos há um ThreadInterruptedException que requer cuidado especial (em resumo - depois de capturá-lo, você deve definir o status de interrupção do thread de volta para 'true')
Kirill Gamazkov
Eu sei que é apenas para ilustrar suas palavras, mas acho que você pode verificar com um regex se sua entrada do usuário é alfanumérica ou qual formato você precisa e não use try catch em todos os lugares sempre.
amdev
Como posso saber se há um erro / lançável se não o detectar? Não vi nada nos logs. Aplicativo Java EE. Passei dias preso sem saber qual era o problema até que adicionei esta captura.
Philip Rego
2
Eu considero a opção nº 2 uma má prática. Imagine 10 chamadas encadeadas com registro e relançamento. Se você olhar o arquivo de log, não ficará feliz. A exceção é registrada 10 vezes, tornando os logs muito difíceis de ler. IMHO muito melhor é fazer throw new Exception("Some additional info, eg. userId " + userId, e);. Isso será registrado em uma boa exceção com 10 causas.
Petr Újezdský de
21

Também esteja ciente de que quando você pega Throwable, você também pode pegar o InterruptedExceptionque requer um tratamento especial. Consulte Lidando com InterruptedException para obter mais detalhes.

Se você deseja apenas capturar exceções não verificadas, também pode considerar este padrão

try {
   ...
} catch (RuntimeException exception) {
  //do something
} catch (Error error) {
  //do something
}

Dessa forma, quando você modifica seu código e adiciona uma chamada de método que pode lançar uma exceção verificada, o compilador irá lembrá-lo disso e então você poderá decidir o que fazer neste caso.

gawi
fonte
14

direto do javadoc da classe Error (que recomenda não capturá-los):

 * An <code>Error</code> is a subclass of <code>Throwable</code> 
 * that indicates serious problems that a reasonable application 
 * should not try to catch. Most such errors are abnormal conditions. 
 * The <code>ThreadDeath</code> error, though a "normal" condition,
 * is also a subclass of <code>Error</code> because most applications
 * should not try to catch it. 

 * A method is not required to declare in its <code>throws</code> 
 * clause any subclasses of <code>Error</code> that might be thrown 
 * during the execution of the method but not caught, since these 
 * errors are abnormal conditions that should never occur. 
 *
 * @author  Frank Yellin
 * @version %I%, %G%
 * @see     java.lang.ThreadDeath
 * @since   JDK1.0
Andrew Norman
fonte
13

Não é uma prática ruim se você absolutamente não pode ter uma exceção borbulhando de um método.

É uma prática ruim se você realmente não pode lidar com a exceção. Melhor adicionar "throws" à assinatura do método do que apenas pegar e relançar ou, pior, envolvê-lo em uma RuntimeException e relançar.

duffymo
fonte
10
Concordo totalmente - há casos absolutamente legítimos para lidar com todas as Throwableinstâncias - por exemplo, para registro de exceção personalizado.
Yuriy Nakonechnyy
9

Às vezes, o Catching Throwable é necessário se você estiver usando bibliotecas que geram erros com muito entusiasmo, caso contrário, sua biblioteca pode matar seu aplicativo.

No entanto, seria melhor nessas circunstâncias especificar apenas os erros específicos lançados pela biblioteca, em vez de todos os Throwables.

DNA
fonte
12
Ou usar uma biblioteca melhor escrita?
Raedwald
6
Na verdade, se você tiver escolha ;-)
DNA
esse é o maior problema em pegar objetos que podem ser jogados e voltar a lançá-los. realmente torna uma interface impossível para todos os métodos anteriores na pilha. Eles têm que lidar com um lance que pode ser jogado ou ter uma assinatura de lance que pode ser jogado inutilizável, com a qual outros terão que lidar.
Andrew Norman
6

Throwable é a classe base para todas as classes que podem ser lançadas (não apenas exceções). Há pouco que você pode fazer se capturar um OutOfMemoryError ou KernelError (consulte Quando capturar java.lang.Error? )

Capturar exceções deve ser o suficiente.

ic3
fonte
5

depende da sua lógica ou para ser mais específico às suas opções / possibilidades. Se houver alguma exceção específica à qual você possa reagir de uma forma significativa, você pode pegá-la primeiro e fazer isso.

Se não houver e você tiver certeza de que fará a mesma coisa para todas as exceções e erros (por exemplo, sair com uma mensagem de erro), então não será problema pegar o que pode ser jogado.

Normalmente, o primeiro caso se mantém e você não pegaria o que pode ser jogado. Mas ainda existem muitos casos em que capturá-lo funciona bem.

b.buchhold
fonte
4

Embora seja descrito como uma prática muito ruim, às vezes você pode encontrar casos raros em que não só é útil, mas também obrigatório. Aqui estão dois exemplos.

Em um aplicativo da web onde você deve mostrar uma página de erro completa significativa para o usuário. Este código garante que isso aconteça, pois é muito grande em try/catchtorno de todos os seus manipuladores de solicitação (servlets, ações de struts ou qualquer controlador ...)

try{
     //run the code which handles user request.
   }catch(Throwable ex){
   LOG.error("Exception was thrown: {}", ex);
     //redirect request to a error page. 
 }

}

Como outro exemplo, considere que você tem uma classe de serviço que atende a negócios de transferência de fundos. Este método retorna um TransferReceiptse a transferência foi feita ou NULLnão.

String FoundtransferService.doTransfer( fundtransferVO);

Agora, imaginando que você obtenha uma Listsérie de transferências de fundos do usuário e você deve usar o serviço acima para fazer todas elas.

for(FundTransferVO fundTransferVO : fundTransferVOList){
   FoundtransferService.doTransfer( foundtransferVO);
}

Mas o que acontecerá se alguma exceção acontecer? Você não deve parar, pois uma transferência pode ter sido bem-sucedida e outra não, você deve continuar com todos os usuários Liste mostrar o resultado a cada transferência. Então você acaba com este código.

for(FundTransferVO fundTransferVO : fundTransferVOList){
    FoundtransferService.doTransfer( foundtransferVO);
 }catch(Throwable ex){
    LOG.error("The transfer for {} failed due the error {}", foundtransferVO, ex);
  }
}

Você pode navegar em muitos projetos de código aberto para ver se o throwableestá realmente armazenado em cache e gerenciado. Por exemplo, aqui está uma pesquisa de tomcat, struts2e primefaces:

https://github.com/apache/tomcat/search?utf8=%E2%9C%93&q=catch%28Throwable https://github.com/apache/struts/search?utf8=%E2%9C%93&q=catch % 28Throwable https://github.com/primefaces/primefaces/search?utf8=%E2%9C%93&q=catch%28Throwable

Alireza Fattahi
fonte
1
Viu o código nesses links. Jogável não é só aquele que é pego! Existem outras exceções capturadas também antes de Throwable.
desenvolvedor1011
@ developer101 é claro, mas eles pegam throwable, que é sobre o que é esta pergunta
Alireza Fattahi
4

A pergunta é um pouco vaga; você está perguntando "está tudo bem em pegar Throwable" ou "está tudo bem em pegar um Throwablee não fazer nada"? Muitas pessoas aqui responderam a última pergunta, mas isso é um problema secundário; 99% do tempo que você não deve "consumir" ou descartar a exceção, se você está pegando Throwableou IOExceptionou qualquer outra coisa.

Se você propagar a exceção, a resposta (como a resposta a tantas perguntas) é "depende". Depende do que você está fazendo com a exceção - por que você está capturando.

Um bom exemplo de por que você deseja capturar Throwableé fornecer algum tipo de limpeza se houver algum erro. Por exemplo, em JDBC, se ocorrer um erro durante uma transação, você deseja reverter a transação:

try {
  
} catch(final Throwable throwable) {
  connection.rollback();
  throw throwable;
}

Observe que a exceção não é descartada, mas propagada.

Mas, como regra geral, pegar Throwableporque você não tem um motivo e está com preguiça de ver quais exceções específicas estão sendo lançadas é uma forma inadequada e uma má ideia.

Garret Wilson
fonte
1

De modo geral, você deseja evitar pegar Errors, mas posso pensar em (pelo menos) dois casos específicos em que seja apropriado fazer isso:

  • Você deseja encerrar o aplicativo em resposta a erros, especialmente AssertionError que, de outra forma, são inofensivos.
  • Você está implementando um mecanismo de agrupamento de threads semelhante a ExecutorService.submit () que exige que você encaminhe exceções de volta ao usuário para que ele possa lidar com isso.
Gili
fonte
0

Se usarmos o que pode ser jogado , ele também cobre o erro e é isso.

Exemplo.

    public class ExceptionTest {
/**
 * @param args
 */
public static void m1() {
    int i = 10;
    int j = 0;
    try {
        int k = i / j;
        System.out.println(k);
    } catch (Throwable th) {
        th.printStackTrace();
    }
}

public static void main(String[] args) {
    m1();
}

}

Resultado:

java.lang.ArithmeticException: / by zero
at com.infy.test.ExceptionTest.m1(ExceptionTest.java:12)
at com.infy.test.ExceptionTest.main(ExceptionTest.java:25)
VicXj
fonte
0

Throwable é a superclasse de todos os erros e exceções. Se você usar Throwable em uma cláusula catch, ele não apenas capturará todas as exceções, mas também todos os erros. Erros são lançados pela JVM para indicar problemas sérios que não devem ser tratados por um aplicativo. Exemplos típicos disso são OutOfMemoryError ou StackOverflowError. Ambos são causados ​​por situações que estão fora do controle do aplicativo e não podem ser tratadas. Portanto, você não deve capturar Throwables a menos que esteja bastante confiante de que será apenas uma exceção dentro do Throwable.

Sidath Bhanuka Randeniya
fonte
-1

Embora seja geralmente uma prática ruim capturar Throwable (conforme elucidado pelas inúmeras respostas a esta pergunta), os cenários em que a captura Throwableé útil são bastante comuns. Deixe-me explicar um desses casos que uso no meu trabalho, com um exemplo simplificado.

Considere um método que realiza a adição de dois números e, após a adição bem-sucedida, envia um alerta por e-mail para certas pessoas. Suponha que o número retornado seja importante e usado pelo método de chamada.

public Integer addNumbers(Integer a, Integer b) {
    Integer c = a + b;          //This will throw a NullPointerException if either 
                                //a or b are set to a null value by the
                                //calling method
    successfulAdditionAlert(c);
    return c;
}

private void successfulAdditionAlert(Integer c) {
    try {
        //Code here to read configurations and send email alerts.
    } catch (Throwable e) {
        //Code to log any exception that occurs during email dispatch
    }
}

O código para enviar alertas de e-mail lê muitas configurações do sistema e, portanto, pode haver uma variedade de exceções lançadas a partir desse bloco de código. Mas não queremos que nenhuma exceção encontrada durante o despacho do alerta se propague para o método do chamador, já que esse método está simplesmente preocupado com a soma dos dois valores Inteiros que ele fornece. Assim, o código para enviar alertas de e-mail é colocado em um try-catchbloco, onde Throwableé capturado e quaisquer exceções são meramente registradas, permitindo que o restante do fluxo continue.

CodeNewbie
fonte
Eu tentaria evitar isso tendo um tópico dedicado ao trabalho (com uma fila) de envio de e-mails.
boumbh
Eu sugiro que este ainda é um código ruim. Pegue Exceptiontudo por todos os meios, mas não Throwable.
andrewf