if / return的最佳做法


60

我想知道当我有if陈述时被认为是更好的返回方法。

范例1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

范例2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

正如您在两个示例中看到的那样,函数将返回,true/false但是else像第一个示例中那样放置语句是个好主意还是最好不要放置它?


7
如果仅在第一个“ if”中检查错误,则最好不要包含“ else”,因为不应将错误视为实际逻辑的一部分。
Mert Akcakaya

2
我个人更担心该功能会引起副作用,但是我想这只是一个选择不佳的例子?
jk。


14
对我来说,第一个版本是一样驳回所有的学生谁没有完成作业,然后一旦他们都走了,说给其他学生的房间“现在,如果你完成你的功课......”。这是有道理的,但不必要。由于该条件不再是条件,因此我倾向于删除else
妮可

1
您想要做的“在这里做更多的事情”是什么?那可能会完全改变您设计功能的方式。
本尼迪克特

Answers:


81

示例2被称为保护块。如果发生错误(错误的参数或无效的状态),则更适合尽早返回/引发异常。在常规逻辑流程中,最好使用示例1


+1-一个很好的区别,以及我要回答的问题。
Telastyn

3
@ pR0Ps在下面具有相同的答案,并提供了一个代码示例,说明了为什么它最终成为更干净的遵循方法。+1为好答案。

在SO上已经多次问过这个问题,尽管它可能引起争议,但这是一个很好的答案。许多受过训练只能从末日返回的人对此概念有些疑问。
Bill K

2
+1。不当地避免使用保护块往往会导致箭头代码。
Brian

两个示例都不显示防护块吗?
ernie 2012年

44

我的个人风格是将单个代码if用作保护块,并在实际方法中使用if/ else处理代码。

在这种情况下,您将使用myString == null作为保护条件,因此我倾向于使用单一if模式。

考虑稍微复杂一点的代码:

范例1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

范例2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

在示例1中,防护罩和方法的其余部分均采用if/ else形式。与示例2进行比较,示例2中的保护块为单一if形式,而方法的其余部分使用if/ else形式。就个人而言,我发现示例2更容易理解,而示例1看起来凌乱且过于缩进。

请注意,这是一个人为的示例,您可以使用else if语句来清理它,但是我的目的是展示保护块和实际函数处理代码之间的区别。

无论如何,一个好的编译器应该为它们两个生成相同的输出。使用一个或另一个的唯一原因是个人喜好或符合现有代码的样式。


18
如果您省去了第二个示例,则示例2甚至更容易阅读。
Briddums 2012年

18

就个人而言,我更喜欢第二种方法。我觉得它更短,缩进更少并且更易于阅读。


+1减少缩进。如果您进行了多次检查
简直令人讨厌-d.raev

15

我的个人做法如下:

  • 我不喜欢带有多个退出点的函数,我发现很难维护和遵循它们,代码修改有时会破坏内部逻辑,因为它本质上有点草率。当它是复杂的计算时,我会在开始处创建一个返回值,并在结束时返回它。这迫使我仔细遵循每个if-else,switch等路径,在正确的位置正确设置该值。我还花了一些时间来决定是设置默认返回值还是在开始时将其保留为未初始化状态。当逻辑或返回值类型或含义更改时,此方法也有帮助。

例如:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • 唯一的例外:开始时“快速退出”(或在极少数情况下,在过程内部)。如果实际的计算逻辑不能处理输入参数和内部状态的某种组合,或者在不运行算法的情况下提供简单的解决方案,则将所有代码封装在(有时很深)if块中无济于事。这是一个“异常状态”,不是核心逻辑的一部分,因此,我必须在检测到后立即退出计算。在这种情况下,没有其他分支,在正常情况下,执行将继续进行。(当然,通过抛出异常可以更好地表达“例外状态”,但有时这是一种过大的杀伤力。)

例如:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

当计算需要您必须释放的外部资源,或者要求您在退出函数之前必须重置时,“一个出口”规则也将提供帮助。有时,它们会在以后的开发过程中添加。由于算法内部有多个出口,因此很难正确地扩展所有分支。(而且,如果可能发生异常,则释放/重置也应放在finally块中,以避免在极少数例外情况下产生副作用……)。

