ChatGPT解决这个技术问题 Extra ChatGPT

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

想象一下下面的代码:

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

    DoThat();
}

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

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

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
怎么样: void DoThis() { if (isValid) DoThat(); }
想象一下代码?为什么?它就在那里! :-D
这是个好问题,我一直认为使用 return 是一种好习惯;退出方法或函数。特别是在具有多个 IQueryable 结果且所有结果相互依赖的 LINQ 数据挖掘方法中。如果其中一个没有结果,则警告并退出。

C
Community

void 方法中的返回还不错,是 invert if statements to reduce nesting 的常见做法。

减少对方法的嵌套可以提高代码的可读性和可维护性。

实际上,如果您有一个没有任何 return 语句的 void 方法,编译器将始终在其末尾生成一个 ret instruction


J
Jason Williams

使用守卫(与嵌套代码相反)还有另一个重要原因:如果另一个程序员将代码添加到您的函数中,他们将在更安全的环境中工作。

考虑:

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,扁平函数的危害较小。
我主张减少嵌套! :-)
我同意这一点。此外,从重构的角度来看,如果 obj 成为结构或您可以保证不会为空的东西,则重构方法会更容易、更安全。
C
Community

坏习惯???没门。事实上,如果验证失败,最好尽早从方法返回来处理验证。否则会导致大量嵌套的 if 和 else。提前终止可以提高代码的可读性。

另请查看对类似问题的回复:Should I use return/continue statement instead of if-else?


M
Mike Hall

这不是不好的做法(出于已经说明的所有原因)。但是,方法中的回报越多,就越有可能将其拆分为更小的逻辑方法。


有时返回是基于其他较小逻辑方法的结果。例如。 isSomeCondition = IsSomeCondition(someCriteria) if(!isSomeCondition) return; isSomeOtherCondition = IsSomeOtherCondition(someOtherCriteria) if(!isSomeOtherCondition) 返回;
c
cdmckay

第一个示例是使用保护语句。从 Wikipedia

在计算机编程中,守卫是一个布尔表达式,如果程序要在相关分支中继续执行,则该表达式必须计算为真。

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

所以一般来说它会是这样的:

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

  DoThat();
}

我认为这更具可读性:

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

R
Russell

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


罗素我不同意你的观点,但你不应该被否决。 +1 以平衡它。顺便说一句,我相信布尔测试并在一行上返回一个空行清楚地表明正在发生什么。例如罗德里戈的第一个例子。
我不同意这一点。增加嵌套并不能提高可读性。第一段代码使用了“guard”语句,这是一种完全可以理解的模式。
我也不同意。提早退出功能的保护条款现在通常被认为是一件好事,可以帮助读者理解实现。
I
Imagist

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


N
Noon Silk

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

总是

if( foo ){
    return;
}

它更具可读性;而且您永远不会意外地假设代码的某些部分不在该语句中。


可读性是主观的。恕我直言,添加到代码中的任何不必要的内容都会降低其可读性...(我必须阅读更多内容,然后我想知道为什么它在那里并浪费时间试图确保我没有遗漏任何东西)...但那是我的主观意见
总是包含大括号的更好理由不是关于可读性,而是更多关于安全性。如果没有大括号,那么以后某些人很容易修复一个需要附加语句作为 if 的一部分的错误,没有足够的注意力并在不添加大括号的情况下添加它们。通过始终包括牙套,可以消除这种风险。
Silky,请在您的 { 前按 Enter。这将您的 { 与您的 } 排列在同一列中,这极大地提高了可读性(更容易找到相应的开/关大括号)。
@Imagist 我将把它留给个人喜好;它是按照我喜欢的方式完成的:)
如果每个右大括号都与一个左大括号匹配位于相同的缩进级别,那么直观区分哪些 if 语句需要右大括号将很容易,因此具有 if statements 控制单个语句将是安全的。将左大括号推回带有 if 的行可以在每个多语句 if 上节省一行垂直空间,但需要使用否则不必要的右大括号行。
J
John R. Strohm

我不同意你们所有年轻的whippersnappers在这个问题上。

在方法的中间使用 return ,不管是无效的还是其他的,都是非常糟糕的做法,原因很清楚,近四十年前,已故的 Edsger W. Dijkstra 从著名的“GOTO 语句被认为是有害的”开始”,并继续阅读 Dahl、Dijkstra 和 Hoare 所著的“结构化编程”。

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

“GOTO 语句被认为是有害的”和“结构化编程”开启了 1970 年代的“结构化编程”革命。这两部分是我们今天拥有 if-then-else、while-do 和其他显式控制结构的原因,也是高级语言中的 GOTO 语句被列入濒危物种名单的原因。 (我个人的意见是它们需要在灭绝物种名单上。)

值得注意的是,消息流调制器是第一次尝试通过验收测试的第一款军事软件,没有任何偏差、弃权或“是的,但是”的措辞,它是用一种甚至没有的语言编写的GOTO 语句。

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


@John:就在我们克服帕斯卡(无论如何,我们大多数人)的时候,我们克服了关于多次退货的 Dykstra 禁令。
需要多次返回的情况通常表明一个方法试图做的太多,应该减少。我不会像 John 这样走得那么远,作为参数验证的一部分的 return 语句可能是一个合理的例外,但我知道这个想法的来源。
@nairdaen:该季度的例外情况仍然存在争议。我的指导方针是:如果正在开发的系统必须解决导致原始异常情况的问题,并且我不介意惹恼必须编写该代码的人,我将抛出异常。然后我在一次会议上被骂了,因为那个人没有费心去捕捉异常,应用程序在测试中崩溃了,我解释了为什么他必须解决这个问题,然后事情又安定下来了。
守卫语句和 goto 之间有很大的区别。 gotos 的缺点是它们可以在任何地方跳转,因此解开和记住可能会非常混乱。 Guard 语句正好相反——它们提供了一个方法的门控入口,之后你知道你在一个“安全”的环境中工作,减少了你在编写其余代码时必须考虑的事情的数量(例如“我知道这个指针永远不会为空,所以我不需要在整个代码中处理这种情况”)。
@Jason:最初的问题不是专门关于保护语句,而是关于方法中间的随机返回语句。给出的例子似乎是一个守卫。关键问题是,在返回站点,您希望能够推断该方法做了什么或没有做什么,而随机返回使这变得更难,原因与随机 GOTO 使其更难的原因完全相同。参见:Dijkstra,“GOTO 声明被认为是有害的”。在语法方面,cdmckay 在另一个答案中给出了他对守卫的首选语法;我不同意他关于哪种形式更具可读性的观点。
l
lue

使用警卫时,请确保遵循某些准则,以免使读者感到困惑。

该功能做一件事

守卫仅作为函数中的第一个逻辑引入

未嵌套部分包含函数的核心意图

例子

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

D
Dhananjay

当对象为空等时抛出异常而不是什么也不返回。

您的方法期望 object 不是 null 并且不是这种情况,因此您应该抛出异常并让调用者处理它。

但是提前返回不是坏习惯。


答案不回答问题。问题是一个 void 方法,因此没有返回任何内容。此外,这些方法没有参数。如果返回类型是一个对象,我得到不返回 null 的点,但这不适用于这个问题。