【问题标题】:Alternatives to embedded if statements?嵌入式 if 语句的替代方案?
【发布时间】:2011-12-09 15:22:55
【问题描述】:

我有编程经验,但在软件开发方面并不多。我目前正在为我工​​作的公司编写一个软件,我开始挑战自己代码的可读性。

我想知道这是否是嵌入 if 语句的“有效”替代方案,或者是否有更好的方法可以使用。

假设我有以下方法:

public void someMethod()
{
    if (some condition)
    {
        if (some condition 2)
        {
            if (some condition 3)
            {
                // ...etc all the way until:
                doSomething();
            }
            else
            {
                System.err.println("Specific Condition 3 Error");
            }
        }
        else
        {
            System.err.println("Specific Condition 2 Error");
        }
    }
    else
    {
        System.err.println("Specific Condition 1 Error");
    }
}

现在我要指出的第一件事是,在这种情况下,组合条件(与 &&)是不可能的,因为每个条件都有一个我想要报告的唯一错误,如果我将它们组合起来,我不会不能这样做(或者我会吗?)。在有人尖叫“SWITCH STATEMENT!”之前,我应该指出的第二件事。在我看来,并非所有这些条件都可以通过 switch 语句处理;有些是对象特定的方法调用,有些是整数比较等。

也就是说,以下是使上述代码更具可读性的有效方法,还是有更好的方法?

public void someMethod()
{
    if (!some condition)
    {
        System.err.println("Specific Condition 1 Error");
        return;
    }

    if (!some condition 2)
    {
        System.err.println("Specific Condition 2 Error");
        return;
    }

    if (!some condition 3)
    {
        System.err.println("Specific Condition 3 Error");
        return;
    }

    doSomething();
}

因此,基本上,我们不是在 else 块中检查条件并报告错误,而是检查条件的反转,如果为真则返回。结果应该是一样的,但是有没有更好的处理方法?

