我们如何处理巨大的if条件?

时间:2020-03-05 18:38:38  来源:igfitidea点击:

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

解决方案

回答

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

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

有时我会使用De-Morgan的定律来简化布尔表达式。

回答

我求助于单独的布尔值:

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

if ( cond1 && cond2 ) {}

回答

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

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

我通常将封闭括号与最后一个条件放在同一行:

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

但是我认为这还不算干净。

回答

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

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

回答

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

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

确保给变量名称实际表示意图而不是功能。这将极大地帮助开发人员维护代码...可能是我们!

回答

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

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

   ...

而且,重构抽象代码示例非常困难。如果我们显示了一个特定的示例,则可以更容易地找到一个更好的模式来解决该问题。

并没有更好,但是我过去所做的事情:
(以下方法可防止布尔布尔测试短路,即使第一个测试为假,也将运行所有测试。不建议使用这种模式,除非我们知道在返回-Thantom to ptomato之前必须始终执行所有代码!)

boolean ok = cond1;

  ok &= cond2;

  ok &= cond3;

  ok &= cond4;

  ok &= cond5;

  ok &= cond6;

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

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

回答

我喜欢按级别细分它们,因此我将格式化示例,如下所示:

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

回答

查看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
}

它有点冗长,但可读性更高,尤其是当我们开始使用怪异的嵌套时,后卫可以提供帮助(结合提取方法)。

我强烈推荐这本书。

回答

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

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

回答

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

"可读性"解决方案是样式问题,因此可以解释。我的偏好是这样的:

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

回答

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

回答

如果我们这样做:

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

然后,我们还可以应对某些不正确的情况。例如,如果我们要验证输入,则可以向用户提供有关如何正确设置其格式的提示,等等。

回答

麦克道尔,

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

回答

正如其他人提到的那样,我将分析条件,以查看是否存在将其外包给其他方法以提高可读性的方法。

回答

@tweakt

It's no better, but what I've done in the past:
  
  boolean ok = cond1;
     ok &= cond2;
     ok &= cond3;
     ok &= cond4;
     ok &= cond5;
     ok &= cond6;
  
  Which is the same as:
  
  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",或者我们可以轻松理解的东西。

回答

尝试查看函子和谓词。 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 );
  }
};

回答

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

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
}

回答

史蒂夫·麦康奈尔(Steve Mcconell)的建议,来自Code Complete:
使用多维表。每个变量都用作表的索引,
if语句变成一个表查找。例如,如果(尺寸== 3 &&重量> 70)
转换为表格入口的决定[大小] [权重组]

回答

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

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

  ... # Place Code Here
}

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

回答

在PHP之类的反射语言中,可以使用变量-变量:

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