“只有一个回报”的概念来自哪里?

2021-05-07 03:24:37

我经常与编程师交谈,他们说" Don' t以相同的方法放置多个返回陈述。"当我让他们告诉我原因时,我得到的就是"编码标准说。"或"它令人困惑。"当他们向我展示具有单个返回声明的解决方案时,代码对我来说看起来很丑陋。例如:

这个50%的代码膨胀如何使程序更容易理解?就个人而言,我发现它更难,因为状态空间刚刚增加了另一个可以容易被阻止的变量。

这个概念在哪里"只有一个返回"来自?有没有历史原因是本公约来了?

21这有点连接到防护条款重构。 stackoverflow.com/a/8493256/679340 guard子句将添加返回的方法开始。它在我看来,它会使代码更清洁。 - piotr perak.

它来自结构化编程的概念。有些人可能会争辩说只有一个返回允许您轻松修改代码,以便在返回之前或轻松调试。 - Martinkunev.

我认为这个例子是一个简单的足够案例,在那里我会有一个强有力的意见或另一方面。单入口单退出的理想是更多的,使我们远离疯狂的情况,如15返回陈述,以及两个人完全没有回到的分支机构! - Mendota.

这是我读过的最糟糕的文章之一。看起来提交人花了更多的时间幻想他的oop的纯洁,而不是实际上弄清楚如何实现任何东西。表达和评估树具有价值,但不是当您只能编写正常功能时。 - Deadmg.

"单个条目,单个出口"当大多数编程以汇编语言,FORTRAN或COBOL完成时,是写的。它已被广泛误解,因为现代语言不支持实践Dijkstra警告。

"单个条目"意思是"不要为函数创建替代的入口点"当然,在汇编语言中,可以在任何指令中输入功能。 Fortran支持使用条目语句的函数的多个条目:

子程序S(x,y)r = sqrt(x * x + y * y)c arderate条目在R已知已知条目S2(r)...返回endc使用呼叫s(3,4)c alterface使用呼叫S2(5)

"单个出口"意味着函数应该只返回一个地方:呼叫后立即的语句。它并不意味着函数应该只能从一个地方返回。写入结构化编程时,通过返回备用位置来表示误差是常见的做法。 Fortran支持这一点"替代返回&#34 ;:

C子程序与备用返回。 ' *'是错误返回子程序qsolve(a,b,c,x1,x2,*)的放置符号符号= b * b-4 * a * cc没有解决方案,如果差异,则返回错误处理位置.lt。 0返回1 SD = SQRT(SCSC)enmom = 2 * x1 =(-b + sd)/ enmom x2 =(-b - sd)/ endom return endc使用备用返回呼叫qsolve(1,0,1,x1 ,x2,* 99)c解决方案找到... c qsolve如果没有解决方案99打印'没有解决方案'

这两种技术都易于出错。使用备用条目通常会留下一些无初始化的变量。使用备用返回的使用具有GOTO语句的所有问题,以及分支条件与分支不相邻的额外复杂化,但在子程序中的某个位置。

感谢Alexey Romanov寻找原始纸张。查看http://www.cs.utexas.edu/users/ewd/ewd02xx/ewd249.pdf,第28页(打印页面号为24)。不限于函数。

17和Don' t忘记了意大利面条代码。使用转孔而不是返回的子程序来退出,返回函数调用参数并在堆栈上返回地址并不知道。单个出口被推广为至少汇集到返回声明的所有代码路径的方式。 - TMN.

@TMN:在早期,大多数机器都没有硬件堆栈。递归通常不支持。子程序参数和返回地址存储在与子程序代码相邻的固定位置中。返回只是一个间接转到。 - Kevin Cline.

