一个函数是否只有一个return语句?

有一个很好的理由,为什么在函数中只有一个return语句是一个更好的做法?

或者,只要从逻辑上正确地返回函数就可以了,这意味着函数中可能有很多返回语句?

在一个方法开始时,我经常会有几个声明来返回“简单”的情况。 例如,这个:

public void DoStuff(Foo foo) { if (foo != null) { ... } } 

…可以变得更可读性(恕我直言)这样:

 public void DoStuff(Foo foo) { if (foo == null) return; ... } 

所以是的,我认为可以从函数/方法中获得多个“退出点”。

没有人提到或引用Code Complete,所以我会做。

17.1回报

最小化每个例程中的返回次数 。 如果从底部读它,你不会意识到它返回到某个地方的可能性。

提高可读性时使用返回 。 在某些例程中,一旦知道了答案,就要立即将其返回到调用例程。 如果例程被定义为不需要清理,那么不立即返回意味着你必须编写更多的代码。

我认为,由于我发现该技术在实践中反复使用 ,实际上为了清楚起见,我经常将现有的代码重构为多个退出点,所以要任意决定多个退出点是非常不明智的。 我们可以比较这两种方法:

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

我认为后者是相当清楚的。 据我所知,近来对多个出口点的批评是一个相当古老的观点。

我目前正在研究一个代码库,其中有两个工作的人盲目地订阅“单点退出”理论,我可以告诉你,从经验来看,这是一个可怕的可怕的做法。 它使代码非常难以维护,我会告诉你为什么。

有了“单点退出”理论,你不可避免地会看到这样的代码:

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

这不仅使代码很难遵循,而且现在稍后说,你需要返回并在1和2之间添加一个操作。你不得不简单地缩进整个freaking函数,祝你好运,确保所有你的if / else条件和大括号是正确匹配的。

这种方法使代码维护非常困难和容易出错。

结构化编程说,你应该只有一个函数返回语句。 这是为了限制复杂性。 Martin Fowler等许多人认为使用多个return语句编写函数会更简单。 他在他写的经典的重构书中提出了这个论点。 如果你遵循他的其他build议并编写小函数,这很有效。 我同意这种观点,只有严格的结构化编程纯粹主义者坚持每个function单一的返回语句。

正如Kent Beck在讨论执行模式中的守卫子句时注意到的那样,一个例程有一个单一的入口和出口点。

“是为了防止在同一程序中进出许多地方时出现混淆,当用于FORTRAN或用大量全局数据编写的汇编语言程序,甚至理解哪些语句被执行是困难的时候,这是非常有意义的。用小的方法和大部分的本地数据,是不必要的保守的。“

我发现一个用guard子句写的函数比if then else语句中的一个长嵌套堆更容易遵循。

在一个没有任何副作用的函数中,没有什么理由可以获得多于一个单一的回报,并且应该以function性的风格来编写它们。 在一个有副作用的方法中,事情是更顺序的(时间索引),所以你写一个命令式的风格,使用返回语句作为命令停止执行。

换句话说,如果可能的话,赞成这种风格

 return a > 0 ? positively(a): negatively(a); 

在这

 if (a > 0) return positively(a); else return negatively(a); 

如果你发现自己编写了几层嵌套的条件,那么可能有一种方法可以重构它,例如使用谓词列表。 如果你发现你的ifs和elses在句法上相距甚远,你可能想把它分解成更小的函数。 跨越多个文本的条件块难以阅读。

没有硬性规定适用于每种语言。 像有一个单一的返回语句不会使你的代码好。 但好的代码会让你用这种方式编写你的函数。

我已经在C ++的编码标准中看到了这一点,它们是C语言的一个挂起,就好像你没有RAII或者其他的自动内存pipe理,那么你必须清理每一个返回,这意味着剪切和粘贴清理或者转到(在托pipe语言中逻辑上与'finally'相同),这两者都被认为是不好的forms。 如果您的做法是使用C ++或其他自动内存系统中的智能指针和集合,那么没有强有力的理由,而这完全取决于可读性,更多的是判断性调用。

我倾向于在函数中间返回语句不好的想法。 你可以使用return来在函数的最上面build立几个guard子句,当然也可以告诉编译器在函数结束时返回什么而没有问题,但是在函数中间返回可能很容易错过并且可以使function更难解释。

有一个很好的理由,为什么在函数中只有一个return语句是一个更好的做法?

是的 ,有:

  • 单一的退出点是一个很好的地方来维护你的后置条件。
  • 能够把一个debugging器断点放在函数末尾的一个返回值上通常是有用的。
  • 回报较less意味着复杂度较低。 线性代码通常比较容易理解。
  • 如果试图将一个函数简化为一个单一的返回会导致复杂性,那么这就是重构为更小,更一般,更容易理解的函数的动机。
  • 如果你的语言没有破坏者,或者你没有使用RAII,那么单一的回报会减less你必须清理的地方的数量。
  • 一些语言需要一个单一的出口点(例如,Pascal和Eiffel)。