您的案例似乎属于“在实际工作之前迅速退出”类别,我将其写为您的示例2版本。


9
为“我不喜欢带有多个出口点的功能” +1
Corv1nus

@LorandKedves-添加了一个示例-希望您不介意。
马修·弗林

@MatthewFlynn好吧,如果您不介意这与我在回答末尾提出的建议相反;-)我继续讲这个例子,我希望最终对我们双方都是有益的:-)
Lorand Kedves

@MatthewFlynn(对不起,我只是一个螃蟹混蛋,感谢您帮助我阐明我的观点。希望您喜欢当前版本。)
Lorand Kedves

13
在具有多个出口点的函数和具有可变结果变量的函数中,前者在我看来似乎有两个弊端。
乔恩·普迪

9

我宁愿在可能的地方尽量使用防护块,原因有二:

  1. 给定某些特定条件,它们允许快速退出。
  2. 删除了后面代码中复杂和不必要的if语句的必要性。

一般而言,我更喜欢看到方法的核心功能清晰且最小的方法。保护块有助于从视觉上实现这一目标。


5

我喜欢“穿越”方法:

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

该动作具有特定条件,其他任何东西都只是默认的“失败”返回。


1

如果我有一个if条件,我将不会花太多时间来思考样式。但是如果我有多个后卫条件,我会更喜欢style2

Picuture这个。假设测试是复杂的,并且您真的不想将它们绑定到单个if-ORed条件中以避免复杂性:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

这里有两个主要优点

  1. 您将单个函数中的代码分为两个可视对数块:验证(保护条件)的上部块和可运行代码的下部块。

  2. 如果必须添加/删除一个条件,则可以减少弄乱整个if-elseif-else阶梯的机会。

另一个次要的优点是您需要减少的牙套数量。


0

听起来应该更好。

If condition
  do something
else
  do somethingelse

表达自己更好

if condition
  do something
do somethingelse

对于较小的方法,它不会有太大的区别,但是对于较大的复合方法,由于它不会适当地分开,因此可能会更难于理解。


3
如果方法太长而难以理解第二种形式,则它太长。
凯文·克莱恩

7
您的两种情况只有在do something包含收益的情况下才是相等的。如果不是这样,则do somethingelse无论if谓词如何,第二代码都将执行,这不是第一个块的工作方式。
阿德南

这也是OP在他的示例中解释的内容。只需以下问题的线
乔斯·瓦伦特

0

我发现示例1令人讨厌,因为在值返回函数末尾缺少return语句会立即触发“ Wait,出了点问题”标志。因此,在这种情况下,我将使用示例2。

但是,通常取决于功能的用途,例如错误处理,日志记录等,因此涉及的更多。因此,我同意Lorand Kedves的回答,通常最后要有一个退出点,即使这样做也要付出代价。一个附加标志变量。在大多数情况下,它使维护和以后的扩展更加容易。


0

当您的if语句始终返回时,则没有理由对函数中的其余代码使用else。这样做会增加额外的行和缩进。添加不必要的代码将使其更难阅读,并且众所周知,阅读代码很难


0

在您的示例中,else显然是不必要的,但是...

当快速浏览代码行时,通常会注视括号和缩进以了解代码流程;在他们真正确定代码本身之前。因此,编写else,并在第二个代码块周围加上花括号和缩进实际上使读者更快地看到这是“ A或B”情况,其中一个或另一个块将运行。也就是说,读者在看到returninitial之后会看到花括号和缩进if

在我看来,这是在少数情况下的一种,其中向代码添加多余的内容实际上使其更具可读性,而不是更少。


0

我更喜欢示例2,因为该函数立即返回某事。但不仅如此,我更喜欢从一个地方返回,就像这样:

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

通过这种风格,我可以:

  1. 立即看到我要退货。

  2. 仅用一个断点就可以捕获函数的每次调用结果。


这类似于我处理问题的方式。唯一的区别是,我将使用结果变量来捕获myString评估并将其用作返回值。
Chuck Conway 2012年

@ChuckConway同意,但我试图坚持使用OP的原型。
Caleb

