在void方法中使用return是不好的做法吗?


92

想象以下代码:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

在void方法内使用return可以吗?它有性能损失吗?或者编写这样的代码会更好:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
怎么样:void DoThis(){if(isValid)DoThat(); }
Dscoduc

30
想象代码?为什么?就在那!:-D
STW,

这是个好问题,我一直认为使用收益是一种好习惯;退出方法或函数。尤其是在具有多个IQueryable <T>结果并且它们全部相互依赖的LINQ数据挖掘方法中。如果其中之一没有结果,请发出警报并退出。
张翔

Answers:



33

使用防护措施的另一个重要原因(与嵌套代码相对):如果另一个程序员向您的函数中添加了代码,则它们在更安全的环境中工作。

考虑:

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();
}

显然,这是一种简单的情况,但是程序员在第一个(嵌套代码)实例中向程序添加了崩溃。在第二个示例(带防护的早期退出)中,一旦您越过防护,您的代码就可以避免意外使用空引用。

当然,出色的程序员不会犯此类错误(通常)。但是预防胜于治疗-我们可以以完全消除这种潜在错误源的方式编写代码。嵌套会增加复杂性,因此最佳实践建议重构代码以减少嵌套。


是的,但是另一方面,多层嵌套及其条件使代码更易于出现错误,逻辑更难跟踪,更重要的是更难调试。IMO扁平函数的危害较小。
Skrim

18
我在吵架 有利于减少嵌套的!:-)
Jason Williams

我同意这一点。同样,从重构的角度来看,如果obj成为一个结构或您可以保证不会为null的东西,则重构该方法将更加容易和安全。
Phil Cooper 2015年


8

这不是一个坏习惯(出于已经说明的所有原因)。但是,您在方法中获得的收益越多,应将其分割成较小的逻辑方法的可能性就越大。


8

第一个示例是使用保护语句。从维基百科

在计算机编程中,guard是一个布尔表达式,如果要在相关分支中继续执行程序,则必须将其评估为true。

我认为在方法的顶部放置一堆警卫是一种完全可以理解的编程方法。基本上是说“如果其中任何一个为真,则不要执行此方法”。

因此,一般来说,它是这样的:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

我认为那更具可读性:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

没有性能损失,但是第二段代码更具可读性,因此更易于维护。


罗素(Russell)我不同意您的意见,但您不应该为此而投票。+1使其均匀。顺便说一句,我认为布尔测试并在一行上返回空白行之后的返回值可以清楚地表明正在发生的事情。例如罗德里戈的第一个例子。
Paul Sasik,2009年

我不同意这一点。增加嵌套不能提高可读性。第一部分代码使用的是“ guard”语句,这是一个完全可以理解的模式。
cdmckay,2009年

我也不同意。如今,早期从功能中退出的保护子句通常被认为是帮助读者理解实现的好东西。
皮特·霍奇森

2

在这种情况下,您的第二个示例是更好的代码,但是与从void函数返回无关,这仅仅是因为第二个代码更直接。但是从void函数返回完全可以。


0

完全可以,没有“性能损失”,但是永远不要写没有括号的“ if”语句。

总是

if( foo ){
    return;
}

它更具可读性;并且您永远不会意外地认为代码的某些部分不在该语句之内。


2
可读性是主观的。恕我直言,添加到代码中的任何不必要的内容都会使它的可读性降低...(我必须阅读更多,然后我想知道为什么它在那里并且浪费时间试图确保我没有丢失任何东西)...但是那是我的主观意见
Charles Bretana 09年

10
始终包含花括号的更好原因是有关可读性的较少,而是有关安全性的。如果不使用花括号,那么以后某个人很容易就可以解决一个需要在if中添加其他语句的错误,而不要特别注意并在不添加花括号的情况下添加它们。通过始终包含括号,可以消除这种风险。
Scott Dorman

2
柔滑,请在输入之前按Enter {。这使您{与您}在同一列中对齐,这极大地提高了可读性(更容易找到对应的打开/关闭括号)。
Imagist

1
@Imagist我将保留个人偏好;并按照我喜欢的方式完成了它:)
Noon Silk