这个问题通常被认为是多重回报或深层嵌套if语句之间的错误二元对立。 几乎总是有第三个解决scheme是非常线性的(没有深层嵌套),只有一个出口点。

更新 :显然, MISRA指导方针也促进了单次退出 。

要清楚,我并不是说多次回报总是错的。 但是,如果采取其他等效的解决scheme,那么有很多好的理由可以select单一的回报。

拥有一个退出点在debugging中提供了一个优点,因为它允许您在函数结束时设置一个断点,以查看实际将要返回的值。

一般来说,我试图从一个函数只有一个退出点。 然而,有时候这样做实际上最终会创build一个比所需的更复杂的函数体,在这种情况下,最好有多个退出点。 基于最终的复杂性,它必须是一个“判断呼叫”,但是在不牺牲复杂性和可理解性的前提下,目标应该尽可能less的出口点。

不,因为我们不再生活在二十世纪七十年代了 。 如果您的function足够长,以致多个退货是一个问题,那就太长了。

(除了具有exception的语言中的多行函数无论如何都将具有多个出口点)。

除非它真的使事情复杂化,否则我的select是单一退出。 我发现在某些情况下,多个存在点可以掩盖其他更重要的devise问题:

 public void DoStuff(Foo foo) { if (foo == null) return; } 

看到这个代码,我马上会问:

  • 'foo'永远为空吗?
  • 如果是这样的话,有多less个“DoStuff”客户端曾经用一个空'foo'调用函数?

根据这些问题的答案,可能是这样的

  1. 检查是毫无意义的,因为它从来不是真实的(即它应该是一个断言)
  2. 该检查非常less见,所以最好改变这些具体的调用者函数,因为他们应该可能采取一些其他的行动。

在上述两种情况下,代码都可以用断言来重写,以确保'foo'永远不为null,并且相关的调用者被改变。

还有另外两个原因(具体我认为是C ++代码),其中多个存在实际上可以有一个消极的影响。 它们是代码大小和编译器优化。