【问题讨论】:

    标签: java if-statement


    【解决方案1】:

    如果我特别迂腐,我会使用这样的东西。

    boolean c1, c2, c3;
    
    public void someMethod() {
      boolean ok = true;
      String err = "";
    
      if (ok && !(ok &= c1)) {
        err = "Specific Condition 1 Error";
      }
    
      if (ok && !(ok &= c2)) {
        err = "Specific Condition 2 Error";
      }
    
      if (ok && !(ok &= c3)) {
        err = "Specific Condition 3 Error";
      }
    
      if ( ok ) {
        doSomething();
      } else {
        System.out.print(err);
      }
    }
    

    你现在是单出口和平房。

    已添加

    如果 &= 对您来说很困难,请使用以下内容:

      if (ok && !c3) {
        err = "Specific Condition 3 Error";
        ok = false;
      }
    

    【讨论】:

    • 这是一些很棒的条件。我想我以前从未使用过 &= 操作!
    • 我似乎记得在 C 中你可以只使用 if (!(ok &= cond)) { 或类似的东西,这样更整洁,但我的 C 现在有点生疏了。
    • 单出口和平坦,但你把一些显而易见的东西变成了人们需要三思而后行的东西。
    • @yshavit ...直到您意识到它在做什么,它就像任何范例一样简单明了。它在不引入多个出口气味的情况下消除了箭头气味。它还有助于调试 - 在每个 err = 行上放置一个断点并将它们永远留在那里。
    • @Paul 当然,然后你一年后回到那个代码,必须再次记住这个奇怪的模式是什么。或者,有人遇到了您的代码并且必须弄清楚。如果每个人都这样做,那将是一回事;但这不是非常惯用的 Java 代码,虽然一个优秀的 Java 开发人员可以弄清楚你在做什么,但恕我直言,即使像嵌套 ifs 这样基本的东西也会强迫某人挠头几秒钟。
    【解决方案2】:

    我会这样写

    if (failing condition) {
        System.err.println("Specific Condition 1 Error");
    } else {
        somethingExpensiveCondition2and3Dependon();
        if (failing condition 2)
            System.err.println("Specific Condition 2 Error");
        else if (failing condition 3)
            System.err.println("Specific Condition 3 Error");
        else
            doSomething();
    }
    

    【讨论】:

    • 如果我是正确的,如果我想在条件检查之间执行代码,这不会起作用吗?除此之外,这是一种很好的方式。
    • 我怀疑你想在检查之间放置的代码可能会放在它们之前。
    • 可以,但在某些情况下,代码需要很长时间才能执行。出于效率的原因,如果条件 1 无论如何都会失败,我不想执行大量条件 2 和 3 所依赖的代码。
    • 我添加了一个如何处理的示例。
    • 嗯,我明白了。然而,如果条件 3 依赖于条件 2 不依赖的东西,那么我们就回到了起点(嵌入式 ifs!)。我喜欢您的解决方案,并且在大多数情况下它会是最好的解决方案,但不幸的是,我的方案并不适用。
    【解决方案3】:

    是的,在这两种情况下,您的代码都带有条件复杂性的味道 (code smells)

    Java 是一种 OOP 语言,因此您的代码应该按照 OOD 的精神进行考虑,如下所示:

    for (Condition cond : conditions) {
        if (cond.happens(params))
             cond.getHandler().handle(params);
    }
    

    条件列表应该被注入到这个类中,这样当一个新的条件被添加或删除时,这个类不会改变。 (open close principle)

    【讨论】:

      【解决方案4】:

      您的第二种方法相当不错。如果你想要一些更巴洛克的东西,你可以将你的条件移动到 Callable 对象中。每个对象还可以提供一种处理错误的方法。这使您可以在不牺牲功能的情况下编写任意长的测试系列。

      class Test {
          private final Callable<Boolean> test;
          private final Runnable errorHandler;
      
          public Test(Callable<Boolean> test, Runnable handler) {
              this.test = test;
              errorHandler = handler;
          }
      
          public boolean runTest() {
              if (test.call()) {
                  return true;
              }
              errorHandler.run();
              return false;
          }
      }
      

      然后您可以按如下方式组织您的代码:

      ArrayList<Test> tests;
      
      public void someMethod() {
          for (Test test : tests) {
              if (!test.runTest()) {
                  return;
              }
          }
          doSomething();
      }
      

      编辑

      这是上述内容的更通用版本。它应该可以处理几乎所有这种类型的情况。

      public class Condition {
          private final Callable<Boolean> test;
          private final Runnable passHandler;
          private final Runnable failHandler;
      
          public Condition(Callable<Boolean> test,
                  Runnable passHandler, Runnable failHandler)
          {
              this.test = test;
              this.passHandler = passHandler;
              this.failHandler = failHandler;
          }
      
          public boolean check() {
              if (test.call()) {
                  if (passHandler != null) {
                      passHandler.run();
                  }
                  return true;
              }
              if (errorHandler != null) {
                  errorHandler.run();
              }
              return false;
          }
      }
      
      public class ConditionalAction {
          private final ArrayList<Condition> conditions;
          private final Runnable action;
      
          public ConditionalAction(ArrayList<Condition> conditions,
                  Runnable action)
          {
              this.conditions = conditions;
              this.action = action;
          }
      
          public boolean attemptAction() {
          for (Condition condition : conditions) {
              if (!condition.check()) {
                  return false;
              }
          }
          action.run();
          return true;
          }
      }
      

      人们可能会想添加某种通用数据,这些数据可以被传递以共享信息或收集结果。与其这样做,我建议在实现条件和操作的对象中实现此类数据共享,并保持此结构不变。

      【讨论】:

      • 因此,如果我有几种不同类型的条件(因此需要不同的 runTest() 方法),我是否会创建一个类来继承每种类型的 Test 类?这是非常有用的信息,谢谢!
      • @Rsaesha - 这是一种方法,尽管 runTest() 非常通用。另一种方法可能是对 Test 类进行更进一步的参数化——例如,同时拥有一个通过处理程序和一个错误处理程序。 (为了对称,我可能会将 errorHandler 重命名为 failHandler。)这样,您可以将一个动作与通过每个测试联系起来。
      【解决方案5】:

      对于这种情况,这与您将获得的一样干净,因为您对每个条件都有自定义标准和自定义响应。

      【讨论】:

        【解决方案6】:

        您实际上所做的是在调用doSomething() 方法之前验证一些条件。我会将验证提取到一个单独的方法中。

        public void someMethod() {
          if (isValid()) {
            doSomething();
          }
        }
        
        private boolean isValid() {
          if (!condition1) {
            System.err.println("Specific Condition 1 Error");
            return false;
          }
          if (!condition2) {
            System.err.println("Specific Condition 2 Error");
            return false;
          }
          if (!condition3) {
            System.err.println("Specific Condition 3 Error");
            return false;
          }
          return true;
        }
        

        【讨论】:

          【解决方案7】:

          不,这就是你在 Java 中得到的东西。如果你有太多这些,这可能表明你应该重构一下,甚至可能重新考虑你的算法——尝试简化一下可能是值得的,因为否则你将回到几个月后,想知道为什么a + b + c + d = ea + b' + c + d = zebra

          【讨论】:

            【解决方案8】:

            您拥有的第二个选项更具可读性。虽然通常不建议多次返回,但将它们全部放在代码的开头是清楚的(并不是它们散布在整个方法中)。另一方面,嵌套的 if 很难理解和理解。

            【讨论】:

            • 我的一些方法的回报会分散在各处。如果是这样的话,你会说第二个选项更具可读性吗?
            • “不建议多次退货?” ——在哪个宇宙? (这个问题不需要回答,只是为了指出以上事实并非事实,而只是一种观点。)
            • 返回规则可以简单地通过创建一个rval变量并在最后用rval == someConditionrval替换每个返回来执行。
            • @Bombe 您只需要代码中的单点退出。如果您想了解更多详细信息,请阅读 Bob Martin 的“清洁代码”。
            • @Bombe - 首先,问题是关于意见的。代码的可读性问题是一种观点。关于你的“什么宇宙”——我想说的是在一个可读性很重要的宇宙中。与单一回报相比,多重回报更难遵循。这并不意味着你不这样做,而是意味着你在做之前先思考。避免嵌套语句(ifs、fors 等)也是这样的建议(当然还有其他建议)。对于这个权衡两个选项对可读性影响的问题,我更喜欢在方法开始时进行多次读取
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-04-19
            • 2012-10-10
            • 1970-01-01
            • 2014-05-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多