Чи використання вкладених блоків пробної застави анти-шаблон?


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

Чи можете ви дати нам справу для цього? Чому ви не можете впоратись із кожним типом помилки в уловці верхнього рівня?
Morons

2
Нещодавно я бачив такий код коду, який виконують недосвідчені програмісти, які насправді не знають, що вони викликають всередині пробних блоків, і вони не хочуть турбуватися про тестування коду. У зразку коду, який я бачив, це була одна і та ж операція, але виконувалась кожен раз із запасними параметрами.
Містер Сміт

@LokiAstari - Ваш приклад - спробу в розділі "Нарешті". Там, де немає Catch. Це вкладене в розділі Спроба. Це інакше.
Морон

4
Чому це повинно бути антитілом?

2
+1 для "більше спробувати уловить?"
JoelFan

Відповіді:


85

Іноді це неминуче, особливо якщо ваш код відновлення може бути винятком.

Не дуже, але іноді немає альтернатив.


17
@MisterSmith - Не завжди.
Одід

4
Так, це те, до чого я намагався потрапити. Звичайно, у ваших вкладених заявах про спробу / ловити момент, де вам просто потрібно сказати, достатньо. Я робив випадок для введення, на відміну від послідовних спроб / лову, кажучи, що є ситуації, коли ви хочете, щоб код був виконаний лише у другій спробі, якщо перша спроба перестане.
AndrewC

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

31
спробуйте {action.commit (); } ловити {спробувати {action.rollback (); } catch {серйозний ()} notsoseriouslogging (); } - приклад необхідного вкладеного спроби лову
Thanos Papathanasiou

3
Принаймні, витягніть блок лову методом, хлопці! Давайте хоча б зробимо це читабельним.
Містер Кохез,

43

Я не вважаю, що це антипатерн, просто широко використовується.

Більшість вкладених випробувань уловлюють насправді уникнути і потворно нас пекло, як правило, продукт молодших розробників.

Але бувають випадки, коли ви не можете допомогти.

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

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


19

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

Однак я б запропонував зробити наступне, щоб покращити код:

  • Рефактор внутрішня спроба ... виймає блоки на окремі функції, наприклад, attemptFallbackMethodі attemptMinimalRecovery.
  • Будьте більш конкретні щодо конкретних видів виключень, які потрапляють. Ви дійсно очікуєте будь-якого підкласу Винятку, і якщо так, то ви дійсно хочете обробляти їх однаково?
  • Поміркуйте, чи 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 {
    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;
        }
    }
}

Тоді ми можемо переробляти кожен спробу лову, і в цьому випадку кожен блок спробу лову спробує те ж саме, але в різних місцях (як зручно: D), нам залишається лише скопіювати вставити один із блоків пробного лову і зробити метод його .

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

Я сподіваюся, що це допомагає :)


хороший приклад. цей приклад є справді тим кодом, який ми маємо переробляти. проте в інші часи вкладений пробний лов необхідний.
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    
      }  
    }
    

Очевидно, що вам доведеться вкласти три або більше рівнів пробних уловів одним методом, що є вірною ознакою часу для рефактора.


3

Я бачив цю схему в мережевому коді, і це насправді має сенс. Ось основна ідея в псевдокоді:

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

В основному це евристика. Одна невдала спроба підключення може бути просто мережевим збоєм, але якщо це трапляється двічі, це, ймовірно, означає, що машина, до якої ви намагаєтесь підключитися, дійсно недосяжна. Можливо, є інші способи втілити цю концепцію, але вони, швидше за все, будуть ще гірші, ніж вкладені спроби.


2

Я вирішив подібну ситуацію так (спробу лову з запасною системою):

$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 пробні блоки, вкладені. Натомість я створив новий метод, де винятки, де потрапляли, а повернене значення - це новий екземпляр класу, який я тестував, коли це вдалося.

Звичайно, мені знадобився лише 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)
        {
        }
    }
}

Якщо ви не знаєте, що шукаєте, ВИ ХОТИ скористатися першим способом, який IMHO, некрасивий і не функціональний. Я думаю, що останнє набагато краще.

Тож, якщо ви знаєте, який тип помилки ви шукаєте, будьте конкретними . Немає необхідності вкладати чи кілька спроб уловлювати всередині одного методу.


2
Код, як той, який ви показали, насправді не має сенсу в більшості, якщо не у всіх випадках. Однак ОП мав на увазі вкладені спроби, що є зовсім іншим питанням, ніж питання для декількох послідовних заяв.
JimmyB

0

У деяких випадках вкладений Try-Catch неминучий. Наприклад, коли сам код відновлення помилок може кинути і виключити. Але для поліпшення читабельності коду ви завжди можете витягнути вкладений блок у власний метод. Перегляньте цю публікацію в блозі, щоб дізнатися більше прикладів про вкладені блоки Try-Catch-нарешті.


0

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

Якщо в блоці лову потрібен блок "спробувати / ловити", його не можна допомогти. І альтернативи немає. Оскільки блок ловлі не може працювати, як спробувати частину, якщо буде викинуто виняток.

Наприклад :

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

Тут у наведеному вище прикладі метод кидає виняток, але doMethod (використовується для обробки винятків методу) навіть викидає виняток. У цьому випадку ми повинні використовувати спробу впіймання в межах 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.