Czy używanie zagnieżdżonych bloków try-catch jest anty-wzorem?


95

Czy to jest antypattern? Czy jest to dopuszczalna praktyka?

    try {
        //do something
    } catch (Exception e) { 
        try {
            //do something in the same line, but being less ambitious
        } catch (Exception ex) {
            try {
                //Do the minimum acceptable
            } catch (Exception e1) {
                //More try catches?
            }
        }
    }

Czy możesz nam to uzasadnić? Dlaczego nie radzisz sobie z każdym rodzajem błędu w haczyku najwyższego poziomu?
— Morons,

2
Ostatnio widziałem ten rodzaj kodu, wykonywany przez niedoświadczonych programistów, którzy tak naprawdę nie wiedzą, co wywołują w blokach try, i nie chcą zawracać sobie głowy testowaniem kodu. W przykładzie kodu, który widziałem, była to ta sama operacja, ale wykonywana za każdym razem z parametrami rezerwowymi.
— Mister Smith,

@LokiAstari -Twoim przykładem jest próba w sekcji końcowej .. Gdzie nie ma połowu. Jest to zagnieżdżone w sekcji Try. Jest inaczej.
— Morons,

4
Dlaczego powinien to być anty-wzór?

2
+1 za „więcej próbować połowów?”
— JoelFan,

Odpowiedzi:


85

Czasami jest to nieuniknione, szczególnie jeśli kod odzyskiwania może wygenerować wyjątek.

Nie ładnie, ale czasem nie ma alternatywy.


17
@MisterSmith - Nie zawsze.
— Oded

4
Tak, to jest coś, co chciałem osiągnąć. Oczywiście w zagnieżdżonych instrukcjach try / catch pojawia się punkt, w którym wystarczy powiedzieć, że wystarczy. Robiłem argument za zagnieżdżeniem w przeciwieństwie do sekwencyjnego try / catch, mówiąc, że są sytuacje, w których chcesz, aby kod w drugiej próbie był wykonywany tylko wtedy, gdy pierwsza próba się przewróci.
— AndrewC

5
@MisterSmith: Wolę zagnieżdżone try-catch niż sekwencyjne try-catch, które są częściowo kontrolowane za pomocą zmiennych flagowych (jeśli byłyby funkcjonalnie takie same).
— FrustratedWithFormsDesigner

31
spróbuj {transaction.commit (); } catch {try {transaction.rollback (); } catch {poważnelogging ()} notsoseriouslogging (); } jest przykładem niezbędnego zagnieżdżonego try catch
— Thanos Papathanasiou,

3
Przynajmniej wyodrębnij blok catch do metody, chłopaki! Uczyńmy to przynajmniej czytelnym.
— Pan Cochese,

43

Nie sądzę, że jest to antypattern, po prostu powszechnie nadużywany.

Większość zagnieżdżonych prób połowu jest rzeczywiście możliwa do uniknięcia i brzydka, do diabła, zwykle jest to produkt młodszych programistów.

Ale są chwile, kiedy nie możesz nic na to poradzić.

try{
     transaction.commit();
   }catch{
     logerror();
     try{
         transaction.rollback(); 
        }catch{
         seriousLogging();
        }
   }

Będziesz także potrzebować dodatkowego bool, aby zaznaczyć nieudane wycofanie ...


19

Logika jest w porządku - w niektórych sytuacjach może być sensowne wypróbować podejście awaryjne, które samo w sobie może doświadczyć wyjątkowych wydarzeń ... stąd ten wzorzec jest prawie nieunikniony.

Sugeruję jednak, aby poprawić kod:

  • Byłaby wewnętrzną try ... catch bloki się na oddzielne funkcje np attemptFallbackMethodi attemptMinimalRecovery.
  • Dokładniej określ konkretne wychwycone typy wyjątków. Czy naprawdę oczekujesz jakiejkolwiek podklasy wyjątków, a jeśli tak, to czy naprawdę chcesz traktować je wszystkie w ten sam sposób?
  • Zastanów się, czy finallyblok może mieć więcej sensu - zwykle dzieje się tak w przypadku czegoś, co przypomina „kod czyszczenia zasobów”

14

W porządku. Refaktoryzacja, którą należy rozważyć, polega na wprowadzeniu kodu do własnej metody i zastosowaniu wczesnych wyjść w celu osiągnięcia sukcesu, umożliwiając pisanie różnych prób zrobienia czegoś na tym samym poziomie:

try {
    // do something
    return;
} catch (Exception e) {
    // fall through; you probably want to log this
}
try {
    // do something in the same line, but being less ambitious
    return;
} catch (Exception e) {
    // fall through again; you probably want to log this too
}
try {
    // Do the minimum acceptable
    return;
} catch (Exception e) {
    // if you don't have any more fallbacks, then throw an exception here
}
//More try catches?

