一个函数应该只有一个return语句吗?

时间:2008-08-31 09:26:56

标签: language-agnostic coding-style

为什么在函数中只有一个return语句更好的做法有充分的理由吗?

或者,一旦逻辑正确就可以从函数返回,这意味着函数中可能有很多返回语句吗?

50 个答案:

答案 0 :(得分:742)

我经常在一个方法的开头有几个语句来返回“简单”的情况。例如,这个:

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

...可以像这样更具可读性(恕我直言):

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

    ...
}

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

答案 1 :(得分:355)

没有人提及或引用Code Complete所以我会这样做。

17.1返回

最大限度地减少每个例行程序的退货次数。理解一个例程是比较困难的,如果从底部读到它,你就不会意识到它返回到某个地方的可能性。

在增强可读性时使用返回 。在某些例程中,一旦知道答案,就要立即将其返回到调用例程。如果例程的定义方式不需要任何清理,则不立即返回意味着您必须编写更多代码。

答案 2 :(得分:229)

我会说任意对多个退出点进行任意决定是非常不明智的,因为我发现该技术在实践中一遍又一遍地有用,事实上我经常重构为清晰起见,现有代码到多个出口点。我们可以比较这两种方法: -

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

我认为后者相当清楚。据我所知,多个退出点的批评现在是一个相当陈旧的观点。

答案 3 :(得分:191)

我目前正在开发一个代码库,其中两个工作的人盲目地订阅了“单点退出”理论,我可以告诉你,根据经验,这是一个可怕的可怕做法。它使代码非常难以维护,我会告诉你原因。

使用“单点退出”理论,您不可避免地会遇到如下代码:

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条件和大括号都匹配正确。

此方法使代码维护极其困难且容易出错。

答案 4 :(得分:72)

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

答案 5 :(得分:62)

正如Kent Beck在Implementation Patterns中讨论保护条款时所说的那样,例程中只有一个入口和出口点......

  

“是为了防止混乱   当进出许多人时   同一例程中的位置。它做了   应用于FORTRAN时的良好意义   编写汇编语言程序   甚至还有很多全球数据   了解哪些陈述   执行起来很辛苦......用小方法和大多数本地数据,这是不必要的保守。“

我发现一个用guard子句编写的函数比一组长的if then else语句更容易理解。

答案 6 :(得分:61)

在没有副作用的函数中,没有充分的理由要有多个返回,你应该用函数式编写它们。在具有副作用的方法中,事物更顺序(时间索引),因此您使用return语句作为命令停止执行,以命令式样式编写。

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

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

这个

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

如果您发现自己编写了几层嵌套条件,那么可能有一种方法可以使用谓词列表进行重构。如果您发现ifs和elses在语法上相距甚远,您可能希望将其分解为更小的函数。一个超过一屏文本的条件块很难阅读。

没有适用于每种语言的硬性规则。像一个返回语句之类的东西不会使你的代码变好。但好的代码往往会让你以这种方式编写函数。

答案 7 :(得分:43)

我已经在C ++的编码标准中看到过,这是C语言的遗留问题,好像你没有RAII或其他自动内存管理那么你必须为每次返回进行清理,这或者意味着切换 - 并且 - 粘贴清理或goto(逻辑上与托管语言中的'finally'相同),这两种都被认为是不良形式。如果你的做法是在C ++或其他自动内存系统中使用智能指针和集合,那么就没有强有力的理由,它就变成了可读性和更多的判断调用。

答案 8 :(得分:40)

我倾向于认为函数的 middle 中的return语句是坏的。你可以使用return在函数的顶部构建一些guard子句,当然告诉编译器在函数结束时返回什么没有问题,但返回函数的 middle 可能容易错过,并且可能使函数难以解释。

答案 9 :(得分:38)

  

为什么在函数中只有一个return语句更好的做法有充分的理由吗?

,有:

  • 单一出口点是确定后置条件的绝佳场所。
  • 能够在函数末尾的一个返回上放置调试器断点通常很有用。
  • 更少的回报意味着更少的复杂性。线性代码通常更容易理解。
  • 如果尝试将函数简化为单一返回会导致复杂性,那么这就是重构更小,更通用,更易于理解的函数的动机。
  • 如果您使用的语言没有析构函数或者您不使用RAII,那么单次返回会减少您需要清理的地点数量。
  • 某些语言需要一个退出点(例如,Pascal和Eiffel)。

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

