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?
            }
        }
    }
Pan Smith
źródło
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.

Oded
źródło
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 ...

Thanos Papathanasiou
źródło
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”
mikera
źródło
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.

Tom Anderson
źródło
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ć.

FrustratedWithFormsDesigner
źródło
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 :)

Adrián Pérez
źródł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.

CsBalazsHungary
źródło
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.

Mason Wheeler
źródło
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();
Adrian
źródło
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.

MarioDS
źródło
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.

George Silva
źródło
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.

codelion
źródło
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)
{
}
gauravprasad
źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu z wcześniejszymi 12 odpowiedziami
komnata