29

这在我使用过的每种语言中都困扰着我,我有一个 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)
     {
4

21 回答 21

61

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

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)
     {
         // ...
     }
}

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

于 2008-08-08T16:54:30.143 回答
12

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

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

于 2008-08-08T17:09:38.477 回答
7

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

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

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”等不是可接受的变量名。它们的名称应该反映它们所代表的数据的基本性质。

于 2008-08-08T17:10:27.897 回答
6

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

bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
于 2008-08-08T16:50:57.093 回答
5

首先,我会移除所有== true部件,这样会缩短 50% ;)

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

有时我使用德摩根定律来简化布尔表达式。

于 2008-08-08T16:49:48.530 回答
4

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

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

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
}

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

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

于 2008-08-08T17:04:33.197 回答
3

我见过很多人和编辑用一个选项卡缩进 if 语句中的每个条件,或者将其与打开的括号匹配:

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

我通常将 close paren 放在与最后一个条件相同的行上:

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

但我不认为这很干净。

于 2008-08-08T16:50:23.570 回答
2

好吧,首先,为什么不:

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

此外,重构抽象代码示例非常困难。如果您展示了一个特定示例,则可以更轻松地确定适合该问题的更好模式。

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

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

与以下相同:(不一样,请参见上面的注释!)

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

于 2008-08-08T16:55:23.090 回答
2

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

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 );
  }
};
于 2008-08-18T03:51:12.607 回答
2

Steve McConell 的建议,来自Code Complete:使用多维表。每个变量都用作表的索引,if 语句变成表查找。例如,如果 (size == 3 && weight > 70) 转换为表条目 decision[size][weight_group]

于 2008-08-18T05:39:28.880 回答
1

我求助于单独的布尔值:

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

if ( cond1 && cond2 ) {}
于 2008-08-08T16:49:54.427 回答
1

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

于 2008-08-08T17:33:46.477 回答
1

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

$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
    if ($$v == true) {
        // do something
        break;
    }
于 2010-05-09T12:01:29.030 回答
0

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

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)){
于 2008-08-08T16:59:56.747 回答
0

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

>>> 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?)。如果是这样,那可能是最干净的方法。

于 2008-08-08T17:11:23.060 回答
0

麦克道尔,

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

于 2008-08-08T17:29:52.680 回答
0

@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”的范围运算符,或者你可以轻松摆脱的东西。

于 2008-08-08T17:37:12.943 回答
0

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

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
}
于 2008-08-18T04:15:01.220 回答
0

如果我在 Perl 中进行,这就是我运行检查的方式。

{
  last unless $var1;
  last unless $var2;
  last unless $var3;
  last unless $var4;
  last unless $var5;
  last unless $var6;

  ... # Place Code Here
}

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

于 2008-10-14T21:07:38.657 回答
0
    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)
       )
    {
    }

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

于 2017-05-03T17:11:48.390 回答
-2

如果你这样做:

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

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

于 2008-08-08T17:13:49.730 回答