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

时间:2020-03-05 18:45:37  来源:igfitidea点击:

是否有充分的理由说明为什么在函数中仅包含一个return语句是一种更好的做法?

还是在逻辑上正确地从函数中返回就可以,这意味着函数中可能有很多return语句?

解决方案

回答

我要说的是,我们应该拥有所需的数量,或者可以使代码更简洁的任何数量(例如,保护子句)。

我个人从未听说过/见过任何"最佳做法",即我们只应该有一份退货声明。

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

回答

我能想到的一个很好的原因是代码维护:我们只有一个出口。如果要更改结果的格式,...,则实现起来要简单得多。另外,为了进行调试,我们可以在此处保留一个断点:)

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

回答

在方法开始时,我经常会有几条语句返回"轻松"情况。例如,这:

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

可以使...更具可读性(恕我直言),如下所示:

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

    ...
}

所以,是的,我认为从一个函数/方法中获得多个"退出点"是很好的。

回答

结构化编程表明每个函数只能有一个return语句。这是为了限制复杂性。许多人(例如Martin Fowler)认为,使用多个return语句编写函数更简单。他在他撰写的经典重构书中提出了这一论点。如果我们遵循他的其他建议并编写了一些小函数,则此方法效果很好。我同意这种观点,并且只有严格的结构化程序设计主义者坚持每个函数使用单个return语句。

回答

我曾使用过糟糕的编码标准,这些标准强制我们使用一个退出路径,并且如果该功能除琐碎的事情外,结果几乎总是非结构化的意大利面-我们最终会遇到很多麻烦,并且继续遇到麻烦。

回答

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

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

我认为后者要清楚得多。据我所知,如今对多个出口点的批评是一种过时的观点。

回答

我在C ++的编码标准中看到了这一点,它是C的遗留物,就好像我们没有RAII或者其他自动内存管理一样,那么我们必须为每次返回清理一下,这意味着剪切和粘贴清理或者跳转(在逻辑上与托管语言中的"最终"相同),这两种形式均被视为错误形式。如果做法是在C ++或者其他自动内存系统中使用智能指针和集合,则没有充分的理由,这全都与可读性有关,而更多的是判断。

回答

正如肯特·贝克(Kent Beck)在讨论实现模式中的保护条款时所指出的那样,使例程具有单个进入点和退出点...

"was to prevent the confusion possible
  when jumping into and out of many
  locations in the same routine. It made
  good sense when applied to FORTRAN or
  assembly language programs written
  with lots of global data where even
  understanding which statements were
  executed was hard work ... with small methods and mostly local data, it is needlessly conservative."

我发现用一个保护子句编写的函数比一堆长的if if else语句容易遵循。

回答

通常,我尝试从一个功能中仅获得一个出口点。但是,有时这样做实际上最终会创建一个比必要的更为复杂的函数体,在这种情况下,最好有多个出口点。实际上,这必须是基于结果复杂性的"判断调用",但是目标应该是在不牺牲复杂性和可理解性的情况下,尽可能地减少出口。

回答

我目前在一个代码库上工作,其中有两个人盲目地接受"单一出口"理论,从经验中我可以告诉你,这是一种可怕的可怕做法。它使代码极难维护,我将向我们展示原因。

使用"单出口"理论,我们不可避免地会遇到如下所示的代码:

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

这种方法使代码维护极为困难且容易出错。

回答

对于足够小的功能,也可以使用多个出口点-即,可以在一个屏幕长度上整体查看的功能。如果一个冗长的函数同样包含多个退出点,则表明该函数可以进一步切分。

也就是说,除非绝对必要,否则我将避免使用多次退出功能。我感到有些错误是由于在更复杂的功能中某些模糊的行中出现了一些杂散返回而引起的。

回答

关于具有单个出口点,有很多好话要说,就像对不可避免的"箭头"编程有不好的话要说。

如果在输入验证或者资源分配过程中使用多个出口点,我会尝试将所有"错误出口"放在功能顶部。

关于" SSDSLPedia"的Spartan Programming文章和" Portland Pattern Repository的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语句(或者,如果我可以花很少的精力就可以摆脱它的话),并且代码会更加简单。和非箭头。而且大多数非最终报酬可能都是例外。