函数出口处的非POD C ++对象的范围内将调用其析构函数。 如果有多个返回语句,则可能是范围内有不同的对象,因此调用的析构函数列表将会不同。 编译器因此需要为每个return语句生成代码:

 void foo (int i, int j) { A a; if (i > 0) { B b; return ; // Call dtor for 'b' followed by 'a' } if (i == j) { C c; B b; return ; // Call dtor for 'b', 'c' and then 'a' } return 'a' // Call dtor for 'a' } 

如果代码大小是一个问题 – 那么这可能是值得避免的事情。

另一个问题涉及“命名返回值优化”(aka Copy Elision,ISO C ++ '03 12.8 / 15)。 如果可以,C ++允许实现跳过调用复制构造函数:

 A foo () { A a1; // do something return a1; } void bar () { A a2 ( foo() ); } 

只要按照原样进行操作,就会在'foo'中构造对象'a1',然后调用它的复制构造来构造'a2'。 但是,复制elision允许编译器在堆栈的相同位置构造'a1'作为'a2'。 因此,当函数返回时,不需要“复制”对象。

多个退出点使编译器的工作复杂化,试图检测这一点,至less对于相对较新版本的VC ++来说,优化不会发生在函数体有多个返回的地方。 有关更多详细信息,请参阅Visual C ++ 2005中的命名返回值优化 。

拥有一个单一的出口点可以降低循环复杂度 ,因此, 从理论上讲 ,当您更改代码时,可以降低您将代码引入到代码中的几率。 然而,实践往往表明需要更务实的方法。 因此,我倾向于有一个单一的出口点,但是如果这个可读性更好,我的代码就可以有几个。

我强迫自己只使用一个return语句,因为它在某种意义上会产生代码异味。 让我解释:

 function isCorrect($param1, $param2, $param3) { $toret = false; if ($param1 != $param2) { if ($param1 == ($param3 * 2)) { if ($param2 == ($param3 / 3)) { $toret = true; } else { $error = 'Error 3'; } } else { $error = 'Error 2'; } } else { $error = 'Error 1'; } return $toret; } 

(条件很简单…)

条件越多,函数越大,阅读就越困难。 所以,如果你熟悉代码的味道,你会意识到它,并想重构代码。 两种可能的解决scheme是

  • 多个回报
  • 重构成单独的函数

多重回报

 function isCorrect($param1, $param2, $param3) { if ($param1 == $param2) { $error = 'Error 1'; return false; } if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; } if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; } return true; } 

独立的function

 function isEqual($param1, $param2) { return $param1 == $param2; } function isDouble($param1, $param2) { return $param1 == ($param2 * 2); } function isThird($param1, $param2) { return $param1 == ($param2 / 3); } function isCorrect($param1, $param2, $param3) { return !isEqual($param1, $param2) && isDouble($param1, $param3) && isThird($param2, $param3); } 

诚然,这是更长,有点混乱,但在这个重构函数的过程中,我们已经

  • 创造了一些可重用的function,
  • 使得这个function更具人性化的可读性
  • 函数的重点在于为什么值是正确的。

我想说你应该有所需要的,或者是让代码更清洁的东西(比如guard子句 )。

我个人从来没有听说过/看过任何“最佳做法”说你应该只有一个返回声明。

大多数情况下,我倾向于根据逻辑path尽快退出函数(守卫子句就是一个很好的例子)。

我相信多重返回通常是好的(在我用C#编写的代码中)。 单返回风格是C的延期。但你可能不是在C编码。

在所有编程语言中没有法律只需要一个方法的出口点 。 有些人坚持这种风格的优越性,有时把它提升到“规则”或“法律”,但是这种信念没有任何证据或研究的支持。

More than one return style may be a bad habit in C code, where resources have to be explicitly de-allocated, but languages such as Java, C#, Python or JavaScript that have constructs such as automatic garbage collection and try..finally blocks (and using blocks in C#), and this argument does not apply – in these languages, it is very uncommon to need centralised manual resource deallocation.

There are cases where a single return is more readable, and cases where it isn't. See if it reduces the number of lines of code, makes the logic clearer or reduces the number of braces and indents or temporary variables.

Therefore, use as many returns as suits your artistic sensibilities, because it is a layout and readability issue, not a technical one.

I have talked about this at greater length on my blog .

There are good things to say about having a single exit-point, just as there are bad things to say about the inevitable "arrow" programming that results.

If using multiple exit points during input validation or resource allocation, I try to put all the 'error-exits' very visibly at the top of the function.

Both the Spartan Programming article of the "SSDSLPedia" and the single function exit point article of the "Portland Pattern Repository's Wiki" have some insightful arguments around this. Also, of course, there is this post to consider.

If you really want a single exit-point (in any non-exception-enabled language) for example in order to release resources in one single place, I find the careful application of goto to be good; see for example this rather contrived example (compressed to save screen real-estate):

 int f(int y) { int value = -1; void *data = NULL; if (y < 0) goto clean; if ((data = malloc(123)) == NULL) goto clean; /* More code */ value = 1; clean: free(data); return value; } 

Personally I, in general, dislike arrow programming more than I dislike multiple exit-points, although both are useful when applied correctly. The best, of course, is to structure your program to require neither. Breaking down your function into multiple chunks usually help 🙂

Although when doing so, I find I end up with multiple exit points anyway as in this example, where some larger function has been broken down into several smaller functions:

 int g(int y) { value = 0; if ((value = g0(y, value)) == -1) return -1; if ((value = g1(y, value)) == -1) return -1; return g2(y, value); } 

Depending on the project or coding guidelines, most of the boiler-plate code could be replaced by macros. As a side note, breaking it down this way makes the functions g0, g1 ,g2 very easy to test individually.

Obviously, in an OO and exception-enabled language, I wouldn't use if-statements like that (or at all, if I could get away with it with little enough effort), and the code would be much more plain. And non-arrowy. And most of the non-final returns would probably be exceptions.

In short;

  • Few returns are better than many returns
  • More than one return is better than huge arrows, and guard clauses are generally ok.
  • Exceptions could/should probably replace most 'guard clauses' when possible.

You know the adage – beauty is in the eyes of the beholder .

Some people swear by NetBeans and some by IntelliJ IDEA , some by Python and some by PHP .

In some shops you could lose your job if you insist on doing this:

 public void hello() { if (....) { .... } } 

The question is all about visibility and maintainability.

I am addicted to using boolean algebra to reduce and simplify logic and use of state machines. However, there were past colleagues who believed my employ of "mathematical techniques" in coding is unsuitable, because it would not be visible and maintainable. And that would be a bad practice. Sorry people, the techniques I employ is very visible and maintainable to me – because when I return to the code six months later, I would understand the code clearly rather seeing a mess of proverbial spaghetti.

Hey buddy (like a former client used to say) do what you want as long as you know how to fix it when I need you to fix it.

I remember 20 years ago, a colleague of mine was fired for employing what today would be called agile development strategy. He had a meticulous incremental plan. But his manager was yelling at him "You can't incrementally release features to users! You must stick with the waterfall ." His response to the manager was that incremental development would be more precise to customer's needs. He believed in developing for the customers needs, but the manager believed in coding to "customer's requirement".

We are frequently guilty for breaking data normalization, MVP and MVC boundaries. We inline instead of constructing a function. We take shortcuts.

Personally, I believe that PHP is bad practice, but what do I know. All the theoretical arguments boils down to trying fulfill one set of rules

quality = precision, maintainability and profitability.

All other rules fade into the background. And of course this rule never fades:

Laziness is the virtue of a good programmer.

I lean towards using guard clauses to return early and otherwise exit at the end of a method. The single entry and exit rule has historical significance and was particularly helpful when dealing with legacy code that ran to 10 A4 pages for a single C++ method with multiple returns (and many defects). More recently, accepted good practice is to keep methods small which makes multiple exits less of an impedance to understanding. In the following Kronoz example copied from above, the question is what occurs in //Rest of code… ?:

 void string fooBar(string s, int? i) { 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; } 

I realise the example is somewhat contrived but I would be tempted to refactor the foreach loop into a LINQ statement that could then be considered a guard clause. Again, in a contrived example the intent of the code isn't apparent and someFunction() may have some other side effect or the result may be used in the // Rest of code… .

 if (string.IsNullOrEmpty(s) || i == null) return null; if (someFunction(s, i).Any(r => !r.Passed)) return null; 

Giving the following refactored function:

 void string fooBar(string s, int? i) { if (string.IsNullOrEmpty(s) || i == null) return null; if (someFunction(s, i).Any(r => !r.Passed)) return null; // Rest of code... return ret; } 

One good reason I can think of is for code maintenance: you have a single point of exit. If you want to change the format of the result,…, it's just much simpler to implement. Also, for debugging, you can just stick a breakpoint there 🙂

Having said that, I once had to work in a library where the coding standards imposed 'one return statement per function', and I found it pretty tough. I write lots of numerical computations code, and there often are 'special cases', so the code ended up being quite hard to follow…

Multiple exit points are fine for small enough functions — that is, a function that can be viewed on one screen length on its entirety. If a lengthy function likewise includes multiple exit points, it's a sign that the function can be chopped up further.

That said I avoid multiple-exit functions unless absolutely necessary . I have felt pain of bugs that are due to some stray return in some obscure line in more complex functions.

I've worked with terrible coding standards that forced a single exit path on you and the result is nearly always unstructured spaghetti if the function is anything but trivial — you end up with lots of breaks and continues that just get in the way.

Single exit point – all other things equal – makes code significantly more readable. But there's a catch: popular construction

 resulttype res; if if if... return res; 

is a fake, "res=" is not much better than "return". It has single return statement, but multiple points where function actually ends.

If you have function with multiple returns (or "res="s), it's often a good idea to break it into several smaller functions with single exit point.

My usual policy is to have only one return statement at the end of a function unless the complexity of the code is greatly reduced by adding more. In fact, I'm rather a fan of Eiffel, which enforces the only one return rule by having no return statement (there's just a auto-created 'result' variable to put your result in).

There certainly are cases where code can be made clearer with multiple returns than the obvious version without them would be. One could argue that more rework is needed if you have a function that is too complex to be understandable without multiple return statements, but sometimes it's good to be pragmatic about such things.

If you end up with more than a few returns there may be something wrong with your code. Otherwise I would agree that sometimes it is nice to be able to return from multiple places in a subroutine, especially when it make the code cleaner.

Perl 6: Bad Example

 sub Int_to_String( Int i ){ given( i ){ when 0 { return "zero" } when 1 { return "one" } when 2 { return "two" } when 3 { return "three" } when 4 { return "four" } ... default { return undef } } } 

would be better written like this

Perl 6: Good Example

 @Int_to_String = qw{ zero one two three four ... } sub Int_to_String( Int i ){ return undef if i < 0; return undef unless i < @Int_to_String.length; return @Int_to_String[i] } 

Note this is was just a quick example

I vote for Single return at the end as a guideline. This helps a common code clean-up handling … For example, take a look at the following code …

 void ProcessMyFile (char *szFileName) { FILE *fp = NULL; char *pbyBuffer = NULL: do { fp = fopen (szFileName, "r"); if (NULL == fp) { break; } pbyBuffer = malloc (__SOME__SIZE___); if (NULL == pbyBuffer) { break; } /*** Do some processing with file ***/ } while (0); if (pbyBuffer) { free (pbyBuffer); } if (fp) { fclose (fp); } } 

This is probably an unusual perspective, but I think that anyone who believes that multiple return statements are to be favoured has never had to use a debugger on a microprocessor that supports only 4 hardware breakpoints. 😉

While the issues of "arrow code" are completely correct, one issue that seems to go away when using multiple return statements is in the situation where you are using a debugger. You have no convenient catch-all position to put a breakpoint to guarantee that you're going to see the exit and hence the return condition.

The more return statements you have in a function, the higher complexity in that one method. If you find yourself wondering if you have too many return statements, you might want to ask yourself if you have too many lines of code in that function.

But, not, there is nothing wrong with one/many return statements. In some languages, it is a better practice (C++) than in others (C).