更新:显然也是MISRA guidelines promote single exit

要清楚,我并不是说多次返回总是错误。但是考虑到其他方面的等效解决方案,有很多理由可以选择单一回报。

答案 10 :(得分:33)

答案 11 :(得分:19)

一般来说,我尝试只从一个函数中获得一个退出点。然而,有时候这样做实际上最终会创建一个比必要的更复杂的函数体,在这种情况下,最好有多个出口点。它必须是基于由此产生的复杂性的“判断调用”,但目标应该是尽可能少的退出点而不牺牲复杂性和可理解性。

答案 12 :(得分:14)

不,因为we don't live in the 1970s any more。如果你的功能足够长,多次返回都是个问题,那就太长了。

(除了具有例外的语言中的任何多行功能无论如何都会有多个退出点这一事实。)

答案 13 :(得分:14)

我的偏好是单退出,除非它真的让事情复杂化。我发现在某些情况下,多个存在点可以掩盖其他更重要的设计问题:

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

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

  • 'foo'永远是空的吗?
  • 如果是这样,“DoStuff”有多少客户端使用null'foo'调用该函数?

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

  1. 检查是没有意义的,因为它永远不是真的(即它应该是一个断言)
  2. 检查很少是真的,所以最好更改那些特定的调用函数,因为他们应该采取其他一些行动。
  3. 在上述两种情况下,可能会使用断言重新编写代码,以确保'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'
    }
    

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

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

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

    只需按原样执行代码,对象'a1'在'foo'中构造,然后调用其复制构造来构造'a2'。但是,copy elision允许编译器在堆栈的同一位置构造'a1'作为'a2'。因此,当函数返回时,无需“复制”对象。

    多个出口点使编译器在尝试检测到这一点时的工作变得复杂,并且至少对于VC ++的相对较新版本,在函数体具有多个返回的情况下不进行优化。有关详细信息,请参阅Named Return Value Optimization in Visual C++ 2005

答案 14 :(得分:11)

只有一个退出点会减少Cyclomatic Complexity因此理论上 会降低在更改代码时将错误引入代码的可能性。然而,实践往往表明需要更务实的方法。因此,我倾向于只有一个退出点,但如果可读性更高,则允许我的代码有几个。

答案 15 :(得分:11)

我强迫自己只使用一个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;
}

(条件是arbritary ......)

条件越多,函数越大,读取的难度就越大。因此,如果你对代码气味很熟悉,你就会意识到这一点,并希望重构代码。两种可能的解决方案是:

  • 多次退货
  • 重构为单独的函数

多次退货

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

当然,它更长,有点凌乱,但在以这种方式重构函数的过程中,我们已经

  • 创建了许多可重用的函数,
  • 使该功能更具人性化,
  • 功能的重点是价值正确的原因。

答案 16 :(得分:10)

关于单一退出点,有很多好的话要说,就像对不可避免的"arrow"编程产生不好的说法一样。

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

“SSDSLPedia”的Spartan Programming文章和“Portland Pattern Repository Wiki”的the single function exit point文章都有一些有见地的论据。当然,还有这篇文章需要考虑。

如果你真的想要一个退出点(在任何非异常启用的语言中),例如为了在一个地方释放资源,我发现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非常容易单独测试。

显然,在一个支持OO和异常的语言中,我不会使用像这样的if语句(或者根本不使用if,如果我能用很少的努力侥幸逃脱它),代码就会更加简单。并且非箭头。大多数非最终回报可能都是例外。

简而言之;

  • 很少有回报比许多回报更好
  • 不止一次返回比巨箭更好,guard clauses通常都可以。
  • 在可能的情况下,例外可能/应该取代大多数“保护条款”。

答案 17 :(得分:10)

