Является ли использование вложенных блоков try-catch антишаблоном?


95

Это антипаттерн? Это приемлемая практика?

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

Можете ли вы дать нам случай для этого? Почему вы не можете обработать каждый тип ошибки в улове верхнего уровня?
дебилы

2
Я недавно видел такой код, выполненный неопытными программистами, которые на самом деле не знают, что они вызывают внутри блоков try, и не хотят беспокоиться о тестировании кода. В примере кода, который я видел, это была та же самая операция, но выполняемая каждый раз с резервными параметрами.
Мистер Смит

@LokiAstari - Ваш пример - попытка в разделе «В конце». Там, где нет Catch. Это гнездо в разделе Try .. Это другое.
дебилы

4
Почему это должно быть анти-паттерном?

2
+1 за "больше пробовать ловить?"
JoelFan

Ответы:


85

Иногда это неизбежно, особенно если ваш код восстановления может выдать исключение.

Не красиво, но иногда нет альтернативы.


17
@MisterSmith - не всегда.
Одед

4
Да, это то, что я пытался получить. Конечно, в ваших вложенных выражениях try / catch наступает момент, когда достаточно просто сказать, что достаточно. Я выдвигал случай вложения в отличие от последовательных try / catch, говоря, что существуют ситуации, когда вы хотите, чтобы код внутри второй попытки выполнялся только в том случае, если первая попытка падает.
AndrewC

5
@MisterSmith: Я бы предпочел вложенные try-catches последовательным try-catches, которые частично контролируются с помощью флаговых переменных (если они были функционально одинаковыми).
FrustratedWithFormsDesigner

31
try {action.commit (); } catch {try {action.rollback (); } catch {Серьезный журнал ()} notsoseriouslogging (); } является примером необходимой вложенной попытки catch
Thanos Papathanasiou

3
По крайней мере, извлеките блок catch в метод, ребята! Давайте хотя бы сделаем это читабельным.
г-н Кочезе

43

Я не думаю, что это антипаттерн, просто широко используется.

Большинство вложенных уловов try действительно избегаемы и ужасны, черт возьми, обычно это продукт младших разработчиков.

Но бывают моменты, когда ты ничего не можешь с этим поделать.

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

Кроме того, вам понадобится дополнительная инструкция, чтобы обозначить неудачный откат ...


19

Логика хороша - в некоторых ситуациях может иметь смысл попробовать альтернативный подход, который сам по себе может испытывать исключительные события ... следовательно, эта модель в значительной степени неизбежна.

Однако я бы предложил следующее, чтобы сделать код лучше:

  • Измените внутреннюю структуру try ... catch на отдельные функции, например, attemptFallbackMethodи attemptMinimalRecovery.
  • Будьте более конкретными о конкретных типах исключений, которые вылавливаются. Вы действительно ожидаете какого-либо подкласса Exception и если да, то действительно ли вы хотите обрабатывать их одинаково?
  • Подумайте, finallyможет ли блок иметь больше смысла - это обычно так для всего, что ощущается как «код очистки ресурса»

14

Все в порядке. Рефакторинг, который нужно рассмотреть, это подтолкнуть код в свой собственный метод и использовать ранние выходы для достижения успеха, позволяя вам писать различные попытки сделать что-то на одном уровне:

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?

После того, как у вас все получится, вы можете подумать о том, чтобы обернуть его в шаблон Стратегии.

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

Тогда просто используйте TrySeveralThingsStrategy, которая является своего рода сложной стратегией (две модели по цене одной!).

Одно огромное предостережение: не делайте этого, если ваши стратегии сами по себе не достаточно сложны или вы не хотите использовать их гибкими способами. В противном случае вы получите несколько строк простого кода с огромной кучей ненужной объектной ориентации.


7

Я не думаю, что это автоматически анти-паттерн, но я бы избежал этого, если бы смог найти более простой и чистый способ сделать то же самое. Если язык программирования, на котором вы работаете, имеет finallyконструкцию, это может помочь в некоторых случаях это исправить.


6

Не анти-шаблон как таковой, а шаблон кода, который говорит о необходимости рефакторинга.

И это довольно просто, вам просто нужно знать практическое правило, которое записывает не больше, чем блок try в том же методе. Если вы хорошо знаете, как писать связанный код вместе, обычно просто копируете и вставляете каждый блок try с его блоками catch и вставляете его в новый метод, а затем заменяете исходный блок вызовом этого метода.

