2013-02-26 13 views
18

Czy to bezpieczne:Czy można bezpiecznie przechowywać wystąpienie wyjątku i ponownie go użyć?

public class Widget { 

    private static final IllegalStateException LE_EXCEPTION 
      = new IllegalStateException("le sophisticated way"); 

    ... 

    public void fun() { 
     // some logic here, that may throw 
     throw LE_EXCEPTION; 
    } 

    .... 
} 
  1. zachować wystąpienie wyjątku
  2. wykorzystać go w razie potrzeby (rzut)

zamiast rzucania new wyjątek za każdym razem?

jestem zainteresowany, czy jest to bezpieczne

Przez bezpieczny Znaczy: nie uszkodzenie zawartości pamięci, bez dodatkowych wyjątki rzucane przez JVM, przez brak zajęcia, ma złych klas załadowane (...). Uwaga: wyjątek zostanie zgłoszony przez sieć (zdalne).

Inne numery (czytelność, koszt przechowywania instancji) nie są ważne.

+2

Myślę, że jest zbędny. chyba że kodowanie wyrzuca to bardzo często – PermGenError

+0

Zawsze ma wystąpienie wyjątku, nawet twój kod nie zgłasza wyjątku – nav0611

Odpowiedz

17

To zależy od definicji "bezpieczne". Wyjątek da mylący ślad stosu, którego nie nazwałbym "bezpiecznym".Rozważmy:

public class ThrowTest { 
    private static Exception e = new Exception("t1"); // Line 2 

    public static final void main(String[] args) { 
     ThrowTest tt; 

     tt = new ThrowTest(); 
     try { 
      tt.t1(); 
     } 
     catch (Exception ex) { 
      System.out.println("t1:"); 
      ex.printStackTrace(System.out); 
     } 
     try { 
      tt.t2();         // Line 16 
     } 
     catch (Exception ex) { 
      System.out.println("t2:"); 
      ex.printStackTrace(System.out); 
     } 
    } 

    private void t1() 
    throws Exception { 
     throw this.e; 
    } 

    private void t2() 
    throws Exception { 
     throw new Exception("t2");     // Line 31 
    } 
} 

który ma z tego wyjścia:

$ java ThrowTest 
t1: 
java.lang.Exception: t1 
    at ThrowTest.<clinit>(ThrowTest.java:2) 
t2: 
java.lang.Exception: t2 
    at ThrowTest.t2(ThrowTest.java:31) 
    at ThrowTest.main(ThrowTest.java:16) 

Zauważ, że metoda t1 jest całkowicie brakuje śladu stosu w pierwszym przypadku testowego. W ogóle nie ma przydatnych informacji kontekstowych.

Teraz może używać fillInStackTrace wypełnienie tej informacji tuż przed rzutem:

this.e.fillInStackTrace(); 
throw this.e; 

... ale to właśnie czyni pracę dla siebie (praca, którą będzie zapomnieć czasami). Po prostu nie ma w tym żadnej korzyści. I nie wszystkie wyjątki pozwalają to zrobić (niektóre wyjątki powodują, że ślad stosu jest tylko do odczytu).


Mówiłeś gdzie indziej w komentarzach, że jest to, aby uniknąć „powielania kodu.” Jesteś znacznie lepiej pełniąca funkcję wyjątkiem producentów:

private IllegalStateException buildISE() { 
    return new IllegalStateException("le sophisticated way"); 
} 

(Może być static final jeśli lubisz).

A potem rzuca go tak:

throw buildISE(); 

który unika duplikacja kodu bez wprowadzających w błąd śladów stosu i niepotrzebnych wystąpień wyjątków.

Oto, co wygląda na to, że stosuje się do powyższego:

public class ThrowTest { 

    public static final void main(String[] args) { 
     ThrowTest tt; 

     tt = new ThrowTest(); 
     try { 
      tt.t1();         // Line 8 
     } 
     catch (Exception ex) { 
      System.out.println("t1:"); 
      ex.printStackTrace(System.out); 
     } 
     try { 
      tt.t2();         // Line 15 
     } 
     catch (Exception ex) { 
      System.out.println("t2:"); 
      ex.printStackTrace(System.out); 
     } 
    } 

    private static final Exception buildEx() { 
     return new Exception("t1");     // Line 24 
    } 

    private void t1() 
    throws Exception { 
     throw buildEx();        // Line 29 
    } 

    private void t2() 
    throws Exception { 
     throw new Exception("t2");      // Line 34 
    } 
} 
$ java ThrowTest 
t1: 
java.lang.Exception: t1 
    at ThrowTest.buildEx(ThrowTest.java:24) 
    at ThrowTest.t1(ThrowTest.java:29) 
    at ThrowTest.main(ThrowTest.java:8) 
t2: 
java.lang.Exception: t2 
    at ThrowTest.t2(ThrowTest.java:34) 
    at ThrowTest.main(ThrowTest.java:15)
+0

Podoba mi się twój pomysł. Czy możesz zapisać numery linii w swoim kodzie, aby można było łatwo zrozumieć tutaj stos, na SE? –