简而言之;

  • 很少有回报比许多回报要好
  • 一个以上的回报比巨大的箭头更好,而且保护子句通常还可以。
  • 在可能的情况下,异常可以/应该替换大多数"保护条款"。

回答

具有单个出口点可降低循环复杂度,因此从理论上讲,可以减少更改代码时将错误引入代码中的可能性。然而,实践往往表明需要一种更为实用的方法。因此,我倾向于目标是具有单个出口点,但是如果我的代码更易读,则允许我的代码具有多个出口点。

回答

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

在某些情况下,它不是很好。有时从函数返回可能非常复杂。我记得一种情况,其中所有功能都必须链接到多个不同的库中。一个库期望返回值是错误/状态代码,而其他则不是。仅使用一个return语句可以节省时间。

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

回答

在没有副作用的函数中,没有充分的理由要有一个以上的收益,而应该以函数样式编写它们。在具有副作用的方法中,事情更具顺序性(按时间索引),因此我们必须以命令式的方式编写代码,并将return语句用作停止执行的命令。

换句话说,在可能的情况下,请偏爱这种风格

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

在这个

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

如果我们发现自己编写了多层嵌套条件,则可能有一种方法可以重构它,例如使用谓词列表。如果发现if和else在语法上相距甚远,则可能需要将其分解为较小的函数。跨越多个屏幕文本的条件块很难阅读。

没有适用于每种语言的一成不变的规则。像只有一个return语句之类的东西不会使代码良好。但是好的代码将倾向于允许我们以这种方式编写函数。

回答

函数中的return语句越多,该方法的复杂度就越高。如果我们想知道自己是否有太多的return语句,则可能要问自己该函数中是否有太多的代码行。

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

回答

由于错误处理,我们已经隐式具有多个隐式return语句,因此请对其进行处理。

但是,就像编程中的典型用法一样,有支持和反对多重返回实践的示例。如果它使代码更清晰,请采用另一种方法。使用许多控制结构会有所帮助(例如case语句)。

回答

在其他所有条件相同的情况下,单个出口点使代码的可读性大大提高。
但是有一个陷阱:受欢迎的建筑

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

是假的," res ="并不比" return"好得多。它具有单个return语句,但是函数实际上在多个点结束。