Kiedy już to rozwiniesz, możesz pomyśleć o podsumowaniu go według strategii.

interface DoSomethingStrategy {
    public void doSomething() throws Exception;
}

class NormalStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something
    }
}

class FirstFallbackStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something in the same line, but being less ambitious
    }
}

class TrySeveralThingsStrategy implements DoSomethingStrategy {
    private DoSomethingStrategy[] strategies = {new NormalStrategy(), new FirstFallbackStrategy()};
    public void doSomething() throws Exception {
        for (DoSomethingStrategy strategy: strategies) {
            try {
                strategy.doSomething();
                return;
            }
            catch (Exception e) {
                // log and continue
            }
        }
        throw new Exception("all strategies failed");
    }
}

Następnie skorzystaj z TrySeveralThingsStrategy, która jest rodzajem złożonej strategii (dwa wzory w cenie jednego!).

Jedno ogromne zastrzeżenie: nie rób tego, chyba że same strategie są wystarczająco złożone lub nie chcesz ich używać w elastyczny sposób. W przeciwnym razie spowalniasz kilka linii prostego kodu z ogromną stertą niepotrzebnej orientacji obiektowej.


7

Nie sądzę, że jest to automatycznie anty-wzór, ale unikałbym go, jeśli znajdę łatwiejszy i czystszy sposób na zrobienie tego samego. Jeśli język programowania, w którym pracujesz, ma finallykonstrukcję, która w niektórych przypadkach może pomóc to wyczyścić.


6

Nie jest to anty-wzorzec per se, ale wzorzec kodu, który mówi, że trzeba zrefaktoryzować.

I to całkiem proste, wystarczy znać ogólną zasadę, która polega na pisaniu tylko bloku try w tej samej metodzie. Jeśli dobrze wiesz, aby pisać razem pokrewny kod, zwykle wystarczy skopiować i wkleić każdy blok try z jego blokami catch i wkleić go w nowej metodzie, a następnie zastąpić oryginalny blok wywołaniem tej metody.

Ta ogólna zasada oparta jest na sugestii Roberta C. Martina z jego książki „Clean Code”:

jeśli słowo kluczowe „try” istnieje w funkcji, powinno to być pierwsze słowo w funkcji i nie powinno być nic po blokach catch / wreszcie.

Szybki przykład „pseudo-java”. Załóżmy, że mamy coś takiego:

