是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
当前回答
拥有多个出口点本质上与使用GOTO是一样的。这是不是件坏事取决于你对迅猛龙的看法。
其他回答
如果管理得当,多次退出是件好事
第一步是明确退出的原因。我的通常是这样的: 1. 不需要执行函数 2. 发现错误 3.提前完成 4. 正常完成 我想你可以把“1”归为一类。不需要执行函数“into”3。提前完成”(如果你愿意的话,可以说是非常提前完成)。
第二步是让函数外部的人知道退出的原因。伪代码看起来像这样:
function foo (input, output, exit_status)
exit_status == UNDEFINED
if (check_the_need_to_execute == false) then
exit_status = NO_NEED_TO_EXECUTE // reason #1
exit
useful_work
if (error_is_found == true) then
exit_status = ERROR // reason #2
exit
if (need_to_go_further == false) then
exit_status = EARLY_COMPLETION // reason #3
exit
more_work
if (error_is_found == true) then
exit_status = ERROR
else
exit_status = NORMAL_COMPLETION // reason #4
end function
显然,如果将上图中的一大块工作移到一个单独的函数中是有益的,那么您应该这样做。
如果您愿意,您可以使用更具体的退出状态,例如,使用几个错误代码和早期完成代码来精确定位退出的原因(甚至位置)。
即使您强制这个函数只有一个出口,我认为您仍然需要指定出口状态。调用者需要知道是否可以使用输出,这有助于维护。
拥有多个出口点本质上与使用GOTO是一样的。这是不是件坏事取决于你对迅猛龙的看法。
我能想到的一个很好的理由是代码维护:您有一个单一的退出点。如果你想改变结果的格式,…,它的实现要简单得多。此外,为了调试,你可以在那里插入一个断点:)
话虽如此,我曾经不得不在一个库中工作,那里的编码标准强制要求“每个函数一个返回语句”,我发现这相当困难。我写了很多数值计算代码,经常有“特殊情况”,所以代码最终很难跟上……
我想说,武断地决定不使用多个出口点是非常不明智的,因为我发现这种技术在实践中一次又一次地有用,事实上,为了清晰起见,我经常将现有的代码重构为多个出口点。我们可以这样比较这两种方法:-
string fooBar(string s, int? i) {
string ret = "";
if(!string.IsNullOrEmpty(s) && i != null) {
var res = someFunction(s, i);
bool passed = true;
foreach(var r in res) {
if(!r.Passed) {
passed = false;
break;
}
}
if(passed) {
// Rest of code...
}
}
return ret;
}
将此与允许多个出口点的代码进行比较
string fooBar(string s, int? i) {
var ret = "";
if(string.IsNullOrEmpty(s) || i == null) return null;
var res = someFunction(s, i);
foreach(var r in res) {
if(!r.Passed) return null;
}
// Rest of code...
return ret;
}
我认为后者要清楚得多。据我所知,现在对多个退出点的批评是一种相当过时的观点。
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语句,特别是如果您正在返回一个值,则很难做到这一点。