是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
当前回答
我想说的是,你应该有尽可能多的需要,或者任何使代码更干净的(如保护子句)。
我个人从来没有听过/见过任何“最佳实践”说你应该只有一个返回语句。
在大多数情况下,我倾向于根据逻辑路径尽快退出函数(保护子句就是一个很好的例子)。
其他回答
有时出于性能考虑,这是必要的(我不想获取不同的缓存线,就像继续一样;有时)。
如果你不使用RAII分配资源(内存、文件描述符、锁等),那么多次返回很容易出错,而且肯定是重复的,因为释放需要手动执行多次,你必须仔细跟踪。
在这个例子中:
function()
{
HRESULT error = S_OK;
if(SUCCEEDED(Operation1()))
{
if(SUCCEEDED(Operation2()))
{
if(SUCCEEDED(Operation3()))
{
if(SUCCEEDED(Operation4()))
{
}
else
{
error = OPERATION4FAILED;
}
}
else
{
error = OPERATION3FAILED;
}
}
else
{
error = OPERATION2FAILED;
}
}
else
{
error = OPERATION1FAILED;
}
return error;
}
我会把它写成:
function() {
HRESULT error = OPERATION1FAILED;//assume failure
if(SUCCEEDED(Operation1())) {
error = OPERATION2FAILED;//assume failure
if(SUCCEEDED(Operation3())) {
error = OPERATION3FAILED;//assume failure
if(SUCCEEDED(Operation3())) {
error = OPERATION4FAILED; //assume failure
if(SUCCEEDED(Operation4())) {
error = S_OK;
}
}
}
}
return error;
}
这当然看起来更好。
这在手动资源释放的情况下尤其有用,因为在哪里和哪些释放是必要的是相当直接的。如下例所示:
function() {
HRESULT error = OPERATION1FAILED;//assume failure
if(SUCCEEDED(Operation1())) {
//allocate resource for op2;
char* const p2 = new char[1024];
error = OPERATION2FAILED;//assume failure
if(SUCCEEDED(Operation2(p2))) {
//allocate resource for op3;
char* const p3 = new char[1024];
error = OPERATION3FAILED;//assume failure
if(SUCCEEDED(Operation3(p3))) {
error = OPERATION4FAILED; //assume failure
if(SUCCEEDED(Operation4(p2,p3))) {
error = S_OK;
}
}
//free resource for op3;
delete [] p3;
}
//free resource for op2;
delete [] p2;
}
return error;
}
如果在没有RAII(忘记异常问题!)的情况下使用多个出口编写这段代码,则必须多次写入删除。如果你用}else{then 这有点难看。
但是RAII使得多个出口资源问题变得毫无意义。
我通常支持多个return语句。它们是最容易阅读的。
在某些情况下,这并不好。有时从函数返回可能非常复杂。我记得有一种情况,所有函数都必须链接到多个不同的库。一个库期望返回值是错误/状态代码,而其他库则不期望。使用一个return语句可以节省时间。
我很惊讶没人提到goto。Goto并不是每个人都想让你相信的编程的祸害。如果必须在每个函数中只有一个return语句,请将它放在末尾,并根据需要使用gotos跳转到该return语句。绝对避免标记和箭头编程,它们既丑陋又运行缓慢。
不,因为我们不再生活在20世纪70年代了。如果你的函数足够长以至于多次返回都是个问题,那么它就太长了。
(除了语言中有异常的任何多行函数都会有多个退出点这一事实。)
永远不要在方法中使用return语句。
我知道我会因此受到指责,但我是认真的。
返回语句基本上是过程式编程时代遗留下来的。它们是goto的一种形式,以及break、continue、if、switch/case、while、for、yield和其他一些语句以及大多数现代编程语言中的等价语句。
返回语句有效地“GOTO”函数被调用的点,并在该范围内分配一个变量。
返回语句就是我所说的“方便的噩梦”。它们似乎能快速完成任务,但却会导致大量的维护问题。
Return语句与封装语句截然相反
这是面向对象编程中最重要和最基本的概念。这是面向对象的存在理由。
当你从一个方法返回任何东西时,你基本上是在从对象中“泄露”状态信息。不管你的状态是否改变了,也不管这个信息是否来自其他对象——对调用者来说没有区别。这样做的目的是允许对象的行为在对象分解封装之外。它允许调用者开始以导致脆弱设计的方式操纵对象。
LoD是你的朋友
我建议任何开发人员在c2.com或维基百科上阅读有关得墨忒耳定律(LoD)的内容。LoD是一种设计理念,在字面意义上被用于真正的“关键任务”软件约束的地方,比如喷气推进实验室。它已被证明可以减少代码中的错误数量并提高灵活性。
关于遛狗有一个很好的类比。当你遛狗的时候,你不需要身体上抓住它的腿并移动它们,这样狗就会走。你命令狗走路,它会照顾好自己的腿。在这个类比中,return语句相当于狗让你抓住它的腿。
只和你最亲近的朋友聊天:
你所在函数的参数, 你自己的属性, 在函数中创建的任何对象
您将注意到,这些都不需要return语句。您可能认为构造函数是一个返回函数,这样您就得到了一些东西。实际上返回的是内存分配器。构造函数只是设置内存中的内容。只要新对象的封装是OK的,这就没问题,因为,当你创建它时,你对它有完全的控制权——没有人可以破坏它。
Accessing attributes of other objects is right out. Getters are out (but you knew they were bad already, right?). Setters are OK, but it is better to use constructors. Inheritance is bad - when you inherit from another class, any changes in that class can and probably will break you. Type sniffing is bad (Yes - LoD implies that Java/C++ style type based dispatch is incorrect - asking about type, even implicitly, is breaking encapsulation. Type is an implicit attribute of an object. Interfaces are The Right Thing).
So why is this all a problem? Well, unless your universe is very different from mine, you spend a lot of time debugging code. You aren't writing code that you plan never to reuse. Your software requirements are changing, and that causes internal API/interface changes. Every time you have used a return statement you have introduced a very tricky dependency - methods returning anything are required to know about how whatever they return is going to be used - that is each and every case! As soon as the interface changes, on one end or the other, everything can break, and you are faced with a lengthy and tedious bug hunt.
它们实际上是代码中的恶性肿瘤,因为一旦您开始使用它们,它们就会促进在其他地方进一步使用(这就是为什么您经常可以在对象系统中找到返回的方法链)。
那么另一种选择是什么?
告诉,不要问。
使用OOP——目标是告诉其他对象要做什么,并让它们来处理。所以你必须忘记做事的程序方法。其实很简单——只要不写return语句。做同样的事情有更好的方法:
return概念没有错,但是return语句有很大的缺陷。
如果你真的需要回复,那就回电话。甚至可以传入要填写的数据结构。通过这种方式,您可以保持接口的干净和开放,并且您的整个系统不那么脆弱,适应性更强。它不会减慢你的系统速度,事实上它可以加快系统速度,就像尾部调用优化一样——除了在这种情况下,没有尾部调用,所以你甚至不必浪费时间用返回值来操作堆栈。
如果遵循这些参数,就会发现实际上根本不需要return语句。
如果您遵循这些实践,我保证您很快就会发现您花在寻找错误上的时间要少得多,适应需求变化的速度要快得多,理解您自己的代码时遇到的问题也要少得多。
Well, maybe I'm one of the few people here old enough to remember one of the big reasons why "only one return statement" was pushed so hard. It's so the compiler can emit more efficient code. For each function call, the compiler typically pushes some registers on the stack to preserve their values. This way, the function can use those registers for temporary storage. When the function returns, those saved registers have to be popped off the stack and back into the registers. That's one POP (or MOV -(SP),Rn) instruction per register. If you have a bunch of return statements, then either each one has to pop all the registers (which makes the compiled code bigger) or the compiler has to keep track of which registers might have been modified and only pop those (decreasing code size, but increasing compilation time).
今天仍然坚持使用一个return语句的一个原因是易于自动重构。如果您的IDE支持方法提取重构(选择一系列行并将它们转换为一个方法),那么如果您想提取的行中有一个return语句,特别是如果您正在返回一个值,则很难做到这一点。