我相信多次返回通常都很好(在我用C#编写的代码中)。单回归风格是C的保留。但你可能不是用C编码。

在所有编程语言中,没有法律只要求一个方法的退出点。有些人坚持这种风格的优越性,有时他们将其提升为“规则”或“法律”,但这种信念并没有任何证据或研究支持。

在C代码中,多个返回样式可能是一个坏习惯,其中必须显式地取消分配资源,但是具有诸如自动垃圾收集和{{1}之类的构造的Java,C#,Python或JavaScript等语言块(和C#中的try..finally块),并且这个参数不适用 - 在这些语言中,需要集中的手动资源释放是非常罕见的。

有些情况下,单个返回更易读,而有些情况则不可读。看看它是否减少了代码行数,使逻辑更清晰或减少了大括号和缩进或临时变量的数量。

因此,请使用尽可能多的回报以适合您的艺术感受,因为它是布局和可读性问题,而不是技术问题。

我已经谈过this at greater length on my blog

答案 18 :(得分:10)

我会说你应该拥有所需数量,或任何使代码更清晰的东西(例如guard clauses)。

我个人从未听过/看过任何“最佳做法”,说你应该只有一份回复声明。

在大多数情况下,我倾向于根据逻辑路径尽快退出函数(保护条款是一个很好的例子)。

答案 19 :(得分:9)

你知道谚语 - 美丽在旁观者的眼中

有些人在NetBeans发誓,有些人在IntelliJ IDEA发誓,有些人在Python发誓,有些人在PHP发誓。

在某些商店,如果你坚持这样做,你可能会失去工作:

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

问题在于可见性和可维护性。

我沉迷于使用布尔代数来减少和简化逻辑和状态机的使用。然而,过去有些同事认为我在编码中使用“数学技术”是不合适的,因为它不可见和可维护。这将是一个糟糕的做法。对不起的人,我使用的技术对我来说是非常明显和可维护的 - 因为当我六个月后回到代码时,我会清楚地理解代码,而不是看到一堆众所周知的意大利面。

嘿哥们(就像以前的客户说的那样)做你想做的事,只要你知道如何解决它我需要你解决它。

我记得20年前,我的一位同事因雇用今天所谓的agile development策略而被解雇。他有一个细致的增量计划。但他的经理却对他大吼大叫“你无法逐步向用户发布功能!你必须坚持使用waterfall。”他对经理的回应是增量开发会更准确地满足客户的需求。他相信开发满足客户的需求,但经理相信编码符合“客户的要求”。

我们经常因违反数据规范化,MVPMVC边界而感到内疚。我们内联而不是构建一个函数。我们采取捷径。

就个人而言,我认为PHP是不好的做法,但我知道什么。所有的理论论点归结为尝试实现一套规则

  

质量=精确度,可维护性   和盈利能力。

所有其他规则都淡入背景。当然,这条规则永远不会消退:

  

懒惰是善的美德   程序员。

答案 20 :(得分:9)

我倾向于使用保护条款提前返回,否则在方法结束时退出。单个进入和退出规则具有历史意义,在处理具有多个返回(以及许多缺陷)的单个C ++方法的10个A4页面的遗留代码时尤其有用。最近,公认的良好做法是保持方法较小,这使得多个出口对理解的阻抗较小。在下面从上面复制的Kronoz示例中,问题是 //其余代码中发生了什么...... ?:

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()可能会产生一些其他副作用,或者结果可能会在 //其余代码中使用。

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

答案 21 :(得分:7)

我能想到的一个很好的理由是代码维护:你只有一个退出点。如果你想改变结果的格式,......,它实现起来要简单得多。另外,对于调试,你可以在那里坚持一个断点:)

话虽如此,我曾经不得不在一个库中工作,其中编码标准强加了“每个函数一个返回语句”,我发现它非常难。我写了很多数值计算代码,经常有“特殊情况”,所以代码最终很难遵循...

答案 22 :(得分:7)

多个退出点适用于足够小的功能 - 也就是说,可以在一个屏幕上查看整个功能。如果冗长的功能同样包括多个出口点,则表明该功能可以进一步切断。

这就是说除非绝对必要,否则我会避免多退出函数。我感到很多错误的痛苦,这些错误是由于在更复杂的功能中某些模糊的线路中出现了一些错误的回归。

答案 23 :(得分:6)

单一退出点 - 所有其他条件相同 - 使代码更具可读性。 但有一个问题:流行建筑

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

是假的,“res =”并不比“返回”好多少。它有单个return语句,但函数实际结束的多个点。

如果你有多个返回函数(或“res =”s),通常最好将它分成几个带有单个退出点的小函数。

答案 24 :(得分:6)

我已经处理了可怕的编码标准,迫使你只有一条退出路径,结果几乎总是非结构化的意大利面,如果这个功能不是什么小事 - 你最终会有很多休息时间并继续进入方式。

答案 25 :(得分:6)

我通常的策略是在函数末尾只有一个return语句,除非通过添加更多代码来大大减少代码的复杂性。事实上,我更喜欢Eiffel,它通过没有return语句强制执行唯一的返回规则(只有一个自动创建的'result'变量来放置你的结果)。

在某些情况下,如果没有它们,明显的版本可以使代码更清晰。有人可能会争辩说,如果你的函数太复杂而没有多个return语句就无法理解,那么就需要更多的返工。但有时候对这些事情务实是好的。

答案 26 :(得分:5)

如果您最终获得了多次退货,那么您的代码可能会出现问题。否则我会同意,有时能够从子例程中的多个位置返回是很好的,特别是当它使代码更清晰时。

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

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

答案 27 :(得分:5)

我最后投票给单回报作为指导。这有助于常见代码清理处理 ...例如,请查看以下代码...

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

答案 28 :(得分:4)

这可能是一个不同寻常的观点,但我认为任何认为多个返回语句都受到青睐的人从来不必在仅支持4个硬件断点的微处理器上使用调试器。 ; - )

