2009-10-30 28 views
11

(Późno edit:. To pytanie mam nadzieję być nieaktualne gdy Java 7 pochodzi ze względu na "final rethrow" feature który seems like it will be added)Jak bezpieczne jest moje bezpieczne ponowne rzucenie?


Dość często znajdę się w sytuacji wygląda tak:

 
    do some initialization 
    try { 
     do some work 
    } catch any exception { 
     undo initialization 
     rethrow exception 
    } 

W języku C# można to zrobić tak:

InitializeStuff(); 
try 
{ 
    DoSomeWork(); 
} 
catch 
{ 
    UndoInitialize(); 
    throw; 
} 

W przypadku języka Java nie ma dobrej zamiany, a od the proposal for improved exception handling was cut from Java 7 wygląda na to, że zajmie to najwyżej kilka lat, dopóki nie otrzymamy czegoś podobnego. Tak więc zdecydowałem się toczyć własne:

(Edit. Pół roku później, final rethrow is back, a przynajmniej tak się wydaje)

public final class Rethrow { 

    private Rethrow() { throw new AssertionError("uninstantiable"); } 

    /** Rethrows t if it is an unchecked exception. */ 
    public static void unchecked(Throwable t) { 
     if (t instanceof Error) 
      throw (Error) t; 
     if (t instanceof RuntimeException) 
      throw (RuntimeException) t; 
    } 

    /** Rethrows t if it is an unchecked exception or an instance of E. */ 
    public static <E extends Exception> void instanceOrUnchecked(
      Class<E> exceptionClass, Throwable t) throws E, Error, 
      RuntimeException { 
     Rethrow.unchecked(t); 
     if (exceptionClass.isInstance(t)) 
      throw exceptionClass.cast(t); 
    } 

} 

Typowe zastosowanie:

public void doStuff() throws SomeException { 
    initializeStuff(); 
    try { 
     doSomeWork(); 
    } catch (Throwable t) { 
     undoInitialize(); 
     Rethrow.instanceOrUnchecked(SomeException.class, t); 
     // We shouldn't get past the above line as only unchecked or 
     // SomeException exceptions are thrown in the try block, but 
     // we don't want to risk swallowing an error, so: 
     throw new SomeException("Unexpected exception", t); 
    } 
    private void doSomeWork() throws SomeException { ... } 
} 

jest to nieco rozwlekły, łapanie Throwable jest zwykle mile widziane, nie jestem zadowolony z użycia refleksji tylko po to, by wyrzucić wyjątek, i zawsze czuję się trochę nieswojo pisząc "to się nie wydarzy" komentarze, ale w praktyce to działa ell (lub przynajmniej wydaje się). Zastanawiam się:

  1. Czy mam jakieś usterki w moich metodach pomocy w rethrow? Jakieś przypadki narożne, które przegapiłem? (Wiem, że Throwable mogły zostać spowodowane przez coś tak poważnego, że moja undoInitialize zawiedzie, ale to jest OK.)
    • Czy ktoś już to wymyślił? Spojrzałem na Commons Lang's ExceptionUtils, ale to robi inne rzeczy.

Edit:

  • finally nie jest droid szukam. Interesuje mnie tylko robić rzeczy, gdy zostanie zgłoszony wyjątek.
  • Tak, wiem łapania Throwable jest wielki nie-nie, ale myślę, że to mniejsze zło tutaj w porównaniu do mająca trzy klauzule catch (dla Error, RuntimeException i SomeException, odpowiednio) z identycznym kodem.
  • Zauważ, że nie próbuję tłumić żadnych błędów - chodzi o to, że wszelkie wyjątki wyrzucone w bloku try będą nadal pojawiać się w stosie wywołań, gdy tylko przewinąłem kilka rzeczy.
+1

Masz dobre uzasadnienie dla łowienia Throwable. Nie pozwól, aby cię to zepsuło. To bardzo przypomina użycie GOTO, "Nigdy go nie używaj, chyba że musisz." –

Odpowiedz

5

Istnieje kilka sposobów, aby sobie z tym poradzić. Pierwsza to moje preferencje, jeśli nie musisz wiedzieć, czym był wyjątek.

boolean okay = false; 
try { 
    // do some work which might throw an exception 
    okay = true; 
} finally { 
    if (!okay) // do some clean up. 
} 

W niektórych przypadkach można zrobić to samo bez dodatkowej zmiennej, w zależności od tego, co robi blok try.

Druga opcja to hak, ale działa również. Sposób

