Czy łapanie Throwable to zła praktyka?

Odpowiedzi:

104

Musisz być jak najbardziej konkretny. W przeciwnym razie nieprzewidziane błędy mogą zniknąć w ten sposób.

Poza tym Throwableokładki Errorteż i to zwykle nie ma sensu . Nie chcesz tego złapać / obsłużyć, chcesz, aby twój program natychmiast umarł, abyś mógł go poprawnie naprawić.

BalusC
źródło
38
Są sytuacje, w których wychwycenie błędu i kontynuowanie jest właściwe. Np. W serwlecie, jeśli wywołasz błąd OutOfMemoryError, ponieważ zdarzy się, że określone żądanie zjada całą pamięć, możesz spróbować kontynuować, ponieważ obiekty będą GC po obsłużeniu żądania. To samo dotyczy błędu asercji. Nie zamykasz aplikacji, ponieważ coś poszło nie tak w żądaniu.
gawi
7
Skąd wiesz, co zostało przydzielone, a co nie było przed OOME? Wszystkie zakłady są odrzucane, gdy to dostaniesz, nawet w kontenerze J2EE, takim jak Tomcat lub JBoss.
bmauter
10
Mieliśmy NoSuchMethodError i na szczęście nie wyeliminowaliśmy wszystkich naszych klientów, wyłączając serwer, tak jak stało się to dwa tygodnie po wdrożeniu. to znaczy. Zawsze łapiemy Throwable i dokładamy wszelkich starań, aby obsłużyć i wysłać błąd do klienta. W końcu istnieje wiele rodzajów błędów, które można naprawić, ponieważ mogą one dotyczyć tylko 1 na 1000 klientów.
Dean Hiller,
10
" chcesz, aby program umarł natychmiast, abyś mógł go poprawnie naprawić " => jeśli program umiera, skąd wiesz, co się stało? Catching Throwable / Error to log the problem is rozsądne do zrobienia ...
assylias
3
@assylias Samodzielna aplikacja zgłosi błąd krytyczny do stderr.
Philip Whitehouse,
36

To jest zły pomysł. W rzeczywistości nawet łowienie Exceptionjest zwykle złym pomysłem. Rozważmy przykład:

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

Teraz powiedzmy, że getUserInput () blokuje się na chwilę, a inny wątek zatrzymuje twój wątek w najgorszy możliwy sposób (wywołuje thread.stop ()). Twój blok catch wykryje ThreadDeathbłąd. To jest bardzo złe. Zachowanie kodu po przechwyceniu tego wyjątku jest w dużej mierze niezdefiniowane.

Podobny problem występuje przy przechwytywaniu wyjątku. Może getUserInput()nie powiodło się z powodu wyjątku InterruptException lub wyjątku odmowy uprawnień podczas próby rejestrowania wyników lub wszelkiego rodzaju innych niepowodzeń. Nie masz pojęcia, co poszło nie tak, ponieważ z tego powodu nie masz również pojęcia, jak rozwiązać problem.

Masz trzy lepsze opcje:

1 - Wyłap dokładnie wyjątek (y), z którymi umiesz sobie poradzić:

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

2 - Ponownie wyświetl każdy wyjątek, na który napotkasz i nie wiesz, jak sobie poradzić:

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

3 - Użyj końcowego bloku, aby nie pamiętać o ponownym rzucie:

 Resources r = null;
 try {
      r = allocateSomeResources();
      doSomething(r);
 } finally {
     if(r!=null) cleanUpResources(r);
 }
