在函数中使用条件语句,最佳实践是什么?

9
如果我有一个函数,里面有很多条件语句,最好的组织方式是什么?
我的担忧在于其他人阅读代码时能否理解其中的逻辑。即使这个例子很简单,也可以想象条件语句非常复杂的情况。
例如:
public void function(string value, string value2)
{
    if (value == null)
        return;

    if (value2 == value)
        DoSomething();
}

或者

public void function(string value, string value2)
{
    if (value != null)
    {
        if (value2 == value)
            DoSomething();
    }
}

或者

public void function(string value, string value2)
{
    if (value != null && value2 == value)
        DoSomething();
}
11个回答

8
你可以采用防御性编程来确保方法功能的合同得以履行。
public void function(string value, string value2)
{
    if (string.IsNullOrEmpty(value1)) throw new ArgumentNullException("value1", "value 1 was not set");
    if (string.IsNullOrEmpty(value2)) throw new ArgumentNullException("value2", "value 2 was not set");

    DoSomething();
}

此选项使您在处理故障时具有更大的灵活性。 - HasaniH
这个实现是否可以用断言来改进?值得注意的是,上面代码的行为与原始问题中的不同。 - teabot

8

整理这些条件并将它们放入一个方法中。

例如,将这个替换:

 if( a& & n || c  && ( ! d || e ) && f > 1 && ! e < xyz ) { 
      // good! planets are aligned.
      buyLotteryTicket();
 } else if( ..... oh my ... ) { 
 }

转化为:

if( arePlanetsAligned() ) { 
    buyLotteryTicket(); 
} else if( otherMethodHere() ) { 
   somethingElse();
}  

那么使用哪种风格(1、2或3)都没有关系,因为if语句将清楚地描述正在测试的条件。不需要额外的结构。关键是使代码更清晰和自我描述。如果您使用面向对象的编程语言,则可以使用对象来存储状态(变量),以避免创建需要5-10个参数的方法。
这些是类似的问题: 消除嵌套if语句的最佳方法 是否有替代此超缩进代码的方法? 第二个链接展示了如何将一个可怕的维护者噩梦转化为自我描述的代码,这是一种更完整和复杂的方式。
它展示了如何将以下内容转换为自我描述的代码:
public String myFunc(SomeClass input)
{
    Object output = null;

    if(input != null)
    {
        SomeClass2 obj2 = input.getSomeClass2();
        if(obj2 != null)
        {
            SomeClass3 obj3 = obj2.getSomeClass3();
            if(obj3 != null && !BAD_OBJECT.equals(obj3.getSomeProperty()))
            {
                SomeClass4 = obj3.getSomeClass4();
                if(obj4 != null)
                {
                    int myVal = obj4.getSomeValue();
                    if(BAD_VALUE != myVal)
                    {
                        String message = this.getMessage(myVal);
                        if(MIN_VALUE <= message.length() &&
                           message.length() <= MAX_VALUE)
                        {
                            //now actually do stuff!
                            message = result_of_stuff_actually_done;
                        }
                    }
                }
            }
        }
    }
    return output;
}

转化为这样:

if ( isValidInput() && 
    isRuleTwoReady() &&
    isRuleTreeDifferentOf( BAD_OBJECT ) &&
    isRuleFourDifferentOf( BAD_VALUE ) && 
    isMessageLengthInRenge( MIN_VALUE , MAX_VALUE ) ) { 
            message = resultOfStuffActuallyDone();
}