try { 
    // do some work which might throw an exception 
} catch (Throwable t) { 
    // do something with t. 
    Thread.currentThread().stop(t); 
} 

ogranicznik (Throwable t) nie jest zatrzymywana w gwint, a nie powoduje nić wyjątek dalej przewidziane w niekontrolowany sposób.

Możesz użyć Unsafe.throwException() z odrobiną manipulowania i istnieje sposób, aby to zrobić z Generykami, o których zapomniałem.

1

Jeśli obawiasz się, że Twoja inicjalizacja się nie powiedzie, możesz po prostu umieścić ten kod w bloku finally, ponieważ, jeśli powinien on zostać wywołany w pewnym momencie, być może powinieneś zawsze posprzątać.

Nie mogę złapać Throwable jako niektórych wyjątków, które chcę obsłużyć, a niektóre po prostu loguję, ponieważ nie ma sensu przekazywać wyjątków, z których użytkownik nie może nic zrobić, takich jak NullPointerException.

Ale, nie wykazały co SomeException jest zdefiniowany jako, ale jeśli OutOfMemoryException jest wyrzucane, Twój Throwable będzie go złapać, ale to nie może być taki sam typ jak SomeException więc wrapper będą potrzebne w funkcji próbkowania , przynajmniej gdy spojrzę na metodę instanceOrUnchecked.

Możesz napisać test jednostkowy, wypróbować różne klasy wyjątków i sprawdzić, co działa lub nie działa zgodnie z oczekiwaniami, aby udokumentować oczekiwane zachowanie.

+0

'SomeException extends Exception'. Jeśli chodzi o uninitializing w 'finally' - chcę tylko, aby uninitialization się stało, jeśli wyjątek zostanie zgłoszony. Mógłbym oczywiście mieć flagę boolowską początkowo ustawioną na false, a następnie ustawić ją na true jako ostatnią rzecz w bloku 'try', ale jest to trochę uciążliwe (a jeśli ktoś doda kod po ustawieniu na true bez myślenia o obsługa wyjątków?). – gustafc

+0

Testowałem też i działa bardzo dobrze ... w każdych okolicznościach, o których myślałem, co tylko dowodzi, że udało mi się napisać kod, nie jestem wystarczająco inteligentny, aby się złamać =) – gustafc

+1

Czy przetestowałeś z klasą, która rozszerza Throwable, a nie taką, która rozszerza wyjątek? –

1

Alternatywą jest mieć fabrykę, która tworzy SomeException tylko wtedy, gdy przyczyną jest sprawdzana wyjątek:

public static SomeException throwException(String message, Throwable cause) throws SomeException { 
     unchecked(cause); //calls the method you defined in the question. 
     throw new SomeException(message, cause); 
    } 

Powodem umieścić w wartości zwracanej w metodzie jest tak, że klient może zrobić coś tak:

 catch (Throwable e) { 
     undoInitialize(); 
     throw SomeException.throwException("message", e); 
    } 

tak, że kompilator jest nabrać nie wymagające powrotu po instrukcji catch, czy metoda ma typ zwrotny, ale to wciąż zgłasza wyjątek, jeśli klient zapomniał umieścić rzut przed wywołaniem do metody fabrycznej.

Wadą tego przez twój kod jest to, że jest on mniej przenośny (działa dla wyjątku SomeException, ale nie dla SomeOtherException), ale może być ok, ponieważ nie będzie dla każdego typu wyjątku, który musisz mieć inicjowanie cofania.

Jeśli pasuje do twojego przypadku użycia, możesz umieścić zaznaczone połączenie w konstruktorze SomeException i mieć logikę dostępną dla wszystkich podklas, ale to musi pasować do twojego konkretnego projektu - nie byłoby to dobrym pomysłem w ogólnym przypadku, ponieważ zapobiega to wyjątkom w pakiecie.

 public SomeException(message, cause) { 
      super(message, unchecked(cause)); 
     } 

     private static Throwable unchecked(Throwable cause) { 
      if (cause instanceof Error) throw (Error) cause; 
      if (cause instanceof RuntimeException) throw (RuntimeException) cause; 
      return cause; 
     } 
+0

Funkcja fabryczna jest dobra, ale ponieważ mam hierarchię wyjątków (kilka klas rozszerzających 'SomeException'), potrzebuję czegoś bardziej elastycznego. Podoba mi się spryt z drugiej alternatywy, ale myślę, że może to zbyt sprytne - potencjał WTF dla programistów utrzymania ruchu wydaje się być wysoki. – gustafc