Brandon Yarbrough
źródło
4
+1 za stwierdzenie, że nawet złapanie Wyjątku nie jest dobre. Przynajmniej istnieje wyjątek ThreadInterruptedException, który wymaga szczególnej uwagi (w skrócie - po jego złapaniu należy ustawić stan przerwania wątku z powrotem na
``
Wiem, że służy to tylko do zilustrowania słów, ale myślę, że możesz sprawdzić za pomocą wyrażenia regularnego, czy dane wprowadzone przez użytkownika są alfanumeryczne lub jakiego formatu potrzebujesz i nie używaj za każdym razem spróbuj złapać wszędzie.
amdev
Skąd mam wiedzieć, czy jest błąd / rzucany przedmiot, jeśli go nie złapię? Nie widziałem nic w dziennikach. Aplikacja Java EE. Po prostu spędziłem kilka dni, nie wiedząc, w czym jest problem, dopóki nie dodałem tego haczyka.
Philip Rego
2
Opcja nr 2 uważam za złą praktykę. Wyobraź sobie 10 połączonych połączeń z logiem i powtórzeniem. Jeśli spojrzysz na plik dziennika, nie będziesz zadowolony. Wyjątek jest rejestrowany 10 razy, przez co dzienniki są bardzo trudne do odczytania. IMHO znacznie lepiej jest zrobić throw new Exception("Some additional info, eg. userId " + userId, e);. Zostanie to zarejestrowane w jednym ładnym wyjątku z 10 przyczynami.
Petr Újezdský
21

Należy również pamiętać, że złapanie Throwablemożna również złapać, InterruptedExceptionco wymaga specjalnego traktowania. Aby uzyskać więcej informacji, zobacz Radzenie sobie z InterruptedException .

Jeśli chcesz złapać tylko niezaznaczone wyjątki, możesz również rozważyć ten wzorzec

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

W ten sposób, gdy zmodyfikujesz kod i dodasz wywołanie metody, które może zgłosić zaznaczony wyjątek, kompilator przypomni Ci o tym, a następnie będziesz mógł zdecydować, co zrobić w tym przypadku.

gawi
źródło
14

bezpośrednio z javadoc klasy Error (która zaleca, aby ich nie łapać):

 * 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
źródło
13

Nie jest to zła praktyka, jeśli absolutnie nie można wyrzucić wyjątku z metody.

To zła praktyka, jeśli naprawdę nie możesz sobie poradzić z wyjątkiem. Lepiej jest dodać „throws” do sygnatury metody niż po prostu złapać i ponownie rzucić lub, co gorsza, zawinąć ją w RuntimeException i ponownie wyrzucić.

duffymo
źródło
10
Całkowicie się zgadzam - istnieją absolutnie uzasadnione przypadki obsługi wszystkich Throwableinstancji - np. W przypadku niestandardowego rejestrowania wyjątków.
Yuriy Nakonechnyy,
9

Łapanie Throwable jest czasami konieczne, jeśli używasz bibliotek, które z nadmiernym entuzjazmem zgłaszają błędy, w przeciwnym razie twoja biblioteka może zabić twoją aplikację.

Jednak w takich okolicznościach najlepiej byłoby określić tylko konkretne błędy generowane przez bibliotekę, a nie wszystkie Throwables.

DNA
źródło
12
A może skorzystasz z lepiej napisanej biblioteki?
Raedwald
6
Rzeczywiście, jeśli masz wybór ;-)
DNA
to jest największy problem z łapaniem przedmiotów do rzucania i ich ponownym rzucaniem. to naprawdę uniemożliwia interfejs dla wszystkich metod w stosie. Albo będą musieli poradzić sobie z rzucanym przedmiotem, albo mają bezużyteczny podpis rzucany, z którym inni będą musieli sobie poradzić.
Andrew Norman
6

Throwable to podstawowa klasa dla wszystkich klas, którą można rzucić (nie tylko wyjątki). Niewiele możesz zrobić, jeśli złapiesz OutOfMemoryError lub KernelError (zobacz Kiedy przechwycić java.lang.Error? )

wyłapywanie wyjątków powinno wystarczyć.

ic3
źródło
5

zależy to od twojej logiki lub być bardziej szczegółowym od twoich opcji / możliwości. Jeśli jest jakiś wyjątek, na który możesz zareagować w znaczący sposób, możesz go najpierw złapać i zrobić.

Jeśli nie ma i jesteś pewien, że zrobisz to samo dla wszystkich wyjątków i błędów (na przykład wyjście z komunikatem o błędzie), to złapanie przedmiotu rzucanego nie jest problemem.

Zwykle pierwszy przypadek jest prawidłowy i nie złapiesz przedmiotu do rzucania. Ale wciąż jest wiele przypadków, w których złapanie działa dobrze.

b. buchhold
źródło
4

Chociaż jest to opisywane jako bardzo zła praktyka, czasami możesz znaleźć rzadkie przypadki, że nie tylko użyteczne, ale także obowiązkowe. Oto dwa przykłady.

W aplikacji internetowej, w której musisz pokazać użytkownikowi pełną stronę błędu. Ten kod zapewnia, że ​​tak się stanie, ponieważ jest on try/catchobszerny wokół wszystkich uchwytów żądań (serwletów, akcji rozpórek lub dowolnego kontrolera ...)

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

}

Jako inny przykład rozważmy klasę usług, która obsługuje transfer środków. Ta metoda zwraca wartość, TransferReceiptjeśli transfer został wykonany lub NULLnie mógł.

String FoundtransferService.doTransfer( fundtransferVO);

Teraz obrazując, otrzymujesz Listprzelewy środków od użytkownika i musisz skorzystać z powyższej usługi, aby wykonać je wszystkie.

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

Ale co się stanie, jeśli zdarzy się jakikolwiek wyjątek? Nie powinieneś przerywać, ponieważ jeden transfer mógł się udać, a drugi nie, powinieneś kontynuować przez wszystkich użytkowników Listi pokazywać wynik dla każdego transferu. Więc kończysz z tym kodem.

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