如果函数具有多个返回值(或者" res =" s"),则通常最好将其分解为几个具有单个退出点的较小函数。

回答

我通常的策略是在函数的末尾仅包含一个return语句,除非通过添加更多代码来大大降低代码的复杂性。实际上,我是Eiffel的粉丝,它通过没有return语句(只有一个自动创建的"结果"变量来放入结果)来强制执行唯一的返回规则。

当然,在某些情况下,与没有返回值的明显版本相比,可以使代码具有多个返回的结果更清晰。有人可能会争辩说,如果函数过于复杂而无法在没有多个return语句的情况下被理解,则需要进行更多的返工,但是有时务实地对待此类事情是件好事。

回答

我使用多个出口点来使错误情况+处理+返回值尽可能接近。

因此,必须测试必须满足的条件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'参数中的值。

回答

如果最终得到的回报不只几个,则代码可能有问题。否则,我会同意有时能够从子例程的多个位置返回是一件好事,尤其是在使代码更简洁的情况下。

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

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

回答

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

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

看到此代码后,我将立即询问:

  • " foo"曾经为空吗?
  • 如果是这样,'DoStuff'的多少个客户曾经使用空'foo'调用该函数?

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

  • 该检查是毫无意义的,因为它永远都不是真的(即它应该是一个断言)
  • 该检查很少是真的,因此最好更改那些特定的调用程序函数,因为它们可能仍应采取其他措施。

在以上两种情况下,都可以使用断言重新编写代码,以确保'foo'永远不会为null且相关的调用者已更改。

还有两个其他原因(我认为是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'
}

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

另一个问题与"命名返回值优化"有关(又名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"。但是,复制省略允许编译器在堆栈上与" a2"相同的位置构造" a1"。因此,函数返回时无需"复制"对象。

多个出口点使编译器尝试检测到此问题变得复杂,并且至少对于VC ++的较新版本,在函数体具有多个返回的情况下并未进行优化。有关更多详细信息,请参见Visual C ++ 2005中的命名返回值优化。

回答

如果管理得当,多次退出是个好选择

第一步是指定退出原因。我的通常是这样的:
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

第二步是让函数外部的世界知道退出的原因。伪代码如下所示:

显然,如果将上图中的大量工作移到一个单独的函数中是有益的,则应该这样做。

如果需要,可以更详细地说明退出状态,例如,使用几个错误代码和提前完成代码来确定退出的原因(甚至位置)。

回答

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

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

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

那给了我们一个出口点,让我们有其他的操作嵌套,但仍然不是真正的深层结构。如果我们不喜欢!Succeeded,则可以随时执行FAILED。这种事情还使我们可以在其他两个检查之间添加其他代码,而不必重新缩进任何内容。

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

回答

如果我们真的很疯狂,那么整个if块也可以被宏化。 :D

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

回答

尽管"箭头代码"的问题是完全正确的,但在使用多个返回语句时似乎已经消失的一个问题是使用调试器的情况。我们没有方便的通用位置放置断点以确保我们将看到出口以及返回条件。

回答

总是需要单个返回类型是没有意义的。我认为可能需要简化某些内容是一个更大的标志。有时有必要获得多个回报,但通常可以通过至少尝试使用一个出口来简化事情。

我知道我会为此而努力,但我是认真的。

从本质上说,返回语句是程序编程时代的后遗症。它们是goto的一种形式,以及break,continue,if,switch / case,while,for,yield和其他大多数现代编程语言中的等效语句。

返回语句有效地" GOTO"了调用函数的位置,并在该范围内分配了一个变量。

退货声明是我所谓的"便捷梦Night"。他们似乎很快就可以完成工作,但是却导致大量的维护麻烦。

这是面向对象编程的最重要和最基本的概念。这是OOP的存在理由。

每当从方法返回任何内容时,基本上就是在从对象中"泄漏"状态信息。状态是否已更改都无关紧要,此信息是否来自其他对象也无关紧要。这是为了使对象的行为超出对象破坏封装的范围。它允许调用者以导致脆弱设计的方式开始操纵对象。

我建议任何开发人员都可以在c2.com或者Wikipedia上阅读有关Demeter法则(LoD)的信息。 LoD是一种设计理念,已在字面上意义上具有真正的"关键任务"软件约束的地方使用,例如JPL。它被证明可以减少代码中的错误数量并提高灵活性。

  • 我们所使用的函数的参数
  • 你自己的属性,
  • 我们在函数中创建的任何对象

有一个很好的类比,以analog狗为基础。 walk狗时,请勿物理上抓住其腿部并移动它们使狗s动。我们命令狗走路,它会照顾自己的腿。这个类比中的return语句等同于让我们抓住它的腿的狗。

我们会注意到,这些都不要求返回语句。我们可能会认为构造函数是一种回报,因此我们将继续前进。实际上,返回是从内存分配器返回的。构造函数只是设置内存中的内容。只要该新对象的封装正常,就可以了,因为正如我们所做的那样,我们可以对其进行完全控制,没有其他人可以破坏它。

直接访问其他对象的属性。吸气剂已经出来了(但是我们知道它们已经很糟了,对吧?)。设置器还可以,但是最好使用构造器。当我们从另一个类继承时,继承是不好的,该类中的任何更改都可能会破坏我们。类型嗅探是不好的(是的,LoD表示基于Java / C ++样式类型的调度是错误地询问类型,甚至隐式地破坏封装。类型是对象的隐式属性。接口是正确的东西)。

那么为什么这都是问题呢?好吧,除非世界与我的世界完全不同,否则我们将花费​​大量时间调试代码。我们不是在编写从未打算重复使用的代码。软件要求正在变化,并且会导致内部API /接口发生变化。每次使用return语句时,我们都引入了一个非常棘手的依赖方法,需要返回任何东西来了解每种情况下将如何使用它们返回的任何东西!接口一旦改变,一端或者另一端都会崩溃,我们将面临冗长而乏味的错误查找。

在代码中,它们确实是恶性肿瘤,因为一旦我们开始使用它们,它们就会促进在其他地方的进一步使用(这就是为什么我们经常可以在对象系统中找到返回的方法链的原因)。

那么替代品是什么?

使用OOP的目的是告诉其他对象该做什么,并让他们来照顾它。因此,我们必须忘记程序的处事方式。真的很容易,永远不要编写return语句。有很多更好的方法可以执行相同的操作:

如果我们确实需要答复,请使用回电。甚至还要传入一个数据结构。这样,我们可以保持界面整洁并易于更改,并且整个系统不那么脆弱,而且适应性更强。它不会降低系统速度,实际上它可以加快系统速度,就像尾部调用优化一样,除了在这种情况下,没有尾部调用,因此我们甚至不必浪费时间来操作带有返回值的堆栈价值观。

如果遵循这些参数,我们将发现实际上根本不需要return语句。

回答

如果我们遵循这些做法,我保证很快我们就会发现,我们花费在寻找错误上的时间更少了,可以更快地适应需求更改,并且在理解自己的代码方面遇到的问题也更少。

为了达到良好的标准和行业的最佳实践,我们必须确定正确数量的退货声明以出现在所有功能中。显然,对于拥有一份退货声明存在共识。因此,我建议将其设置为两个。

如果每个人现在都可以浏览他们的代码,只用一个出口找到任何函数,然后再添加一个出口,我将不胜感激。没关系,在哪里。

回答

毫无疑问,这种改变的结果将是更少的错误,更大的可读性和难以想象的财富从天而降。

回答

唯一重要的问题是"代码如何更简单,可读性更好,更易于理解?"如果它有多个退货比较简单,请使用它们。

回答

我更喜欢单个return语句。尚未指出的原因之一是,某些重构工具对于单出口点效果更好,例如Eclipse JDT提取/内联方法。

回答

我倾向于这样的想法,即函数中间的return语句是不好的。我们可以使用return在函数顶部构建一些保护子句,并且当然可以告诉编译器在函数末尾要返回什么而没有问题,但是在函数中间的return可能很容易遗漏并且可以使函数难以解释。

好吧,也许我是这里的少数几个人中的一员,足以记住为什么"只有一个return语句"如此难行的重要原因之一。这样编译器可以发出更有效的代码。对于每个函数调用,编译器通常会将一些寄存器压入堆栈以保留其值。这样,函数可以将那些寄存器用于临时存储。函数返回时,必须将那些保存的寄存器从堆栈中弹出,然后返回到寄存器中。每个寄存器就是一条POP(或者MOV-(SP),Rn)指令。如果我们有一堆return语句,那么或者每个都必须弹出所有寄存器(这会使编译的代码更大),或者编译器必须跟踪哪些寄存器可能已被修改而仅弹出(减小代码大小,但会增加编译时间)。

回答

今天仍然坚持使用一个return语句仍然有意义的原因之一是易于自动重构。如果IDE支持方法提取重构(选择范围的行并将其转换为方法),那么如果要提取的行中包含return语句,则很难做到这一点,尤其是在返回值的情况下。

17.1返回

没有人提及或者引用"代码完成",所以我会做。

最小化每个例程中的返回数。如果在底部阅读该例程,而又没有意识到该例程可能会返回到上方,则很难理解该例程。

回答

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

有时出于性能原因有必要(有时候,我不想获取具有相同需求的另一种缓存行)。

如果在不使用RAII的情况下分配资源(内存,文件描述符,锁等),则多次返回可能容易出错,并且肯定是重复的,因为需要多次手动进行发布,并且我们必须保持谨慎的状态。

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使多重出口资源问题无济于事。

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

回答

Are there good reasons why it's a better practice to have only one return statement in a function?

最后,我投票支持"单一回报"。这有助于常见的代码清理处理。例如,看下面的代码...

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

是的,有:

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

更新:显然,MISRA准则也促进了单一退出。

回答

明确地说,我并不是说拥有多个回报总是错误的。但是,如果给出了其他等效的解决方案,则有很多充分的理由偏爱具有单收益的解决方案。

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

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

(条件是精明的...)

  • 多次退货
  • 重构为单独的功能

条件越多,功能越大,则读取起来就越困难。因此,如果我们习惯了代码的味道,那么我们将意识到它,并希望重构代码。两种可能的解决方案是:

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

分开的功能

  • 创建了许多可重用的功能,
  • 使该功能更易于阅读,并且
  • 函数的重点在于为什么值正确。

回答

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

回答

具有多个退出点与使用`GOTO'本质上是相同的。这是否一件坏事取决于我们对猛禽的感觉。

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

var retVal = new RetVal();

if(!someCondition)
    return ProcessVal(retVal);

if(!anotherCondition)
   return retVal;

回答

一注。如果在几种情况下但并非在所有情况下都应在返回之前处理返回值,则最好的解决方案(IMHO)定义类似ProcessVal的方法并在返回之前调用它:

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

我完全和绝对不同意"单返回语句理论",并且发现它在代码的可读性,逻辑和描述性方面大多是投机性的,甚至是破坏性的。

对于裸过程式编程,更不用说单次返回的习惯了,更不用说更高级的抽象了(函数式,组合式等)。而且,我希望所有用该样式编写的代码都经过一些特殊的重写解析器,以使其具有多个return语句!

一个函数(如果确实是一个根据"查询-命令分离"注解的函数/查询,例如,请参见Eiffel编程语言),则必须定义与其控制流场景一样多的返回点。它更加清晰并且在数学上是一致的。这是编写函数(即查询)的方式

回答

但是对于突变消息,我不会那么激进,以至于代理确实会收到该过程调用。

我可能会为此而感到讨厌,但理想情况下,我认为应该完全没有return语句,一个函数应仅返回其最后一个表达式,并且在完全理想的情况下应仅包含一个表达式。

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

    //code for non failure
}

所以不是

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

}

反而

回答

对我来说,如果不是陈述式和返回陈述式的if陈述是非常可疑的做法。

我相信多次返回通常是好的(在我用C#编写的代码中)。单返回样式是C的保留。但是我们可能没有使用C进行编码。

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

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

在某些情况下,单项退货更具可读性,而在某些情况下则不易理解。看看它是否减少了代码行数,使逻辑更清晰或者减少了花括号,缩进或者临时变量的数量。

因此,请使用尽可能多的适合艺术敏感性的退货,因为这是布局和可读性的问题,而不是技术性的问题。

回答

我在博客上更详细地讨论了这一点。

不,因为我们不再生活在1970年代。如果函数足够长而导致多次返回是一个问题,那就太长了。

回答

(除了以下事实外,任何语言中的任何多行函数都将有多个出口点,但有例外)。

我们知道格言美人在情人眼中。

有些人对NetBeans发誓,有些人则对IntelliJ IDEA发誓,有些人对Python发誓,而有些人对PHP发誓。

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

如果坚持这样做,在某些商店中,我们可能会失业:

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

我沉迷于使用布尔代数来减少和简化逻辑以及使用状态机。但是,有些同事认为我在编码中使用"数学技术"是不合适的,因为它不可见且不可维护。那将是一个坏习惯。抱歉,我使用的技术对我来说是非常明显和可维护的,因为六个月后当我返回代码时,我会清楚地理解代码,而不是看到一堆意大利面条。

嘿哥们(就像以前的客户曾经说过的)会做我们想做的事,只要我们知道如何解决它,当我需要我们修复它时。

我记得20年前,我的一位同事因采用今天称为敏捷开发策略的工作而被解雇。他有一个细致的增量计划。但是他的经理对他大喊:"我们不能向用户逐步发布功能!我们必须坚持瀑布式设计。"他对经理的回答是,渐进式开发将更精确地满足客户的需求。他相信开发能够满足客户的需求,但是经理却相信能够按照"客户的需求"进行编码。

我们经常因违反数据规范化,MVP和MVC界限而感到内gui。我们内联而不是构造一个函数。我们采取捷径。

quality = precision, maintainability
  and profitability.

我个人认为PHP是不好的做法,但是我知道什么。所有理论上的争论都归结为试图满足一套规则

Laziness is the virtue of a good
  programmer.

回答

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

回答

具有单个出口点确实在调试中提供了一个优势,因为它使我们可以在函数末尾设置单个断点,以查看实际将要返回的值。

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

段落数量不匹配