虽然“箭头代码”的问题完全正确,但在使用多个return语句时似乎消失的一个问题是您正在使用调试器。您没有方便的全部位置来设置断点以保证您将看到退出并因此返回条件。

答案 29 :(得分:4)

如果可以写下一个意见,那就是我的意思:

我完全并且完全不同意“单一返回语句理论”,并且发现它主要是关于代码可读性,逻辑和描述性方面的推测性甚至是破坏性的。

对于简单的程序编程而言,单一返回的习惯甚至很差,更不用说更高级别的抽象(功能,组合等)。此外,我希望以该样式编写的所有代码都经过一些特殊的重写解析器,以使其具有多个返回语句!

一个函数(如果它实际上是一个函数/查询,根据`查询 - 命令分离'注释 - 参见Eiffel编程语言。例如)只需定义与其具有的控制流场景一样多的返回点。它更清晰,数学上更一致;它是编写函数(即查询)

的方法

但是,对于你的代理人确实收到的变异信息,我不会那么激进 - 过程调用。

答案 30 :(得分:4)

函数中返回的语句越多,该方法的复杂性就越高。如果您发现自己想知道是否有太多的返回语句,您可能想问问自己该函数中是否有太多代码行。

但是,不是,一个/多个return语句没有错。在某些语言中,它比其他语言(C)更好(C ++)。

答案 31 :(得分:3)

拥有多个退出点与使用GOTO基本相同。这是否是一件坏事取决于你对猛禽的感受。

答案 32 :(得分:3)

始终要求单一返回类型没有意义。我认为这可能需要简化一些标志。有时需要多次返回,但通常至少可以通过尝试来保持一个退出点。

答案 33 :(得分:3)

唯一重要的问题是“代码如何更简单,更易读,更容易理解?”如果多次返回更简单,那就使用它们。

答案 34 :(得分:2)

好吧,也许我是这里为数不多的人之一,记得为什么“只有一个回复声明”被推得如此艰难的一个重要原因。这样编译器就可以发出更高效的代码。对于每个函数调用,编译器通常会在堆栈上推送一些寄存器以保留它们的值。这样,该函数可以使用这些寄存器进行临时存储。当函数返回时,那些保存的寄存器必须从堆栈中弹出并返回寄存器。这是每个寄存器的一个POP(或MOV - (SP),Rn)指令。如果你有一堆返回语句,那么每个都必须弹出所有寄存器(这使得编译的代码更大)或者编译器必须跟踪哪些寄存器可能已被修改并且只弹出那些(减小代码大小,但是增加编译时间。)

今天尝试坚持使用一个return语句仍然有意义的一个原因是易于自动重构。如果您的IDE支持方法提取重构(选择一系列行并将它们转换为方法),如果要提取的行中包含return语句,则很难执行此操作,尤其是在返回值时

答案 35 :(得分:2)

您已经隐式地拥有多个由错误处理引起的隐式返回语句,因此要处理它。

与编程一样,有一些例子支持和反对多重回归实践。如果它使代码更清晰,那么就这样做。使用许多控制结构可以提供帮助(例如 case 语句)。

答案 36 :(得分:1)

我更喜欢单一的退货声明。尚未指出的一个原因是一些重构工具对于单个退出点更好地工作,例如, Eclipse JDT提取/内联方法。

答案 37 :(得分:1)

为了良好标准行业最佳实践,我们必须建立正确数量的返回语句以显示在所有函数中。显然有人反对一个回复声明。所以我建议我们把它设置为两个。

如果每个人现在都查看他们的代码,找到只有一个退出点的任何函数,并添加另一个函数,我将不胜感激。无所谓。

这种变化的结果无疑将是更少的错误,更高的可读性和难以想象的财富从天而降到我们头上。

答案 38 :(得分:1)

有些时候出于性能原因需要它(我不想获取与继续相同的需求的其他缓存行;有时)。

如果你在不使用RAII的情况下分配资源(内存,文件描述符,锁等),那么muliple返回可能容易出错并且肯定是重复的,因为需要多次手动完成发布,你必须小心跟踪。 / p>

在示例中:

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{写它 它变得有点难看。

但是RAII使多重退出资源问题没有实际意义。

答案 39 :(得分:1)

我总是避免多个return语句。即使是小功能。小功能可以变得更大,并且跟踪多个返回路径使得(我的小脑子)更难以跟踪正在发生的事情。单个返回也使调试更容易。我见过有人发帖说,多个return语句的唯一替代方案是嵌套IF语句10层深的杂乱箭头。虽然我确实同意这种编码确实发生,但它不是唯一的选择。我不会在多个返回语句和IF嵌套之间做出选择,我会重构它,所以你要消除它们。这就是我编码的方式。以下代码消除了这两个问题,在我看来,它很容易理解:

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

答案 40 :(得分:1)

我使用多个退出点使错误案例+处理+返回值尽可能接近。

因此必须测试必须为真的条件a,b,c并且你需要以不同的方式处理每个条件:

if (a is false) {
    handle this situation (eg. report, log, message, etc.)
    return some-err-code
}
if (b is false) {
    handle this situation
    return other-err-code
}
if (c is false) {
    handle this situation
    return yet-another-err-code
}

perform any action assured that a, b and c are ok.

a,b和c可能是不同的东西,例如a是输入参数检查,b是指向新分配的内存的指针,c是检查'a'参数中的值。

答案 41 :(得分:0)

你可以这样做只实现一个return语句 - 在开始时声明它并在结束时输出它 - 问题解决了:

$content = "";
$return = false;

if($content != "")
{
  $return = true;
}
else 
{
  $return = false;
}

return $return;

答案 42 :(得分:0)

这主要是Fortran的遗留问题,可以将多个语句标签传递给函数,以便它可以返回到其中任何一个。

所以这种代码完全有效

       CALL SOMESUB(ARG1, 101, 102, 103)
C Some code
 101   CONTINUE
C Some more code
 102   CONTINUE
C Yet more code
 103   CONTINUE
C You get the general idea

但被调用的函数决定了代码路径的去向。高效?大概。维护?没有。

这就是规则的来源(顺便说一下,函数没有多个入口点,这在fortran和汇编程序中是可能的,但在C中则不行。)

然而,这样的措辞似乎可以应用于其他语言(关于多个入口点的那个可以应用于其他语言,因此它不是真正的程序)。因此规则得以延续,即使它涉及一个完全不同的问题,并且不适用。

对于更多结构化语言,需要删除该规则或至少考虑更多。当然,一个散布着回报的功能很难理解,但在开始时回归并不是问题。在某些C ++编译器中,如果您只从一个地方返回一个值,则单个返回点可能会产生更好的代码。

但最初的规则被误解,误用。并且不再相关。

答案 43 :(得分:0)

有人可能会争辩......如果你有多个条件必须满足之前要执行函数的任务,那么在满足这些条件之前不要调用函数:< / p>

而不是:

function doStuff(foo) {
    if (foo != null) return;
}

function doStuff(foo) {
    if (foo !== null) {
        ...
    }
}

foo!= null

之前不要调用doStuff
if(foo != null) doStuff(foo);

其中,要求每个呼叫站点确保在呼叫之前满足调用条件。如果有多个呼叫站点,这个逻辑可能最好放在一个单独的函数中,在一个待调用函数的方法中(假设它们是一等公民),或者在一个代理中

关于函数是否在数学上可证明的主题,请考虑语法上的逻辑。如果一个函数有多个返回点,这并不意味着(默认情况下)它在数学上是不可证明的。

答案 44 :(得分:0)

作为嵌套IF的替代方案,有一种方法可以使用do / while(false)在任何地方突破:

    function()
    {
        HRESULT error = S_OK;

        do
        {
            if(!SUCCEEDED(Operation1()))
            {
                error = OPERATION1FAILED;
                break;
            }

            if(!SUCCEEDED(Operation2()))
            {
                error = OPERATION2FAILED;
                break;
            }

            if(!SUCCEEDED(Operation3()))
            {
                error = OPERATION3FAILED;
                break;
            }
            if(!SUCCEEDED(Operation4()))
            {
                error = OPERATION4FAILED;
                break;
            }
        } while (false);

        return error;
    }

这会让你获得一个退出点,让你有其他的操作嵌套,但仍然不是一个真正的深层结构。如果你不喜欢!SUCCEEDED你可以随时做失败。这种事情还允许您在任何其他两个检查之间添加其他代码,而不必重新缩进任何内容。

如果你真的疯了,整个if块也可以被宏大化。 :d

    #define BREAKIFFAILED(x,y) if (!SUCCEEDED((x))) { error = (Y); break; }

    do
    {
        BREAKIFFAILED(Operation1(), OPERATION1FAILED)
        BREAKIFFAILED(Operation2(), OPERATION2FAILED)
        BREAKIFFAILED(Operation3(), OPERATION3FAILED)
        BREAKIFFAILED(Operation4(), OPERATION4FAILED)
    } while (false);

答案 45 :(得分:0)

我可能会对此感到讨厌,但理想情况下应该有 no 返回语句我觉得,函数应该只返回它的最后一个表达式,并且应该在完全理想的情况下只包含一个。

所以不是

function name(arg) {
    if (arg.failure?)
        return;

    //code for non failure
}

而是

function name(arg) {
    if (arg.failure?)
        voidConstant
    else {
        //code for non failure


}

if-statements不是表达式和​​return语句对我来说是一个非常可疑的练习。

答案 46 :(得分:0)

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

一个注意事项。如果你应该在几种情况下返回之前处理返回值,但不是全部,最好的解决方案(恕我直言)定义一个像ProcessVal这样的方法并在返回之前调用它:

var retVal = new RetVal();

if(!someCondition)
    return ProcessVal(retVal);

if(!anotherCondition)
   return retVal;

答案 47 :(得分:-1)

我通常赞成多个return语句。它们最容易阅读。

有些情况下不好。有时从函数返回可能非常复杂。我记得有一个案例,其中所有函数都必须链接到多个不同的库。一个库期望返回值为错误/状态代码,而其他库则没有。拥有一个return语句可以节省时间。

我很惊讶没有人提到过。 Goto不是每个人都会相信的编程的祸根。如果每个函数必须只有一个返回值,请将其放在最后并使用gotos根据需要跳转到该return语句。绝对避免标志和箭头编程既丑陋又运行缓慢。

答案 48 :(得分:-2)

如果管理得好,可以多次退出

第一步是指定退出的原因。我的通常是这样的:
1.无需执行功能
2.发现错误 3.提前完成 4.正常完成
我想你可以将“1.无需执行功能”分组为“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

显然,如果将上图中的一部分工作移到一个单独的函数中是有益的,那么你应该这样做。

如果您愿意,您可以更具体地说明退出状态,例如,使用多个错误代码和早期完成代码来查明退出的原因(甚至位置)。

即使您将此功能强制为只有一个退出的功能,我认为您仍然需要指定退出状态。调用者需要知道是否可以使用输出,这有助于维护。

答案 49 :(得分:-2)