这种流动结构控制是否良好实践?

时间:2009-01-09 14:15:59

标签: refactoring control-flow

我想重写一个嵌套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 个答案:

答案 0 :(得分:9)

我做过类似的事情,但没有异常处理:

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变量?

答案 1 :(得分:7)

不是真的。如果您的“catch”块发生错误,您的方法应该引发异常。

答案 2 :(得分:5)

据我所知,这是一个级联步骤的例子,如果第一个和第一个和第二个有效,将执行第二个和第三个步骤,即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
        }
    }
} 

验证器对象可以在一行中创建,但我更喜欢这种样式,因为它明确了验证的顺序。在链中创建新的验证链接是子类化AbstractValidator类以及谓词和isInvalid方法的实现的问题。

答案 3 :(得分:3)

没有评论try / catch的东西,因为我真的不知道那里发生了什么,我会改变它,所以被调用的方法返回true / false成功然后根据布尔短路检查它们如果上述方法失败,请避免调用后面的方法。

public void MyMethod()
{
    bool success = false;
    try
    {
         success = GetNewOrders()
                   && CheckInventory()
                   && PreOrder();
         // etc
    }
    catch(Exception ex)   {   }
    finally
    {
        if(!success)
        {
        }
    }
}

答案 4 :(得分:1)

这对我来说真的不太好看。使用hasFailed变量真的不太好。如果GetNewOrders失败并出现异常,那么例如你最终在catch块中使用hasFailed = false!

在这里反对其他答案我相信可能有合法用途的布尔“hasFailed”并非例外。但我真的不认为你应该将这样的条件混合到你的异常处理程序中。

答案 5 :(得分:0)

我知道我可能会复制一些帖子:else出了什么问题?你也可以使用延迟评估(a() && b())来链接方法 - 但这依赖于作为返回值给出的状态,无论如何恕我直言都更具可读性。

我不同意您应该提出异常的海报,因为如果发生程序错误或程序由于操作而进入异常状态时应该引发异常。例外不是业务逻辑。

答案 6 :(得分:0)

我会这样做:

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

答案 7 :(得分:0)

好吧,我不喜欢看似获取订单列表然后处理它们的代码,然后在发生错误时停止处理它们,肯定它应该跳过那个订单并转移到下一个订单?完全失败的唯一方法是当数据库(订单来源,预订者的目的地)死亡时。我认为整个逻辑确实有点时髦,但也许那是因为我没有你所用语言的经验。

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
}

答案 8 :(得分:0)

对于一组简单的指令,您的新方法并不是那么糟糕,但是当添加其他步骤时会发生什么?你/你是否会要求交易行为? (如果PreOrder失败怎么办?或者如果PreOrder失败后的下一步怎么办?)

期待,我会使用命令模式: http://en.wikipedia.org/wiki/Command_pattern

...并将每个动作封装为实现Execute()和Undo()的具体命令。

然后,只需创建一个命令队列并循环直到失败或空队列。如果任何步骤失败,则只需在先前的命令上按顺序停止并执行Undo()。容易。

答案 9 :(得分:0)

克里斯解决方案是最正确的。但我认为你不应该做的比你需要的多。解决方案应该是可以接受的,这就足够了。

  1. 更改参数值是不好的做法。
  2. 永远不要使用空的通用catch语句,至少添加注释为什么这样做。
  3. 使方法抛出异常并在适当的位置处理它们。
  4. 所以现在它更加优雅:)

    public void MyMethod()
    {
       try
       {
          GetNewOrders();
          CheckInventory();
          PreOrder();
          // etc
       }
       finally
       {
          // do something
       }
    }