Является ли этот контроль структуры потока хорошей практикой? - PullRequest
2 голосов
/ 09 января 2009

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

Я придумал этот подход и хотел узнать ваше мнение:

public void MyMethod()
{
   bool hasFailed = false;

   try
   {
      GetNewOrders(out hasFailed);

      if(!hasFailed)
          CheckInventory(out hasFailed);

      if(!hasFailed)
          PreOrder(out hasFailed);              

      // etc
   }
   catch(Exception ex)
   {
   }
   finally
   {
      if(hasFailed)
      {
           // do something
      }
   }
}

Ответы [ 10 ]

9 голосов
/ 09 января 2009

Я сделал что-то подобное, но без обработки исключений:

BOOL ok = CallSomeFunction();
if( ok ) ok = CallSomeOtherFunction();
if( ok ) ok = CallYetAnotherFunction();
if( ok ) ok = WowThatsALotOfFunctions();
if( !ok ) {
    // handle failure
}

Или, если вы хотите быть умным:

BOOL ok = CallSomeFunction();
ok &= CallSomeOtherFunction();
ok &= CallYetAnotherFunction();
...

Если вы все равно используете исключения, зачем вам переменная hasFailed?

7 голосов
/ 09 января 2009

Не совсем. Ваши методы должны вызывать исключение в случае ошибки, которая будет обнаружена вашим блоком "catch".

5 голосов
/ 09 января 2009

Насколько я вижу, это пример каскадных шагов, когда второй и третий шаги будут выполнены, если первое и первое и второе верны, то есть return hasFailed == false.

Этот код можно сделать намного более элегантным, используя Template Method и шаблон проектирования Decorator.

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

public interface Validator {
    public boolean isValid();
}

public class GetNewOrders implements Validator {
    public boolean isValid() {
       // same code as your GetNewOrders method
    }
}

public abstract class AbstractValidator implements Validator {
    private final Validator validator;

    public AbstractValidator(Validator validator) {
        this.validator = validator;
    }
    protected boolean predicate();
    protected boolean isInvalid();

    public final boolean isValid() {
        if (!this.validator.isValid() && predicate() && isInvalid())
            return false;
        return true;
    }
}

public class CheckInventory extends AbstractValidator {
    public CheckInventory(Validator validator) {
        super(validator);
    }
    @Override
    public boolean predicate() {
        return true;
    }
    @Override
    public boolean isInvalid() {
        // same code as your CheckInventory method
    }
}

public class PreOrder extends AbstractValidator {
    public CheckInventory(Validator validator) {
        super(validator);
    }
    @Override
    public boolean predicate() {
        return true;
    }
    @Override
    public boolean isInvalid() {
        // same code as your PreOrder method
    }
}

Теперь ваш метод может выглядеть намного элегантнее:

public void MyMethod() {
    bool success = false;
    try {
        Validator validator = new GetNewOrders();
        validator = new CheckInventory(validator);
        validator = new PreOrder(validator);
        success = validator.isValid();
    } finally {
        if (!success) {
            // do something
        }
    }
} 

Объект Validator может быть создан в одну строку, но я предпочитаю этот стиль, поскольку он делает очевидным порядок проверки. Создание новой ссылки проверки в цепочке зависит от подкласса класса AbstractValidator и реализации методов предиката и isInvalid.

3 голосов
/ 09 января 2009

Не комментируя материал try / catch, поскольку я действительно не знаю, что там происходит, я бы изменил его так, чтобы вызываемые методы возвращали true / false для успеха, а затем просто проверяли их в зависимости от логического короткого замыкания чтобы избежать вызова более поздних методов, если предыдущий метод не удался.

public void MyMethod()
{
    bool success = false;
    try
    {
         success = GetNewOrders()
                   && CheckInventory()
                   && PreOrder();
         // etc
    }
    catch(Exception ex)   {   }
    finally
    {
        if(!success)
        {
        }
    }
}
1 голос
/ 09 января 2009

Это не очень хорошо для меня выглядит. Использование переменной hasFailed действительно нехорошо. если GetNewOrders завершится неудачно с исключением, вы, например, окажетесь внутри блока catch с hasFailed = false!

В отличие от других ответов здесь, я считаю, что МОЖЕТ быть законным использование для логического «hasFailed», которое не является исключительным. Но я действительно не думаю, что вы должны смешивать такое условие с вашим обработчиком исключений.

0 голосов
/ 09 января 2009

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

  1. Изменение значения параметра является плохой практикой.
  2. Никогда не используйте пустое универсальное выражение catch, хотя бы добавьте комментарий, почему вы это делаете.
  3. Заставьте методы генерировать исключение и обрабатывайте их там, где это необходимо.

Так что теперь это намного элегантнее:)

public void MyMethod()
{
   try
   {
      GetNewOrders();
      CheckInventory();
      PreOrder();
      // etc
   }
   finally
   {
      // do something
   }
}
0 голосов
/ 09 января 2009

Ваш новый подход не так уж плох для простого набора инструкций, но что происходит, когда добавляются дополнительные шаги? Требуете ли вы когда-либо транзакционного поведения? (Что если PreOrder завершится неудачно? Или если следующий шаг после PreOrder завершится неудачей?)

Заглядывая вперед, я бы использовал шаблон команды: http://en.wikipedia.org/wiki/Command_pattern

... и инкапсулировать каждое действие в виде конкретной команды, реализующей Execute () и Undo ().

Тогда это просто вопрос создания очереди команд и зацикливания до сбоя или пустой очереди. Если какой-либо шаг не удался, просто остановите и выполните Undo () по порядку на предыдущих командах. Легко.

0 голосов
/ 09 января 2009

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

try {
  // Get all of the orders here
  // Either in bulk, or just a list of the new order ids that you'll call the DB
  // each time for, i'll use the former for clarity.
  List<Order> orders = getNewOrders();

  // If no new orders, we will cry a little and look for a new job
  if (orders != null && orders.size() > 0) {
    for (Order o : orders) {
      try {
        for (OrderItem i : o.getOrderItems()) {
          if (checkInventory(i)) {
            // Reserve that item for this order
            preOrder(o, i);
          } else {
            // Out of stock, call the magic out of stock function
            failOrderItem(o, i);
          }
        }
      } catch (OrderProcessingException ope) {
        // log error and flag this order as requiring attention and
        // other things relating to order processing errors that aren't database related
      }
    }
  } else {
    shedTears();
  }
} catch (SQLException e) {
  // Database Error, log and flag to developers for investigation
}
0 голосов
/ 09 января 2009

Я бы сделал это так:

public void MyMethod()
{
bool success = false;   
try
   {

      GetNewOrders(); // throw GetNewOrdersFailedException    
      CheckInventory(); // throw CheckInventoryFailedException
      PreOrder(); // throw PreOrderFailedException  
      success = true;            
   }
   catch( GetNewOrdersFailedException e)
   {
     // Fix it or rollback
   }
   catch( CheckInventoryFailedException e)
   {
     // Fix it or rollback
   }
   catch( PreOrderFailedException e)
   {
     // Fix it or rollback
   }
   finally
   {
      //release resources;
   }
}

Расширение исключения довольно тривиально,

public NewExecption: BaseExceptionType {}

0 голосов
/ 09 января 2009

Я знаю, что, возможно, дублирую несколько постов: что не так с else? Вы также можете использовать ленивую оценку (a() && b()) для связи методов, но это зависит от статуса, который присваивается в качестве возвращаемого значения, что в любом случае более читабельно, IMHO.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...