想象一下下面的代码:
void DoThis()
{
if (!isValid) return;
DoThat();
}
void DoThat() {
Console.WriteLine("DoThat()");
}
在 void 方法中使用 return 可以吗?它有任何性能损失吗?或者写这样的代码会更好:
void DoThis()
{
if (isValid)
{
DoThat();
}
}
void 方法中的 return 还不错,这是一种常见的做法,即反转if
语句以减少嵌套。
减少对方法的嵌套可以提高代码的可读性和可维护性。
实际上,如果您有一个没有任何 return 语句的 void 方法,编译器将始终在其末尾生成一个ret 指令。
使用守卫(与嵌套代码相反)还有另一个重要原因:如果另一个程序员将代码添加到您的函数中,他们将在更安全的环境中工作。
考虑:
void MyFunc(object obj)
{
if (obj != null)
{
obj.DoSomething();
}
}
相对:
void MyFunc(object obj)
{
if (obj == null)
return;
obj.DoSomething();
}
现在,假设另一个程序员添加了以下行:obj.DoSomethingElse();
void MyFunc(object obj)
{
if (obj != null)
{
obj.DoSomething();
}
obj.DoSomethingElse();
}
void MyFunc(object obj)
{
if (obj == null)
return;
obj.DoSomething();
obj.DoSomethingElse();
}
显然这是一个简单的案例,但程序员在第一个(嵌套代码)实例中添加了程序崩溃。在第二个示例中(使用警卫提前退出),一旦您越过了警卫,您的代码就不会意外使用空引用。
当然,一个伟大的程序员不会(经常)犯这样的错误。但是预防胜于治疗——我们可以以一种完全消除这种潜在错误来源的方式编写代码。嵌套增加了复杂性,因此最佳实践建议重构代码以减少嵌套。
坏习惯???没门。事实上,如果验证失败,最好尽早从方法返回来处理验证。否则会导致大量嵌套的 if 和 else。提前终止可以提高代码的可读性。
还要检查对类似问题的回答:我应该使用 return/continue 语句而不是 if-else 吗?
第一个示例是使用保护语句。来自维基百科:
在计算机编程中,守卫是一个布尔表达式,如果程序要在相关分支中继续执行,则该表达式必须计算为真。
我认为在方法的顶部有一堆守卫是一种完全可以理解的编程方式。它基本上是说“如果其中任何一个为真,请不要执行此方法”。
所以一般来说它会是这样的:
void DoThis()
{
if (guard1) return;
if (guard2) return;
...
if (guardN) return;
DoThat();
}
我认为这更具可读性:
void DoThis()
{
if (guard1 && guard2 && guard3)
{
DoThat();
}
}
这不是不好的做法(出于已经说明的所有原因)。但是,方法中的回报越多,就越有可能将其拆分为更小的逻辑方法。
没有性能损失,但是第二段代码更具可读性,因此更易于维护。
在这种情况下,您的第二个示例是更好的代码,但这与从 void 函数返回无关,这仅仅是因为第二个代码更直接。但是从 void 函数返回完全没问题。
我不同意你们所有年轻的whippersnappers在这个问题上。
在方法的中间使用 return ,不管是无效的还是其他的,都是非常糟糕的做法,原因很清楚,近四十年前,已故的 Edsger W. Dijkstra 从著名的“GOTO 语句被认为是有害的”开始”,并继续阅读 Dahl、Dijkstra 和 Hoare 所著的“结构化编程”。
基本规则是每个控制结构和每个模块都应该有一个入口和一个出口。模块中间的显式返回打破了该规则,并且使推理程序状态变得更加困难,这反过来又使判断程序是否正确变得更加困难(这是一个更强大的属性而不是“它是否看起来有效”)。
“GOTO 语句被认为是有害的”和“结构化编程”开启了 1970 年代的“结构化编程”革命。这两部分是我们今天拥有 if-then-else、while-do 和其他显式控制结构的原因,也是高级语言中的 GOTO 语句被列入濒危物种名单的原因。(我个人的意见是它们需要在灭绝物种名单上。)
值得注意的是,消息流调制器是第一次尝试通过验收测试的第一款军事软件,没有任何偏差、弃权或“是的,但是”的措辞,它是用一种甚至没有的语言编写的GOTO 语句。
还值得一提的是,Nicklaus Wirth 在 Oberon 编程语言的最新版本 Oberon-07 中改变了 RETURN 语句的语义,使其成为类型化过程(即函数)声明的尾部,而不是函数体中的可执行语句。他对更改的解释说,他这样做正是因为以前的形式违反了结构化编程的单出口原则。
完全没问题,没有“性能损失”,但永远不要写没有括号的“if”语句。
总是
if( foo ){
return;
}
它更具可读性;而且您永远不会意外地假设代码的某些部分不在该语句中。
使用警卫时,请确保遵循某些准则,以免使读者感到困惑。
例子
// guards point you to the core intent
void Remove(RayCastResult rayHit){
if(rayHit== RayCastResult.Empty)
return
;
rayHit.Collider.Parent.Remove();
}
// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
flaw==0 ? 100 :
flaw<10 ? 30 :
flaw<20 ? 0 :
-20
;
当对象为空等时抛出异常而不是什么也不返回。
您的方法期望 object 不是 null 并且不是这种情况,因此您应该抛出异常并让调用者处理它。
但是提前返回不是坏习惯。