是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?

或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?


当前回答

我认为在不同的情况下,不同的方法更好。例如,如果您应该在返回之前处理返回值,则应该有一个退出点。但在其他情况下,使用多次返回会更舒服。

一个音符。如果在某些情况下,你应该在返回之前处理返回值,但不是所有情况下,最好的解决方案(IMHO)是定义一个像ProcessVal这样的方法,并在返回之前调用它:

var retVal = new RetVal();

if(!someCondition)
    return ProcessVal(retVal);

if(!anotherCondition)
   return retVal;

其他回答

我倾向于单一退出,除非事情真的变得复杂。我发现在某些情况下,多个存在点可以掩盖其他更重要的设计问题:

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

在看到这段代码时,我马上会问:

'foo'是否为空? 如果是这样,有多少客户端'DoStuff'曾经调用一个空'foo'函数?

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

这种检查毫无意义,因为它从来都不是真的。它应该是一个断言) 这种检查很少是正确的,所以最好改变那些特定的调用函数,因为它们可能应该采取一些其他的操作。

在上述两种情况下,代码可能都可以用断言重做,以确保'foo'永远不为空,并更改相关的调用者。

还有另外两个原因(我认为是针对c++代码的),多重存在实际上会产生负面影响。它们是代码大小和编译器优化。

在函数出口作用域中的非pod c++对象将调用其析构函数。如果有几个return语句,那么作用域中可能有不同的对象,因此要调用的析构函数列表也会不同。因此,编译器需要为每个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'
}

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

另一个问题涉及到“命名返回值优化”(又名复制省略,ISO c++ '03 12.8/15)。c++允许实现在可以的情况下跳过调用复制构造函数:

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

void bar () {
  A a2 ( foo() );
}

就像代码一样,对象'a1'是在'foo'中构造的,然后它的复制构造将被调用来构造'a2'。然而,复制省略允许编译器在堆栈上与'a2'相同的位置构造'a1'。因此,当函数返回时,不需要“复制”对象。

多个出口点会使编译器的检测工作复杂化,至少对于相对较新的vc++版本,优化不会发生在函数体有多个返回的地方。有关详细信息,请参阅Visual c++ 2005中的命名返回值优化。

是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?

是的,有:

The single exit point gives an excellent place to assert your post-conditions. Being able to put a debugger breakpoint on the one return at the end of the function is often useful. Fewer returns means less complexity. Linear code is generally simpler to understand. If trying to simplify a function to a single return causes complexity, then that's incentive to refactor to smaller, more general, easier-to-understand functions. If you're in a language without destructors or if you don't use RAII, then a single return reduces the number of places you have to clean up. Some languages require a single exit point (e.g., Pascal and Eiffel).

这个问题通常被提出为多个返回或深度嵌套的if语句之间的错误二分法。几乎总有第三种解决方案,它是线性的(没有深度嵌套),只有一个出口点。

更新:MISRA的指导方针显然也提倡单次退出。

需要澄清的是,我并不是说拥有多个回报总是错误的。但如果有其他等价的解决方案,有很多很好的理由选择单一回报的方案。

我曾经使用过糟糕的编码标准,强迫你使用单一的退出路径,如果函数不是琐碎的,结果几乎总是非结构化的意大利面条——你最终会遇到许多中断和继续。

I always avoid multiple return statements. Even in small functions. Small functions can become larger, and tracking the multiple return paths makes it harder (to my small mind) to keep track of what is going on. A single return also makes debugging easier. I've seen people post that the only alternative to multiple return statements is a messy arrow of nested IF statements 10 levels deep. While I certain agree that such coding does occur, it isn't the only option. I wouldn't make the choice between a multiple return statements and a nest of IFs, I'd refactor it so you'd eliminate both. And that is how I code. The following code eliminates both issues and, in my mind, is very easy to read:

public string GetResult()
{
    string rv = null;
    bool okay = false;

    okay = PerformTest(1);

    if (okay)
    {
        okay = PerformTest(2);
    }

    if (okay)
    {
        okay = PerformTest(3);
    }

    if (okay)
    {
        okay = PerformTest(4);
    };

    if (okay)
    {
        okay = PerformTest(5);
    }

    if (okay)
    {
        rv = "All Tests Passed";
    }

    return rv;
}

如果你最终得到了很多返回值,那么你的代码可能有问题。否则,我同意有时能够从子例程的多个位置返回是很好的,特别是当它使代码更干净的时候。

Perl 6:坏例子

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

这样写会更好吗

Perl 6:好例子

@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]
}

注意,这只是一个简单的例子