@Kevin:是的,但根据你这个并不是意味着它被发明的东西。 (顺便说一句,我实际上合理地确保弗雷德问道是对目前对&#34解释的偏好;单个出口和#34;来自。)此外,在这里的许多用户出生之前,C已经常常。 ,所以即使在C中,也不需要资本常数。但Java保留了所有那些坏老的习惯。 - SBI.

因此,异常是否违反了对单一出口的解释? (或他们更加原始的堂兄,setjmp / longjmp?) - 梅森惠勒

尽管OP询问了目前对单个返回的解释,但这个答案是具有最历史根源的答案。除非您希望您的语言相匹配VB(不是.NET)的语言,否则在那里使用单个返回没有点数,除非您希望您的语言相匹配。请记住使用非短路布尔逻辑。 - acelent.

单个条目,单个出口(SESE)的这种概念来自具有显式资源管理的语言,如C和Commerting。在C中,像这样的代码会泄漏资源:

void f(){resource res = acquire_resource(); //认为malloc()如果(f1(res))返回; //泄漏res f2(res); Refoy_Resource(RES); //思考免费()}

使用goto跳转到清理代码。这要求清理代码成为函数中的最后一件事。 (这就是为什么有些人认为它的位置。它确实 - 在C.)

引入局部变量并通过该局部操纵控制流程。缺点是通过语法操纵的控制流程(思考中断,返回,如果,如果返回)比通过变量状态操作的控制流程更容易遵循(因为这些变量在看算法时没有状态)。

在装配它'甚至奇怪的情况下,因为当您调用该函数时,您可以在函数中跳转到任何地址,从而有效意味着您对任何功能都有几乎无限数量的入口点。 (有时这有用。这种Thunk是编译器实现在C ++中的多继承方案中调用虚拟函数所需的本指针调整所需的本指针调整的常用技术。)

当您手动管理资源时,利用任何地方进入或退出函数的选项导致更复杂的代码,从而达到错误。因此,一所思想学校出现了传播的SESE,为了获得更清洁的代码和更少的错误。

但是,当语言特征异常时,(几乎)任何功能都可能过早退出(几乎)任何点,因此无论如何您需要进行早产的规定。 (我认为最终主要用于Java,并在C#中使用(在实现IDisposable时,最终)在C#中使用时使用。)在完成RAII。)一旦完成此操作,由于早期退货声明,您就无法在自己之后清理。 ,那么最有利于SESE的最强大的论点都消失了。

留下可读性。当然,一个带有六个返回陈词滥调的200个LOC功能随机洒在其上,这不是良好的编程风格,并且不适用于可读代码。但这种功能不会很容易理解,没有那些早产的情况。

在任何语言中,资源不是或不应该手动管理,很少或根本没有遵守旧的SESE惯例。 OTOH,正如我上面的争论,SESE经常会使代码更加复杂。这是一种恐龙(除了c)不适合今天大部分'语言。它而不是帮助代码的可理解感,而是阻碍它。

为什么Java程序员坚持下去?我不知道,但是从我的(外面)POV,Java从C(他们有意义的地方)采取了大量的约定,并将它们应用于其OO世界(他们是无用或彻底的坏的),现在在哪里无论成本如何,都要贴在他们身上。 (就像“公约”在范围开始时定义所有变量。)

编程人员坚持出于非理性原因的各种奇怪的符号。 (深刻嵌套的结构陈述 - " arrowheads" - 曾经是帕斯卡的语言,曾经被视为美丽的代码。)将纯粹的逻辑推理应用于这似乎未能说服其中大多数人偏离他们既定的方式。改变这种习惯的最佳方法可能会早点教他们做什么' s best,而不是常规的'你是一个编程老师,把它放在手里。 :)

28右。在Java中,清理代码属于最终条款,无论早期返回还是例外,它都会被执行。 - Dan04.

@ Dan04在Java 7中,您甚至需要最终的时间。 - R. Martinho Fernandes

@steven:当然你可以证明!实际上,您可以使用任何功能显示复杂的和复杂的代码,也可以显示任何功能,以便制作代码更简单,更易于理解。一切都可以滥用。这一点是编写代码,使其更容易理解,并且涉及将SESE抛出窗外,所以就是它,并该死的旧习惯应用于不同语言。但是,如果i&#39认为它使得代码更容易阅读,我会毫不犹豫地控制变量的执行。它只是我不记得在近二十年中看到这样的代码。 - SBI.

@karl:实际上,它是java等GC语言的严重缺点,它们可以缓解您必须清理一个资源,但与所有其他人失败。 (C ++解决了使用RAII的所有资源的解决问题。)但我甚至只讨论了内存(我只将Malloc()和免费()作为一个例子中的评论),我正谈论资源一般。我也没有暗示GC会解决这些问题。 (我确实提到了C ++,它不会在盒子中发出GC。)从我理解的那样,在Java中最终用于解决这个问题。 - SBI.

@SBI:对于函数(程序,方法等)来说比不超过一个页面的函数更重要的是具有明确定义的合同的功能;如果它'没有做点什么,因为它被切碎,以满足任意长度的约束,即' s坏。编程是关于脱离不同的,有时相互矛盾的力量。 - 捐赠者

一方面,单个返回语句使日志记录更容易,以及依赖日志记录的调试形式。我记得很多时候我必须将功能缩小为单个返回只是为了在单点打印出返回值。

int函数(){if(bidi){print("返回1");返回1; for(int i = 0; i< n; i ++){if(vidi){print("返回2");返回2;}}打印("返回3");返回3; }

另一方面,您可以将其重构为调用_function()的函数()并记录结果。

13我还会添加它使得调试更容易,因为您只需要设置一个断点来捕获函数的所有退出*。我相信一些ides让你在函数的近括号上放置一个突击点来做同样的事情。 (*除非您拨打退出) - 滑冰

出于类似的原因,它还使得更容易扩展(添加到)函数,因为您的新功能并在每个返回之前都必须插入'例如,您需要更新具有函数调用结果的日志。 - 杰夫萨霍尔

诚实地,如果我在维持该代码中,我' d宁愿具有明确定义的_function(),在适当的位置返回返回,以及一个名为function()的包装器,它与单个函数()处理外来的日志记录。扭曲的逻辑使所有返回都适合单个出口点,所以我可以在该点之前插入附加语句。 - Ruakh.

"单个条目,单个出口"起源于20世纪70年代初期的结构化编程革命,由Edsger W. dijkstra' S信中被送到了编辑,Goto声明被认为有害。结构化编程背后的概念在Ole Johan-Dahl,Edsger W. dijkstra和Charles Anthony Richard Haare的Charles Anthony Rhare的经典书籍结构编程中被定制。

"转到陈述被认为有害"即使在今天,是必需的阅读。 "结构化编程"被约会,但仍然非常,非常有益,并且应该是任何开发人员的顶部和#39; s"必须阅读"列表,远远超过一节。 Steve McConnell。 (DAHL' S部分为SIMULA 67列出了类别的类别,这是C ++中类的技术基础以及所有面向对象的编程。)

13在CORO'批次使用时,在C之前的日子里写了这篇文章。他们ann' t敌人,但这个答案肯定是正确的。返回声明,而不是函数末尾的实际上是GOTO。 - User606723.

这篇文章也在转到的日子里写的是,就像在另一个函数中的某个随机点一样,绕过任何程序,函数,呼叫堆栈等的概念。没有明智的语言允许这些天用直转到。 C' s setjmp / longjmp是I' m意识到的唯一半特殊情况,甚至需要两端的合作。 (半讽刺,我使用这个词"卓越的"尽管如此,考虑到这个例外情况几乎相同的事情......)基本上,这篇文章劝阻'长期死亡的练习。 - 概念

从最后一段的" Goto陈述被认为有害&#34 ;:"在[2] jacopini似乎证明了转到陈述的(逻辑)多余。然而,锻炼将任意流程图或多或少地将任意流程图转换为跳跃的流程图,因此不得建议。然后,不能预期所得到的流程图比原始的流程图更透明。" - 青少年

这与问题有什么关系?是的,Dijkstra'最终导致SESE语言,所以是什么? Babbegbe&#39的工作也是如此。也许如果您认为它在函数中具有多个退出点的任何内容,您应该重新阅读本文。因为它没有。 - JALF.

@John,你似乎在没有实际回答的情况下回答这个问题。它'是一个很好的阅读清单,但你'既不引用也没有引用任何东西,以证明你的声明和书籍有任何关于问候人员的疑虑和顾虑。实际上,在评论之外你'除了这个问题没有什么大量的。考虑扩大此答案。 - Shog9.

Double GetPayamount(){双重结果;如果(_isdead)结果= deavamount();否则{if(_issepparated)结果= secondedamount();否则{if(_isretired)结果= RetiveMount(); else结果= normalpayamount(); }; }返回结果;};

double getPayamount(){if(_isdead)返回deadamount(); if(_issepparated)返回secondatedamount(); if(_isretired)返回RetiveMount(); return quandpayamount();};

9你的例子是不公平的,怎么样:双重getpayamount(){double ret = normalpayamount(); if(_isdead)ret = deavamount(); if(_issepparated)ret = secondedamount(); if(_isretired)Ret = RetiveMount();返回Ret; }; - Charbel.

@charbel那个' s不是同一件事。如果_issepparated和_Isretired都可以是真的(以及为什么可能是可能的?)你返回错误的金额。 - HVD.

@konchog"嵌套条件将提供比护卫条款的更好的执行时间和#34;这主要需要引文。我有疑虑它'最可靠的真实。例如,在这种情况下,在生成的代码方面,如何从逻辑短路与逻辑短路的任何不同?即使它很重要,我也可以想象一个差异的差异超过无穷小条子。所以你'通过使代码更少可读来申请早熟优化,只是为了满足一些关于您认为稍微更快的代码略较较快的未经证实的理论点。我们在这里做到这一点 - UndersCore_D.

@underscore_d,你是对的。它取决于编译器上的很多,但它可以采取更多空间..看看两个伪组件和它'很容易看看为什么防护条款来自高级语言。 " a"测试(1); Branch_Fail End;测试(2); Branch_Fail End;测试(3); Branch_Fail End; {code}结束:返回; " B"测试(1); Branch_Good Next1;返回; NEXT1:测试(2); Branch_Good Next2;返回; Next2:测试(3); Branch_Good Next3;返回; Next3:{代码}返回; - User269891.

底线是,这条规则来自Don' T具有垃圾收集或异常处理的语言时代。没有正式的研究表明,这条规则导致现代语言中的更好代码。每当这将导致更短或更可读的代码时,随意忽略它。 java guys坚持认为这是盲目和毫无疑问的毫无意义的规则。

7嘿,我可以再达到那个链接了。你碰巧有一个版本,托管在某个地方仍然可以访问吗? - 基金Monica'诉讼

嗨,Qpt,好的地方。我已经将博客发布返回并更新了上面的URL。它应该立即链接! - 安东尼

它比'比这更重要。它更容易使用SESE管理精确执行时间。无论如何,嵌套条件通常可以用交换机重构。它不仅仅是是否存在返回值。 - User269891.

Mehrdad,如果有正式的研究支持它,展示了它。所有的。坚持反对的证据正在改变证据的负担。 - 安东尼

脱掉它,质疑声明"没有正式的学习,显示这个"是询问&#34的教科书案;证明了负面"声明"没有x"是教科书"负面索赔"哪个"断言某事物的不存在或排除;所以你'这是错误的。也是,毫无意义的规则在造成额外的工作并导致尴尬的代码时是糟糕的。如果有一个好理由,很好。但没有,除非你知道我们不知道的东西,否则我们是什么? - 安东尼

一次回报使重构更容易。尝试执行"提取方法"到一个用于循环的内部主体,包含返回,断裂或继续。这将失败,因为您损坏了控制流程。

重点是:我猜没有人假装写完完美的代码。所以代码在重构下是"改进的"并扩展。因此,我的目标是将我的代码保留为尽可能重构友好。

通常,如果我包含控制流断路器,并且如果我想添加很少的功能,我经常面临完全重新重整函数的问题。当您更改整个控制流时,这非常出错,而不是将新路径引入孤立的嵌套。如果您最后只有一个返回,或者使用警卫退出循环,您当然可以拥有更多嵌套和更多代码。但是,您获得编译器和IDE支持的重构功能。

2相同适用于变量。 这是使用早期回报等控制流动构造的替代方案。 - Defuplicator. 变量主要不会阻碍您将代码打破成果,以便保留现有的控制流程。 尝试"提取方法" 该IDE只能能够执行控制流预制学重构,因为它们无法从您所写的内容派生语义。 - ooopexpert. 考虑到多个返回陈述等同于转到单个返回声明。 这与中断陈述相同。 因此,有些,像我一样,考虑他们的所有意图和目的。 但是,我 ......