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

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


当前回答

对于足够小的函数来说,多个出口点很好——也就是说,可以在一个屏幕长度上完整地查看函数。如果一个冗长的函数同样包含多个出口点,则表明该函数可以进一步分割。

也就是说,除非绝对必要,否则我避免使用多出口功能。在更复杂的函数中,由于一些模糊的行中出现的一些错误返回,我感到了错误的痛苦。

其他回答

一般来说,我尝试从一个函数中只有一个出口点。然而,有时这样做实际上会创建一个比必要的更复杂的函数体,在这种情况下,最好有多个出口点。它确实需要基于结果的复杂性进行“判断”,但目标应该是在不牺牲复杂性和可理解性的情况下尽可能减少出口点。

我能想到的一个很好的理由是代码维护:您有一个单一的退出点。如果你想改变结果的格式,…,它的实现要简单得多。此外,为了调试,你可以在那里插入一个断点:)

话虽如此,我曾经不得不在一个库中工作,那里的编码标准强制要求“每个函数一个返回语句”,我发现这相当困难。我写了很多数值计算代码,经常有“特殊情况”,所以代码最终很难跟上……

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

我意识到这个例子有点做作,但我很想把foreach循环重构成一个LINQ语句,然后将其视为一个保护子句。同样,在一个人为的例子中,代码的意图并不明显,someFunction()可能会有一些其他副作用,或者结果可能会在代码的// Rest中使用....

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

给出以下重构函数:

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

对于足够小的函数来说,多个出口点很好——也就是说,可以在一个屏幕长度上完整地查看函数。如果一个冗长的函数同样包含多个出口点,则表明该函数可以进一步分割。

也就是说,除非绝对必要,否则我避免使用多出口功能。在更复杂的函数中,由于一些模糊的行中出现的一些错误返回,我感到了错误的痛苦。

使用单一的退出点有好处,就像不可避免的“箭头”编程也有坏处一样。

如果在输入验证或资源分配期间使用多个出口点,我尝试将所有“错误出口”非常明显地放在函数的顶部。

“SSDSLPedia”的Spartan Programming文章和“Portland Pattern Repository’s Wiki”的单一函数出口点文章都对此有一些深刻的争论。当然,还有这篇文章要考虑。

例如,如果您真的需要一个单一的退出点(在任何不支持异常的语言中),以便在一个地方释放资源,我发现仔细应用goto是很好的;请看这个相当做作的例子(压缩以节省屏幕空间):

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

就我个人而言,总的来说,我不喜欢箭头编程,而不喜欢多个出口点,尽管两者在正确应用时都很有用。当然,最好的方法是将程序的结构设计成两者都不需要。将你的函数分解成多个块通常会有所帮助:)

尽管在这样做的时候,我发现我最终得到了多个出口点,就像在这个例子中,一些较大的函数被分解成几个较小的函数:

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

根据项目或编码指南的不同,大多数样板代码可以被宏替换。作为旁注,这样分解可以使函数g0、g1、g2非常容易单独测试。

显然,在面向对象和支持异常的语言中,我不会使用这样的if语句(或者根本不会使用,如果我可以用足够少的努力就能摆脱它的话),代码将更加简单。和non-arrowy。大多数非最终回报可能是例外。

简而言之,

少回报比多回报好 多于一次的返回比巨大的箭头更好,并且保护条款通常是可以的。 在可能的情况下,例外可能/应该取代大多数“保护条款”。