Możesz przeglądać wiele projektów open source, aby zobaczyć, że throwablejest on naprawdę buforowany i obsługiwany. Na przykład o to poszukiwanie tomcat, struts2oraz 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
źródło
1
Widziałem kod w tych linkach. Rzut to nie tylko ten, który jest łapany! Przed Throwable są też inne wyjątki.
programista1011
@ developer101 oczywiście, ale łapią throwable, o co chodzi w tym pytaniu
Alireza Fattahi
4

Pytanie jest trochę niejasne; pytasz „czy można złapać Throwable”, czy „czy można złapać Throwablei nic nie robić”? Wiele osób odpowiedziało na to drugie pytanie, ale to kwestia poboczna; W 99% przypadków nie należy „konsumować” ani odrzucać wyjątku, niezależnie od tego, czy jest on łapany, ThrowableczyIOException czy cokolwiek innego.

Jeśli propagujesz wyjątek, odpowiedź (podobnie jak odpowiedź na wiele pytań) brzmi „to zależy”. To zależy od tego, co robisz z wyjątkiem - dlaczego to łapiesz.

Dobrym przykładem tego, dlaczego chcesz przechwycić, Throwablejest zapewnienie pewnego rodzaju czyszczenia, jeśli wystąpi jakikolwiek błąd. Na przykład w JDBC, jeśli wystąpi błąd podczas transakcji, chcesz wycofać transakcję:

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

Zauważ, że wyjątek nie jest odrzucany, ale propagowany.

Ale ogólnie rzecz biorąc, łapanie, Throwableponieważ nie masz powodu i jesteś zbyt leniwy, aby zobaczyć, które konkretne wyjątki są rzucane, jest kiepską formą i złym pomysłem.

Garret Wilson
źródło
1

Ogólnie rzecz biorąc, chcesz uniknąć łapania Errors, ale przychodzą mi do głowy (przynajmniej) dwa konkretne przypadki, w których jest to właściwe:

  • Chcesz zamknąć aplikację w odpowiedzi na błędy, zwłaszcza AssertionError co w innym przypadku jest nieszkodliwe.
  • Czy implementujesz mechanizm buforowania wątków podobny do ExecutorService.submit (), który wymaga przekazania wyjątków z powrotem do użytkownika, aby mógł go obsłużyć.
Gili
źródło
0

Jeśli używamy przedmiotu miotanego , obejmuje on również błąd i to wszystko.

Przykład.

    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();
}

}

Wynik:

java.lang.ArithmeticException: / by zero
at com.infy.test.ExceptionTest.m1(ExceptionTest.java:12)
at com.infy.test.ExceptionTest.main(ExceptionTest.java:25)
VicXj
źródło
0

Rzut jest superklasą wszystkich błędów i wyjątków. Jeśli użyjesz Throwable w klauzuli catch, nie tylko wyłapie wszystkie wyjątki, ale także wyłapie wszystkie błędy. JVM generuje błędy w celu wskazania poważnych problemów, które nie są przeznaczone do obsługi przez aplikację. Typowe przykłady to OutOfMemoryError lub StackOverflowError. Obie są spowodowane sytuacjami, które są poza kontrolą aplikacji i nie można ich obsłużyć. Więc nie powinieneś łapać Throwables, chyba że jesteś całkiem pewien, że będzie to tylko wyjątek znajdujący się w Throwable.

Sidath Bhanuka Randeniya
źródło
-1

Chociaż ogólnie złą praktyką jest łapanie Throwable (jak wyjaśniono w licznych odpowiedziach na to pytanie), scenariusze, w których łapanie Throwable jest przydatne, są dość powszechne. Pozwólcie, że wyjaśnię jeden taki przypadek, który wykorzystuję w pracy, na uproszczonym przykładzie.

Rozważ metodę, która polega na dodaniu dwóch liczb, a po pomyślnym dodaniu wysyła alert e-mail do określonych osób. Załóżmy, że zwracany numer jest ważny i używany przez metodę wywołującą.

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
    }
}

Kod służący do wysyłania alertów e-mailowych odczytuje wiele konfiguracji systemu, dlatego z tego bloku kodu może być wyrzucanych wiele wyjątków. Ale nie chcemy, aby jakikolwiek wyjątek napotkany podczas wysyłania alertu był propagowany do metody wywołującej, ponieważ ta metoda jest po prostu związana z sumą dwóch wartości całkowitych, które zapewnia. W związku z tym kod do wysyłania alertów e-mailowych jest umieszczony w try-catchbloku, w którym Throwablejest przechwytywany, a wszelkie wyjątki są jedynie rejestrowane, co pozwala na kontynuowanie pozostałej części przepływu.

CodeNewbie
źródło
Spróbowałbym tego uniknąć, mając wątek poświęcony zadaniu (z kolejką) wysyłania e-maili.
boumbh
Sugerowałbym, że to nadal zły kod. Złap Exceptions jak najbardziej, ale nie Throwable.
andrewf