+0

Myślę, że pierwszy przykład na stosie był lepszy! –

+1

@JoshuaMN: Aktualizacja sprawia, że ​​bardziej przypomina ona proponowany mechanizm (wyjątek 'static'). Zaktualizowałem numery linii i przykład, jak funkcja budowania sprawia, że ​​ślad stosu jest bardziej czytelny. –

1

Myślę, że to jest złe, ponieważ zachęci Cię do używania wyjątków użycia do opisu normalnych sytuacji, a nie tylko wyjątkowych. Zobacz drugie wydanie Effective Java autorstwa J. Blocha, pozycja 57, strona 241.

Również zawsze trzymasz obiekt w stercie, więc nie jest to konieczne, ponieważ tworzenie obiektów jest bardzo szybkie w moderowanych maszynach JVM, a także po wyjątek jest rzucony, prawdopodobnie bardzo szybko zostaną zebrane śmieci.

Również twój kod może stać się bardzo mylący, co zwiększy koszty związane z czymś, co jest dość proste.

+0

Proszę pominąć styl kodowania.Potrzebuję instancji, aby ** uniknąć ** powielania kodu (muszę poradzić sobie z wieloma wyjątkami i wyrzucić inną) –

+0

@JoshuaMN powinieneś wyrzucić wyjątki tam, gdzie są potrzebne, jeśli chcesz, możesz wykreślić kod w metoda, która to robi, jeśli podejrzewasz, że twój typ wyjątku się zmieni, ale pamiętaj, aby zrobić to z niezaznaczonymi wyjątkami, aby zaoszczędzić wiele zmian w klauzulach catch/throws. Jeśli zgłoszenie wyjątku jest czymś "normalnym" dla twojej aplikacji, powinieneś pomyśleć o czymś innym, by zmodyfikować przepływ programu zamiast rzucać wyjątki, tak jak powiedziałem, wyjątki powinny być używane tylko w wyjątkowych sytuacjach. – comanitza

3

Kolejnym powodem, aby tego nie robić, jest to, że ślad stosu będzie niewłaściwy.

Bez względu na to, w którym miejscu w kodzie rzucisz wyjątek, po wydrukowaniu jego stosu, pokaże on wiersz, w którym został zainicjowany wyjątek, zamiast linii, w której jest wysyłany (w tym konkretnym przypadku zamiast tego oczekiwany Widget.fun() ślad stosu będzie zawierał Widget.<clinit>).

Więc ty (lub ktokolwiek użyje twojego kodu) nie będzie w stanie określić, gdzie leży błąd.

2

Uważa, że ​​może to powodować problemy podczas odczytu śledzenia stosu wyjątku. Śledzenie stosu jest wypełniane, gdy kod zgłasza wyjątek, ale jest przechowywany w wystąpieniu samego wyjątku. Jeśli dwa razy wyrzucisz to samo wystąpienie wyjątku, myślę, że getStackTrace() zwraca ostatni ślad stosu. Jeśli z jakiegoś powodu (na przykład w środowisku wielowątkowym) wyjątek jest wyrzucany dwa razy z różnych miejsc kodu, a następnie drukowany, ślad stosu od pierwszego rzutu może być nieprawidłowy.

Ponieważ nie ma powodu, aby ponownie używać wystąpień wyjątku, nie zalecam tego.

Jeśli chcesz użyć wyjątku jako rodzaju kontenera zawierającego dodatkowe informacje (np. Komunikat o błędzie, kod błędu itp.), Użyj wyjątku do serializacji: sklonuj go przed rzuceniem. Tak więc za każdym razem, gdy rzucisz unikalną instancję wyjątku, jego pola zostaną skopiowane z gotowego szablonu.

4

To nie jest bezpieczne, chyba że wyjątek jest niezmienna (tj enableSuppression = writableStackTrace = false).

Jeśli wyjątek nie jest niezmienny, można go zmodyfikować za pomocą modułu uruchamiającego - ustawienie nowego stosu lub dodanie pominiętego wyjątku. Jeśli jest wiele łapaczy próbujących zmodyfikować wyjątek, nastąpi chaos.

Zadziwiająco, Throwable jest w rzeczywistości bezpieczny dla wątków, ponieważ Bóg wie czego. Przynajmniej nie będzie katastrofalnej awarii, jeśli wyjątek zostanie zmodyfikowany przez wiele wątków. Ale wystąpi awaria logiki.

Przeciek pamięci jest również możliwy, jeśli aplikacja dodaje wyjątki z tłumionymi wyjątkami do tego wyjątku długiego okresu eksploatacji.

+0

Ta odpowiedź zawiera najważniejsze informacje. Dzięki! –

2

To nie jest bezpieczne, aby mieć ochotę na kod błędu. Utrzymuj to w prostocie, utrzymuj to oczywiste i nie pisz niczego, o co musisz zadać pytanie. Obsługa błędów nie jest miejscem, w którym mogą wystąpić dodatkowe błędy.

Powiązane problemy