0

我的个人风格倾向于

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

使用注释掉的else语句来指示程序流并保持其可读性,但是要避免大量缩进,如果涉及很多步骤,缩进很容易在屏幕上到达。


1
我个人希望我永远不必使用您的代码。
CVn 2012年

如果您进行了多次检查,则此方法可能会花费一些时间。从每个if子句中返回都类似于使用goto语句跳过代码部分。
Chuck Conway

如果else要在包含这些子句的代码和代码之间做出选择,我会选择后者,因为编译器也会有效地忽略它们。虽然这将取决于else if 增加缩进比原来多一次-如果它确实我同意你的看法。但是我的选择是else如果允许该样式,则完全删除该样式。
马克·赫德

0

我会写

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

我喜欢这种风格,因为很明显我会回来userName != null


1
问题是您为什么偏爱那种风格?
卡勒布

...坦白说,我不确定在这种情况下您为什么还要使用该字符串,更不用说为什么您在末尾添加了一个出于任何原因实际上都不需要的额外布尔测试。
沙杜尔

@Shadur我知道额外的布尔测试和未使用的myString结果。我只是从OP复制功能。我将其更改为看起来更真实的东西。布尔测试是微不足道的,不应发出。
tia

1
@Shadur我们不在这里进行优化,对吧?我的观点是使我的代码易于阅读和维护,就我个人而言,我将以一次引用空值检查的费用来支付此费用。
TIA

1
@tia我喜欢您代码的精神。我没有对用户名进行两次评估,而是捕获了变量中的第一个评估,然后将其返回。
Chuck Conway 2012年

-1

我的经验法则是在没有其他情况下执行if返回(主要是针对错误),或者对所有可能性进行完整的if-else-if链接。

这样,我避免尝试对分支过于花哨,因为每当最终这样做时,我都会将应用程序逻辑编码到ifs中,从而使它们难以维护(例如,如果枚举OK或ERR并且我编写了所有ifs利用!OK <-> ERR的事实,在下一次向枚举添加第三个选项时变得很痛苦。)

例如,在您的情况下,我将使用普通格式,因为“如果为null则返回”肯定是常见的模式,并且您不太可能需要担心第三种可能性(除了null / not null外)在将来。

但是,如果该测试对数据进行了更深入的检查,那么我会偏向于完整的if-else。


-1

我认为示例2有很多陷阱,可能会导致意外的代码。首先,这里的重点是基于围绕“ myString”变量的逻辑。因此,为了明确起见,所有条件测试均应在代码块中进行,以说明已知默认/未知逻辑。

如果稍后在代码上无意中将示例2引入到示例2中,该示例显着改变了输出,该怎么办:

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

我认为else在检查空值之后紧接着的代码块将使那些在未来版本中添加了增强功能的程序员将所有逻辑加在一起,因为现在我们有了原本不希望用于原始规则的逻辑字符串(返回值是否为空值)。

我对Codeplex上的某些C#准则(在此链接:http : //csharpguidelines.codeplex.com/)深信不疑,其中指出以下内容:

“如果默认块(否则)为空,则添加描述性注释。此外,如果不应到达该块,则抛出InvalidOperationException来检测将来可能发生在现有情况下的更改。这确保了更好的代码,因为已经考虑过代码可以使用的所有路径。”

我认为使用这样的逻辑块时,总是添加一个默认块(if-else,case:default)来显式考虑所有代码路径,并且不使代码暴露于意外的逻辑后果,这是一种很好的编程习惯。


如何在第二个例子中没有别的例子而不考虑所有情况?预期结果是继续执行。在这种情况下,添加else子句只会增加方法的复杂性。
Chuck Conway 2012年

我基于在简洁明了的模块中不考虑所有相关逻辑的能力而不同意。如果字符串!= null ,则重点在于操纵myString值和该if块后的代码是所得到的逻辑。因此,由于重点在于操纵该特定值的附加逻辑,因此我认为应将其封装在一个else块中。否则,这会打开潜力,因为我表明会无意间分离逻辑并引入意想不到的后果。可能不会一直发生,但这确实是最佳实践意见风格的问题。
atconway 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.