try {
    FileInputStream is = new FileInputStream(PATH_ONE);
    String configData = InputStreamUtils.readString(is);
    return configData;
} catch (FileNotFoundException e) {
    try {
        FileInputStream is = new FileInputStream(PATH_TWO);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        try {
            FileInputStream is = new FileInputStream(PATH_THREE);
            String configData = InputStreamUtils.readString(is);
            return configData;
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}

Następnie moglibyśmy refaktoryzować każdą próbę catch iw tym przypadku każdy blok try-catch próbuje tego samego, ale w różnych lokalizacjach (jak wygodne: D), musimy tylko skopiować wklej jeden z bloków try-catch i zastosować metodę .

public String loadConfigFile(String path) {
    try {
        FileInputStream is = new FileInputStream(path);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        return null;
    }
}

Teraz używamy tego w tym samym celu, co wcześniej.

String[] paths = new String[] {PATH_ONE, PATH_TWO, PATH_THREE};

String configData;
for(String path : paths) {
    configData = loadConfigFile(path);
    if (configData != null) {
        break;
    }
}

Mam nadzieję że to pomogło :)


dobry przykład. ten przykład jest naprawdę rodzajem kodu, który musimy zmienić. jednak w niektórych przypadkach konieczne jest zagnieżdżenie try-catch.
— linehrr

4

Z pewnością zmniejsza to czytelność kodu. Powiedziałbym, że jeśli masz szansę , unikaj zagnieżdżania try-catch.

Jeśli musisz zagnieżdżać try-catch, zawsze zatrzymaj się na minutę i pomyśl:

  • czy mam szansę je połączyć?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • powinienem po prostu wyodrębnić zagnieżdżoną część do nowej metody? Kod będzie znacznie czystszy.

    ...  
    try {  
      ... code  
    } catch (FirstKindOfException e) {  
       panicMethod();  
    }   
    ...
    
    private void panicMethod(){   
    try{  
    ... do the nested things  
    catch (SecondKindOfException e) {  
      ... do something else    
      }  
    }
    

Oczywiste jest, że musisz zagnieździć trzy lub więcej poziomów try-catchów w jednej metodzie, co jest pewnym znakiem czasu dla refaktora.


3

Widziałem ten wzorzec w kodzie sieci i ma to sens. Oto podstawowy pomysł w pseudokodzie:

try
   connect;
catch (ConnectionFailure)
   try
      sleep(500);
      connect;
   catch(ConnectionFailure)
      return CANT_CONNECT;
   end try;
end try;

Zasadniczo jest to heurystyka. Jedna nieudana próba połączenia może być po prostu usterką sieci, ale jeśli zdarzy się to dwukrotnie, prawdopodobnie oznacza to, że maszyna, z którą próbujesz się połączyć, jest naprawdę nieosiągalna. Prawdopodobnie istnieją inne sposoby wdrożenia tej koncepcji, ale najprawdopodobniej byłyby jeszcze brzydsze niż próby zagnieżdżenia.


2

Rozwiązałem tę sytuację w ten sposób (try-catch with fallback):

$variableForWhichINeedFallback = null;
$fallbackOptions = array('Option1', 'Option2', 'Option3');
while (!$variableForWhichINeedFallback && $fallbackOptions){
    $fallbackOption = array_pop($fallbackOptions);
    try{
        $variableForWhichINeedFallback = doSomethingExceptionalWith($fallbackOption);
    }
    catch{
        continue;
    }
}
if (!$variableForWhichINeedFallback)
    raise new ExceptionalException();

2

„Musiałem” to zrobić przypadkowo w klasie testowej (JUnit), gdzie metoda setUp () musiała tworzyć obiekty z niepoprawnymi parametrami konstruktora w konstruktorze, który zgłosił wyjątek.

Gdybym musiał na przykład zawieść w budowie 3 niepoprawnych obiektów, potrzebowałbym 3 zagnieżdżonych bloków try-catch. Zamiast tego utworzyłem nową metodę, w której wychwytywano wyjątki, a zwracana wartość była nową instancją klasy, którą testowałem, gdy się powiodła.

Oczywiście potrzebowałem tylko 1 metody, ponieważ zrobiłem to samo 3 razy. Może nie być to dobre rozwiązanie dla zagnieżdżonych bloków, które robią zupełnie inne rzeczy, ale przynajmniej twój kod stałby się bardziej czytelny w większości przypadków.


0

Myślę, że to antypattern.

W niektórych przypadkach możesz chcieć wielokrotnych prób, ale tylko jeśli NIE WIESZ, jakiego rodzaju błędu szukasz, np .:

public class Test
{
    public static void Test()
    {            
        try
        {
           DoOp1();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp2();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp3();
        }
        catch(Exception ex)
        {
            // treat
        }
    }

    public static void Test()
    {
        try
        {
            DoOp1();
            DoOp2();
            DoOp3();
        }
        catch (DoOp1Exception ex1)
        {
        }
        catch (DoOp2Exception ex2)
        {
        }
        catch (DoOp3Exception ex3)
        {
        }
    }
}

Jeśli nie wiesz, czego szukasz, musisz użyć pierwszego sposobu, który jest IMHO, brzydki i niedziałający. Myślę, że to drugie jest znacznie lepsze.

Jeśli więc wiesz, jakiego rodzaju błędu szukasz, bądź konkretny . W tej samej metodzie nie ma potrzeby zagnieżdżania ani wielokrotnego próbowania.


2
Kod taki jak ten, który pokazałeś, w rzeczywistości nie ma sensu w większości, jeśli nie we wszystkich przypadkach. Jednak OP odnosiło się do zagnieżdżonych try-catch, co jest zupełnie innym pytaniem niż w przypadku wielu kolejnych instrukcji.
— JimmyB,

0

W niektórych przypadkach zagnieżdżone Try-Catch jest nieuniknione. Na przykład, gdy sam kod odzyskiwania po błędzie może zgłosić wyjątek. Jednak w celu poprawy czytelności kodu zawsze można wyodrębnić zagnieżdżony blok we własnej metodzie. Sprawdź ten post na blogu, aby uzyskać więcej przykładów zagnieżdżonych bloków Try-Catch-Wreszcie.


0

Nigdzie nie ma wzmianki o Anti Pattern w java. Tak, nazywamy kilka rzeczy dobrą i złą praktyką.

Jeśli blok try / catch jest wymagany w bloku catch, to nie możesz mu pomóc. I nie ma alternatywy. Blok blokujący nie może działać jako część próbna, jeśli zostanie zgłoszony wyjątek.

Na przykład :

String str=null;
try{
   str = method(a);
}
catch(Exception)
{
try{
   str = doMethod(a);
}
catch(Exception ex)
{
  throw ex;
}

W powyższym przykładzie metoda zgłasza wyjątek, ale doMethod (używany do obsługi wyjątku metody) nawet zgłasza wyjątek. W takim przypadku musimy użyć try catch w ramach try catch.

sugeruje się, aby tego nie robić ...

try 
{
  .....1
}
catch(Exception ex)
{
}
try 
{
  .....2
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....4
}
catch(Exception ex)
{
}

wydaje się, że nie oferuje to nic istotnego w porównaniu z wcześniejszymi 12 odpowiedziami
— komnata
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.