1
如果每个右括号都与放在相同indent级别的右括号匹配,那么在视觉上区分哪些if语句需要右括号很容易,因此让if语句控制单个语句将是安全的。用来将开括号推回该行if可以在每个多语句中节省一行垂直空间if,但是将需要使用其他不必要的闭括号行。
超级猫2014年

0

我将不同意所有对此的年轻胡扯。

由于将近四十年前已故的Edsger W. Dijkstra很清楚地阐明了原因,所以在方法中间使用void或其他方法返回是非常不好的做法,始于著名的“ GOTO声明被认为有害”。 ”,并继续进行Dahl,Dijkstra和Hoare撰写的“结构化程序设计”。

基本规则是,每个控制结构和每个模块都应具有一个入口和一个出口。模块中间的显式返回会破坏该规则,并使推理程序状态变得更加困难,这反过来使得很难判断程序是否正确(这是更强大的属性)。而不是“是否似乎有效”)。

“ GOTO声明被认为有害”和“结构化编程”开始了1970年代的“结构化编程”革命。这两个部分是我们今天拥有if-then-else,while-do和其他显式控制构造的原因,以及为什么使用高阶语言的GOTO语句位于“濒危物种”列表中的原因。(我个人认为它们需要列入“绝种​​”列表中。)

值得注意的是,消息流调制器(Message Flow Modulator)是第一次尝试通过验收测试的第一款军事软件,没有偏差,弃权或“是的,但是”的说法,是用一种甚至没有GOTO语句。

还值得一提的是,尼克劳斯·沃思(Nicklaus Wirth)更改了Oberon-07(Oberon编程语言的最新版本)中RETURN语句的语义,使其成为类型化过程(即函数)声明的结尾部分,而不是函数主体中的可执行语句。他的变化解释说,他这样做是正是因为以前的形式WAS违反了结构化编程的一个退出的原则。


2
@John:我们克服了帕斯卡尔(无论如何,我们大多数人)的时候就获得了多次回报的Dykstra禁令。
约翰·桑德斯

需要多次返回的情况经常表明一种方法试图做太多事情,应予以缩减。我不会像John这样深入,将return语句作为参数验证的一部分可能是一个合理的例外,但是我知道了这个想法的出处。
2009年

@nairdaen:在那个季度,关于例外的争议仍然存在。我的指导方针是:如果正在开发的系统必须解决导致原始异常情况的问题,并且我不介意惹恼那些必须编写该代码的人,那么我将抛出异常。然后我在一次会议上大喊大叫,因为那个家伙没有费心去捕捉异常,并且应用程序在测试中崩溃了,我解释了为什么他必须解决这个问题,然后事情又解决了。
约翰·斯特罗姆

保护声明和gotos之间有很大的区别。gotos的弊端在于它们可以跳到任何地方,因此很难解开并记住它们。Guard语句与之完全相反-它们提供了方法的门禁条目,之后您知道自己在“安全”环境中工作,从而减少了编写其余代码时必须考虑的事情(例如“我知道这个指针永远不会为空,因此我不需要在整个代码中处理这种情况”。
杰森·威廉姆斯

@Jason:最初的问题不是专门关于保护语句,而是关于方法中间的随机返回语句。给出的示例似乎是一个守卫。关键问题在于,在返回站点上,您希望能够推断出该方法执行或未执行的操作,而随机返回使该操作变得更加困难,这与随机GOTO使该操作变得更加困难的原因完全相同。请参阅:Dijkstra,“ GOTO声明被认为有害”。在语法方面,cdmckay在另一个答案中给出了警卫的首选语法。我不同意他对哪种格式更易读的看法。
约翰·斯特罗姆

0

使用防护罩时,请确保遵循某些准则,以免混淆读者。

  • 功能 做一件事
  • 警卫只是作为第一个介绍函数中逻辑
  • 嵌套的部分包含函数的核心意图

// 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
;

-3

当object为null等时,抛出异常而不返回任何内容。

您的方法期望object不为null,而是为null,因此您应该引发异常并让调用方处理该异常。

但是,否则早日返回并不是一个坏习惯。


1
答案不能回答问题。问题是无效方法,因此没有返回任何内容。而且,这些方法没有参数。我得到的一点是,如果返回类型是一个对象,则不返回null,但这不适用于此问题。
路加·哈默
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.