虽然有所改进,但重构后的条件语句仍然太长、难以阅读和理解(在我看来)。 - teabot
@teabot: :-O。但它几乎是字面意思!好吧,我想你总是可以重新提取方法并执行:"if( isThatHorribleCondition() ) { message = yikes() };" 但我认为这只会让你去查看"isThatHorribleCondition()"的源代码... :) - OscarRyz
@teabot:如果这样写甚至更好:如果(condition()){work();} 看起来够简短了吧?;) - OscarRyz
1
那个庞大的条件肯定代表了应用领域中的一个真实概念。因此,必须能够给它一个描述性名称——例如:“messageParametersAreValidAndInRange”。将其提取到一个单独的方法中会增加可读性(在我看来),并将复合条件移动到一个小的私有方法中,如果需要了解精确实现,则可以选择性地检查该方法。但更重要的是,当函数名提供的意图足够时,它可以被忽略。 - teabot

8
我更喜欢第一种选择 - 快速失败 更加简洁、明确,易于阅读和理解。
我知道这并不是一个失败,但这个概念仍然适用。我真的不喜欢嵌套的if语句。

所有三个示例都会快速失败,它们执行相同的空值测试,并在值== null时立即返回。 - HasaniH
如果你需要释放任何资源怎么办?那么你就需要在每个返回语句之前复制这个清理代码。我认为第二和第三个选项更好。 - msvcyc
@SpaceghostAli:是的,你说得对,但这只是一个简单的例子,大多数情况下并不那么简单。 @msvcyc:如果你需要释放资源,那么你真的应该使用try/finally块或者适用的using语句。 - Andrew Hare

2
将那部分重构为自己的函数。使用一个描述性的函数名称要比一堆布尔表达式更易读。
// leave complex conditional code out, so that we can focus on the larger problem in the function
public void function(string value, string value2)
{
    if (MyDescriptiveTestName)
    {
        DoSomething();
    }
}

// encapsulate complex conditional code so that we can focus solely on it.
private bool MyDescriptiveTestName(string value, string value2)
{
    if (value != null && value2 == value)
    {
        return true;
    }
    return false;
}

抛弃if语句...只需使用"return value != null && value2 == value" - Jonas
好的,你说得对。当时我没有想到这个问题,但我相信在重构时我会发现它的。 - Matthew Vines

2
我可以推荐 Robert C. Martin 的书 Clean Code,它提供了一组很好的启发式方法来编写易读且易于维护的代码。
另一个选项是将条件语句提取到另一个私有函数中,并命名该函数以描述您的意图。由于提供的代码是通用的,因此这种方法可能不太适用,但大致如下:
public void function(string value, string value2)
{
    if (valuesAreValidAndEqual(value, value2))
    {
        DoSomething();
    }
}

private void valuesAreValidAndEqual(string value, string value2)
{
    return value != null && value2 == value;
}

显然,如果变量名称和函数名称与您的领域相关,则更有用。

1

很好的观点。许多嵌套条件语句可能是方法尝试做太多事情(低内聚性)的症状。 - JohnFx

0

我喜欢第三个选项,但这取决于编程语言。你假设在第三个选项中,语句的第一部分将失败并且不会执行第二部分。这是与编程语言相关的。我知道大多数基于C的编程语言都会这样做,但由于你没有指定具体的编程语言,这可能会成为一个潜在的问题。可能存在一种我不知道的编程语言,它没有短路概念。


0

你提前考虑代码可读性这个事实已经完成了一半。

至于哪个例子最易读,那是很主观的。

我的想法如下:

  • 就我个人而言,我认为第一个例子最容易理解。
  • 尽量减少嵌套层次和/或条件数量通常可以提高可读性。
  • 有些人会反对从方法中多次退出(例如示例1),但我认为只有当方法变得非常长时才会成为问题。如果只是检查输入并快速失败,这并不是什么大问题。

0

我更倾向于第二个选项。就我个人而言,当我阅读这样的代码时,我会记住进入每个嵌套级别的条件。在第一个例子中,我可能会忘记第一个条件(value == null 为假)仍然成立。第三个选项也不错,但我更喜欢第二个。


0

如果一个函数中有很多语句,那么这就是一个将该函数分解为更小的函数的信号。

放置if语句的最佳方式并不存在,我认为答案是主观的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接