【问题标题】:How do you handle huge if-conditions?你如何处理巨大的 if 条件?
【发布时间】:2017-05-04 01:11:48
【问题描述】:

在我使用的每一种语言中,我都有一个问题,我有一个 if 语句,但条件部分有太多的检查,我必须将它分成多行,使用嵌套的 if 语句,或者只是接受它很难看继续我的生活。

您是否发现任何其他方法可能对我和遇到同样问题的其他人有用?

例如,全部在一行中:

if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{

示例,多行:

if (var1 = true && var2 = true && var2 = true
 && var3 = true && var4 = true && var5 = true
 && var6 = true)
{

示例嵌套:

if (var1 = true && var2 = true && var2 = true && var3 = true)
{
     if (var4 = true && var5 = true && var6 = true)
     {

【问题讨论】:

    标签: language-agnostic if-statement


    【解决方案1】:

    将条件分成几个布尔值,然后使用主布尔值作为条件。

    bool isOpaque = object.Alpha == 1.0f;
    bool isDrawable = object.CanDraw && object.Layer == currentLayer;
    bool isHidden = hideList.Find(object);
    
    bool isVisible = isOpaque && isDrawable && ! isHidden;
    
    if(isVisible)
    {
        // ...
    }
    

    更好:

    public bool IsVisible {
        get
        {
            bool isOpaque = object.Alpha == 1.0f;
            bool isDrawable = object.CanDraw && object.Layer == currentLayer;
            bool isHidden = hideList.Find(object);
    
            return isOpaque && isDrawable && ! isHidden;
        }
    }
    
    void Draw()
    {
         if(IsVisible)
         {
             // ...
         }
    }
    

    请确保您为变量命名实际上表明意图而不是功能。这将极大地帮助开发人员维护您的代码......可能是您!

    【讨论】:

    • 简单、易做、有效。
    【解决方案2】:

    我很惊讶还没有人得到这个。有专门针对此类问题的重构:

    http://www.refactoring.com/catalog/decomposeConditional.html

    【讨论】:

    • 我不喜欢 Decompose Conditional,因为它使用不可重用的一次性函数污染了代码结构。我宁愿为每个“组”相关检查有一个带有 cmets 的大 IF 语句。
    【解决方案3】:

    这里有两个问题需要解决:可读性和可理解性

    “可读性”解决方案是一个风格问题,因此可以解释。我的偏好是这样的:

    if (var1 == true && // Explanation of the check
        var2 == true && // Explanation of the check
        var3 == true && // Explanation of the check
        var4 == true && // Explanation of the check
        var5 == true && // Explanation of the check
        var6 == true)   // Explanation of the check
        { }
    

    或者这个:

    if (var1 && // Explanation of the check
        var2 && // Explanation of the check
        var3 && // Explanation of the check
        var4 && // Explanation of the check
        var5 && // Explanation of the check
        var6)   // Explanation of the check
        { }
    

    也就是说,这种复杂的检查在扫描代码时可能很难在精神上解析(尤其是如果您不是原作者)。考虑创建一个辅助方法来抽象一些复杂性:

    /// <Summary>
    /// Tests whether all the conditions are appropriately met
    /// </Summary>
    private bool AreAllConditionsMet (
        bool var1,
        bool var2,
        bool var3,
        bool var4,
        bool var5,
        bool var6)
    {
        return (
            var1 && // Explanation of the check
            var2 && // Explanation of the check
            var3 && // Explanation of the check
            var4 && // Explanation of the check
            var5 && // Explanation of the check
            var6);  // Explanation of the check
    }
    
    private void SomeMethod()
    {
        // Do some stuff (including declare the required variables)
        if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
        {
            // Do something
        }
    }
    

    现在,当从视觉上扫描“SomeMethod”方法时,测试逻辑的实际复杂性被隐藏了,但语义却被保留了下来,以供人类在高层次上理解。如果开发者确实需要了解细节,可以考察 AreAllConditionsMet 方法。

    我认为这正式称为“分解条件”重构模式。 Resharper 或 Refactor Pro 等工具!可以让这种重构变得容易吗!

    在所有情况下,具有可读性和可理解性的代码的关键是使用真实的变量名称。虽然我知道这是一个人为的示例,但“var1”、“var2”等是可接受的变量名称。它们的名称应该反映它们所代表的数据的基本性质。

    【讨论】:

      【解决方案4】:

      我经常将它们拆分为组件布尔变量:

      bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
      bool custValid = customerBalance == 0 && customerName != "Mike";
      if (orderValid && custValid)
      {
      ...
      

      【讨论】:

        【解决方案5】:

        首先,我会删除所有 == true 部分,这会缩短 50% ;)

        当我有很大的条件时,我会寻找原因。有时我看到我应该使用多态,有时我需要添加一些状态对象。基本上,这意味着需要重构(代码味道)。

        有时我使用De-Morgan's laws 来稍微简化布尔表达式。

        【讨论】:

          【解决方案6】:

          查看 Kent Beck 的 Implementation Patterns。我正在考虑一种特殊的模式,它可能会在这种情况下有所帮助......它被称为“警卫”。您可以将它们分解为守卫,而不是拥有大量条件,这可以清楚地表明方法中的不利条件。

          例如,如果您有一个方法可以做某事,但在某些情况下它不应该做某事,而不是:

          public void doSomething() {
              if (condition1 && condition2 && condition3 && condition4) {
                  // do something
              }
          }
          

          你可以改成:

          public void doSomething() {
              if (!condition1) {
                  return;
              }
          
              if (!condition2) {
                  return;
              }
          
              if (!condition3) {
                  return;
              }
          
              if (!condition4) {
                  return;
              }
          
              // do something
          }
          

          它有点冗长,但更具可读性,尤其是当你开始有奇怪的嵌套时,守卫可以提供帮助(结合提取方法)。

          顺便说一句,我强烈推荐这本书。

          【讨论】:

          • 'Fast return' 也杀死了 'arrow-head' 反模式 :)
          • 我不同意这些 Guards 使其更易于阅读。对“复杂”条件的评论会更好。
          【解决方案7】:

          我见过很多人和编辑要么用一个制表符缩进 if 语句中的每个条件,要么将其与打开的括号匹配:

          if (var1 == true
              && var2 == true
              && var3 == true
             ) {
              /* do something.. */
          }
          

          我通常把右括号和最后一个条件放在同一行:

          if (var1 == true
              && var2 == true
              && var3 == true) {
              /* do something.. */
          }
          

          但我不认为这很干净。

          【讨论】:

            【解决方案8】:

            史蒂夫·麦康奈尔的建议,来自Code Complete: 使用多维表。每个变量都用作表的索引, if 语句变成了表查找。例如如果 (size == 3 && weight > 70) 转化为表项决策[size][weight_group]

            【讨论】:

              【解决方案9】:

              尝试查看函子和谓词。 Apache Commons 项目有一组很棒的对象,允许您将条件逻辑封装到对象中。 O'reilly here 上提供了它们的使用示例。代码示例摘录:

              import org.apache.commons.collections.ClosureUtils;
              import org.apache.commons.collections.CollectionUtils;
              import org.apache.commons.collections.functors.NOPClosure;
              
              Map predicateMap = new HashMap();
              
              predicateMap.put( isHonorRoll, addToHonorRoll );
              predicateMap.put( isProblem, flagForAttention );
              predicateMap.put( null, ClosureUtils.nopClosure() );
              
              Closure processStudents = 
                  ClosureUtils.switchClosure( predicateMap );
              
              CollectionUtils.forAllDo( allStudents, processStudents );
              

              现在是所有这些 isHonorRoll 谓词的详细信息以及用于评估它们的闭包:

              import org.apache.commons.collections.Closure;
              import org.apache.commons.collections.Predicate;
              
              // Anonymous Predicate that decides if a student 
              // has made the honor roll.
              Predicate isHonorRoll = new Predicate() {
                public boolean evaluate(Object object) {
                  Student s = (Student) object;
              
                  return( ( s.getGrade().equals( "A" ) ) ||
                          ( s.getGrade().equals( "B" ) && 
                            s.getAttendance() == PERFECT ) );
                }
              };
              
              // Anonymous Predicate that decides if a student
              // has a problem.
              Predicate isProblem = new Predicate() {
                public boolean evaluate(Object object) {
                  Student s = (Student) object;
              
                  return ( ( s.getGrade().equals( "D" ) || 
                             s.getGrade().equals( "F" ) ) ||
                           s.getStatus() == SUSPENDED );
                }
              };
              
              // Anonymous Closure that adds a student to the 
              // honor roll
              Closure addToHonorRoll = new Closure() {
                public void execute(Object object) {
                  Student s = (Student) object;
              
                  // Add an award to student record
                  s.addAward( "honor roll", 2005 );
                  Database.saveStudent( s );
                }
              };
              
              // Anonymous Closure flags a student for attention
              Closure flagForAttention = new Closure() {
                public void execute(Object object) {
                  Student s = (Student) object;
              
                  // Flag student for special attention
                  s.addNote( "talk to student", 2005 );
                  s.addNote( "meeting with parents", 2005 );
                  Database.saveStudent( s );
                }
              };
              

              【讨论】:

              • 不错!一个可以下载并与 methinks 一起玩。
              【解决方案10】:

              好吧,首先,为什么不呢:

              if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
              ...

              此外,重构抽象代码示例非常困难。如果你展示了一个具体的例子,就更容易找到更好的模式来解决问题。

              这并没有更好,但是我过去做过的事情: (以下方法可防止布尔测试短路,即使第一个测试为假,所有测试也会运行。不是推荐的模式,除非您知道在返回之前始终需要执行所有代码——感谢 ptomato 发现我的错误!)

              布尔确定 = cond1;
              好的 &= cond2;
              好的 &= cond3;
              好的 &= cond4;
              好的 &= cond5;
              好的 &= cond6;

              等同于:(不一样,见上注!)

              ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

              【讨论】:

              • 如果&amp;&amp;操作符短路就不一样了。
              • 哇。我应该知道的。我在自己的一个答案中的第一个脸;-)
              【解决方案11】:

              我使用单独的布尔值:

              Bool cond1 == (var1 && var2);
              Bool cond2 == (var3 && var4);
              
              if ( cond1 && cond2 ) {}
              

              【讨论】:

                【解决方案12】:

                正如其他人所提到的,我会分析您的条件,看看是否有办法将其外包给其他方法以提高可读性。

                【讨论】:

                  【解决方案13】:

                  在 PHP 等反射语言中,您可以使用变量变量:

                  $vars = array('var1', 'var2', ... etc.);
                  foreach ($vars as $v)
                      if ($$v == true) {
                          // do something
                          break;
                      }
                  

                  【讨论】:

                    【解决方案14】:

                    我喜欢按级别分解它们,所以我会像这样格式化你的示例:

                    if (var1 = true
                     && var2 = true
                     && var2 = true
                     && var3 = true
                     && var4 = true
                     && var5 = true
                     && var6 = true){
                    

                    当你有更多的嵌套时,它会很方便,就像这样(显然真实条件比“= true”更有趣):

                    if ((var1 = true && var2 = true)
                     && ((var2 = true && var3 = true)
                      && (var4 = true && var5 = true))
                     && (var6 = true)){
                    

                    【讨论】:

                      【解决方案15】:

                      如果您碰巧使用 Python 进行编程,那么在变量列表上应用内置的 all() 函数就很简单了(我在这里只使用布尔文字):

                      >>> L = [True, True, True, False, True]
                      >>> all(L) # True, only if all elements of L are True.
                      False
                      >>> any(L) # True, if any elements of L are True.
                      True
                      

                      您的语言(C#?Java?)中是否有任何相应的功能。如果是这样,那可能是最干净的方法。

                      【讨论】:

                        【解决方案16】:

                        麦克道尔,

                        您是正确的,当使用表达式两边都计算的单个“&”运算符时。但是,当使用“&&”运算符时(至少在 C# 中),第一个返回 false 的表达式是最后一个计算的表达式。这使得将评估放在 FOR 语句之前与任何其他方式一样好。

                        【讨论】:

                          【解决方案17】:

                          @tweakt

                          没有更好的,但是我过去所做的:

                          布尔确定 = cond1; 好的 &= cond2; 好的 &= cond3; 好的 &= cond4; 好的 &= cond5; 好的 &= cond6;

                          等同于:

                          ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

                          实际上,这两个东西在大多数语言中并不相同。第二个表达式通常会在其中一个条件为假时立即停止评估,如果评估条件代价高昂,这可能会大大提高性能。

                          为了可读性,我个人更喜欢上面 Mike Stone 的建议。冗长的评论很容易,并保留了能够提早退出的所有计算优势。如果它会混淆代码的组织以使条件评估远离其他函数,您也可以在函数中内联执行相同的技术。这有点俗气,但你总是可以这样做:

                          do {
                              if (!cond1)
                                 break;
                              if (!cond2)
                                 break;
                              if (!cond3)
                                 break;
                              ...
                              DoSomething();
                          } while (false);
                          

                          while (false) 有点俗气。我希望语言有一个名为“once”的范围运算符,或者你可以轻松摆脱的东西。

                          【讨论】:

                            【解决方案18】:

                            如果我在 Perl 中执行此操作,这就是我运行检查的方式。

                            {
                              last unless $var1;
                              last unless $var2;
                              last unless $var3;
                              last unless $var4;
                              last unless $var5;
                              last unless $var6;
                            
                              ... # Place Code Here
                            }
                            

                            如果您打算在子例程中使用它,请将 last 的每个实例替换为 return

                            【讨论】:

                              【解决方案19】:

                              我喜欢将每个条件分解为描述性变量。

                              bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
                              isVar1Valid = ( var1 == 1 )
                              isVar2Valid = ( var2.Count >= 2 )
                              isVar3Valid = ( var3 != null )
                              isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
                              if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
                                   //do code
                              }
                              

                              【讨论】:

                              • 创建单个布尔值有什么意义。此外,您将每个varXtrue 进行比较,并将其分配给isVarXValid 布尔值,这实际上只是isVar1Valid = var简写,这是多余的。你已经有 bools 开始了,所以为什么不只是 if(var1 &amp;&amp; var2 &amp;&amp; var3 &amp;&amp; var4)
                              【解决方案20】:
                                  if (   (condition_A)
                                      && (condition_B)
                                      && (condition_C)
                                      && (condition_D)
                                      && (condition_E)
                                      && (condition_F)
                                     )
                                  {
                                     ...
                                  }
                              

                              相对

                                  if (condition_A) {
                                     if (condition_B) {
                                        if (condition_C) {
                                           if (condition_D) {
                                              if (condition_E) {
                                                 if (condition_F) {
                                                    ...
                                                 }
                                              }
                                           }
                                        }
                                     }
                                  }
                              

                                  if (   (   (condition_A)
                                          && (condition_B)
                                         )
                                      || (   (condition_C)
                                          && (condition_D)
                                         )
                                      || (   (condition_E)
                                          && (condition_F)
                                         )
                                     )
                                  {
                                     do_this_same_thing();
                                  }
                              

                              相对

                                  if (condition_A && condition_B) {
                                     do_this_same_thing();
                                  }
                                  if (condition_C && (condition_D) {
                                     do_this_same_thing();
                                  }
                                  if (condition_E && condition_F) {
                                     do_this_same_thing();
                                  }
                              

                              如果多个条件表达式不使用显式括号指示表达式分析,而不是依赖运算符优先规则和更少的括号,大多数用于检查代码的静态分析工具都会报错。​​

                              开/闭大括号{}、开闭括号()、带括号的条件表达式和左侧的运算符在同一缩进级别垂直对齐是一种非常有用的做法,它大大增强了代码的可读性和清晰度,而不是将所有可能被卡在一条线上的东西,没有垂直对齐、空格或括号

                              运算符优先级规则很棘手,例如&& 的优先级高于 ||,但 |优先于 &&

                              所以,...

                                  if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
                                  }
                              

                              是一个非常简单的多重条件表达式,供人类阅读和评估不正确。

                                  if (   (  (expr_A)
                                          & (expr_B)
                                         )
                                      || (  (expr_C)
                                          | (  (expr_D)
                                             & (expr_E)
                                            )
                                         )
                                      || (   (expr_E)
                                          && (  (expr_F)
                                              & (expr_G)
                                             )
                                         )
                                      || (expr_H)
                                     )
                                  {
                                  }
                              

                              水平空格(换行)、垂直对齐或显式括号指导表达式评估没有任何问题,所有这些都增强了可读性和清晰度

                              【讨论】:

                                【解决方案21】:

                                如果你这样做:

                                if (var1 == true) {
                                    if (var2 == true) {
                                        if (var3 == true) {
                                            ...
                                        }
                                    }
                                }
                                

                                然后,您还可以对某些不真实的情况做出回应。例如,如果您正在验证输入,您可以向用户提供如何正确格式化输入的提示,或其他任何内容。

                                【讨论】:

                                • 这可能是这个问题最糟糕的解决方案。
                                猜你喜欢
                                • 1970-01-01
                                • 2011-02-12
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2010-09-12
                                • 2017-02-18
                                • 2012-07-28
                                相关资源
                                最近更新 更多