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

Posted

技术标签:

【中文标题】在 void 方法中使用 return 是不好的做法吗?【英文标题】:Is it bad practice to use return inside a void method? 【发布时间】:2010-11-19 23:29:25 【问题描述】:

想象一下下面的代码:

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 数据挖掘方法中。如果其中一个没有结果,则警告并退出。 【参考方案1】:

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

函数只做一件事 守卫仅作为函数中的第一个逻辑引入 未嵌套部分包含函数的核心意图

例子

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

【讨论】:

【参考方案2】:

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

您的方法期望对象不为空,但事实并非如此,因此您应该抛出异常并让调用者处理。

但提前返回不是坏习惯。

【讨论】:

答案不回答问题。问题是一个 void 方法,因此没有返回任何内容。此外,这些方法没有参数。如果返回类型是一个对象,我明白不返回 null 但这不适用于这个问题。【参考方案3】:

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

考虑:

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 成为结构或您可以保证不会为 null 的东西,则重构方法会更容易、更安全。【参考方案4】:

第一个例子是使用保护语句。来自Wikipedia:

在计算机编程中,守卫是 必须计算的布尔表达式 如果程序执行为真 在有问题的分支中继续。

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

所以一般来说是这样的:

void DoThis()

  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();

我认为这更具可读性:

void DoThis()

  if (guard1 && guard2 && guard3)
  
    DoThat();
  

【讨论】:

【参考方案5】:

我将不同意你们所有年轻的whippersnappers。

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

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

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

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

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

【讨论】:

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

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

【讨论】:

有时返回是基于其他较小逻辑方法的结果。例如。 isSomeCondition = IsSomeCondition(someCriteria) if(!isSomeCondition) return; isSomeOtherCondition = IsSomeOtherCondition(someOtherCriteria) if(!isSomeOtherCondition) return;【参考方案7】:

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

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

实际上如果你有一个没有任何返回语句的void方法,编译器总是会在它的末尾生成一个ret instruction。

【讨论】:

【参考方案10】:

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

总是

if( foo )
    return;

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

【讨论】:

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

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

【讨论】:

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

以上是关于在 void 方法中使用 return 是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

用这个方法向复位STM32 ((void (*) (void)) 0x08000000) ();不好用!

非 void 方法中缺少 return 语句编译

java中return的作用

java中return的作用?

Spring Boot Test Mockito Mock Void Return,无返回值的方法

24.如何结束返回值是void的方法