Это правило основано на предложении Роберта К. Мартина из его книги «Чистый код»:

если в функции существует ключевое слово 'try', оно должно быть самым первым словом в функции и что после блоков catch / finally ничего не должно быть.

Быстрый пример на «псевдо-Java». Предположим, у нас есть что-то вроде этого:

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

Затем мы могли бы реорганизовать каждый try catch, и в этом случае каждый блок try-catch пытается сделать то же самое, но в разных местах (как удобно: D), нам нужно только скопировать вставить один из блоков try-catch и создать метод для него ,

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

Теперь мы используем это с той же целью, что и раньше.

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

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

Надеюсь, это поможет :)


хороший пример. этот пример действительно тот код, который мы должны реорганизовать. однако в некоторых других случаях необходим вложенный try-catch.
linehrr

4

Это, безусловно, снижает читабельность кода. Я сказал бы, если у вас есть шанс , тогда избегайте вложенных попыток ловить.

Если вам нужно вложить пробные уловы, всегда останавливайтесь на минуту и ​​думайте:

  • у меня есть шанс объединить их?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • я должен просто извлечь вложенную часть в новый метод? Код будет намного более чистым.

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

Это очевидно, если вам нужно вложить три или более уровней try-catch в одном методе, что является верным признаком времени для рефакторинга.


3

Я видел этот шаблон в сетевом коде, и он действительно имеет смысл. Вот основная идея в псевдокоде:

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

По сути это эвристика. Одной неудачной попыткой подключения может быть просто сбой сети, но если это происходит дважды, это, вероятно, означает, что машина, к которой вы пытаетесь подключиться, действительно недоступна. Возможно, есть и другие способы реализации этой концепции, но они, скорее всего, будут еще страшнее, чем вложенные попытки.


2

Я решил эту ситуацию следующим образом (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

Я "должен" сделать это в тестовом классе по совпадению (JUnit), где метод setUp () должен был создавать объекты с недопустимыми параметрами конструктора в конструкторе, который вызывал исключение.

Например, если бы мне пришлось сделать сбой при создании 3 недопустимых объектов, мне понадобилось бы 3 блока try-catch, вложенных. Вместо этого я создал новый метод, где исключения были обнаружены, а возвращаемое значение было новым экземпляром класса, который я тестировал, когда это удалось.

Конечно, мне нужен был только 1 метод, потому что я делал то же самое 3 раза. Это может быть не очень хорошим решением для вложенных блоков, которые делают совершенно разные вещи, но, по крайней мере, ваш код станет более читабельным в большинстве случаев.


0

Я на самом деле думаю, что это антипаттерн.

В некоторых случаях вам может потребоваться несколько попыток, но только если вы НЕ ЗНАЕТЕ, какую ошибку вы ищете, например:

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

Если вы не знаете, что ищете, вы должны использовать первый способ, который, на мой взгляд, ужасен и не функционален. Я думаю, что последний намного лучше.

Итак, если вы знаете, какой ВИД ошибки вы ищете, будьте конкретны . Нет необходимости во вложенных или нескольких попытках перехвата внутри одного и того же метода.


2
Код, подобный тому, который вы показали, действительно не имеет никакого смысла в большинстве, если не во всех случаях. Тем не менее, OP имел в виду вложенные попытки, что является совершенно другим вопросом, чем вопрос для нескольких последовательных операторов.
JimmyB

0

В некоторых случаях вложенный Try-Catch неизбежен. Например, когда код восстановления ошибки сам может бросить и исключение. Но для улучшения читабельности кода вы всегда можете извлечь вложенный блок в собственный метод. Посмотрите этот пост в блоге, чтобы увидеть больше примеров для вложенных блоков Try-Catch-finally.


0

Там ничего не упоминается как Anti Pattern в Java нигде. Да, мы называем несколько вещей хорошей практикой и плохой практикой.

Если требуется блок try / catch внутри блока catch, вы не можете помочь. И альтернативы нет. Поскольку блок catch не может работать, попробуйте выполнить часть, если выброшено исключение.

Например :

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

Здесь в приведенном выше примере метод генерирует исключение, но doMethod (используемый для обработки исключения метода) даже генерирует исключение. В этом случае мы должны использовать try catch внутри try catch.

что-то, что предлагается не делать, это ..

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

кажется, что это не дает ничего существенного по сравнению с предыдущими 12 ответами
gnat
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.