在面向对象的编程中,方法的最大长度当然没有确切的规则,但是我仍然发现这两个引号有些矛盾,所以我想听听您的想法。 >
罗伯特·马丁(Robert Martin)在“干净代码:敏捷软件技巧手册”中说:


函数的第一个规则是它们应该很小。函数的第二个规则是,该函数应小于该函数。函数
的长度不得超过100行。函数几乎不应该长20行。


他给出了一个从肯特·贝克(Kent Beck)看到的Java代码示例:


他程序中的每个函数只有两行,三行或四行
。每个明显透明。每个人都讲一个故事。每个人都以令人信服的顺序带领您进入下一个。这就是您的函数应该有多短!


这听起来不错,但另一方面,在代码完成中,史蒂夫·麦康奈尔说了很不一样的东西:


应该允许例程有机增长到100-200行,
数十年的证据表明,这样长度的例程不会出现错误,而比较短的例程容易发生。 >

他提到了一项研究,该研究说65行或更长的例程开发起来比较便宜。

因此,尽管对此事有不同的意见,但您有实用的最佳实践吗?

评论

功能应该易于理解。长度应根据情况而定。

我认为真正的限制是53行。平均行大小为32.4个字符。认真地说,没有确切的答案。 100行方法可能非常清晰且易于维护,而4行方法可能是一场噩梦。通常,长方法往往职责过多,比小方法更难理解和维护。我会考虑责任,并尝试对每种方法承担单一责任。

程序设计中有一个术语称为“功能一致性”。如果函数的实现仍构成应用程序中逻辑的单个连贯单元,则应允许函数的长度变化。任意拆分函数以使其更小可能更容易膨胀代码并损害可维护性。

而且,如果您想限制功能的复杂性,则应该测量其圈复杂性,而不是长度。具有100个条件条件的switch语句比嵌套在一起的10个if语句级别更易于维护。

鲍勃·马丁(Bob Martin)的方法来自2008年,史蒂夫·麦康纳尔(Steve Mc Connell)的方法来自1993年。他们对什么是“好的代码”有不同的看法,恕我直言,鲍勃·马丁(Bob Martin)试图获得更高水平的代码质量。

#1 楼

函数通常应该简短,用Java或C#编码时,我个人的“经验法则”在5至15行之间。这是一个不错的尺寸,原因如下:


它很容易在屏幕上显示而无需滚动
这是您可以握在头上的概念尺寸
足够有意义,以至于自己需要一个函数(作为独立的有意义的逻辑块)
小于5行的函数表明您可能将代码分解得太多了(这使得阅读起来更加困难) /了解是否需要在功能之间进行导航)。要么就是您忘记了特殊情况/错误处理!

但是我认为设置绝对规则并没有帮助,因为始终会有有效的例外/原因与规则:在某些情况下,执行类型转换的单行访问器函数在某些情况下显然是可以接受的。
有一些非常简短但有用的函数(例如,如前所述)用户不明),显然需要少于5行。没什么大不了的,只有几个3行函数不会对您的代码库造成任何损害。
如果非常清楚正在执行的操作,那么一个100行函数就是一个大的switch语句,那么可以接受。即使需要很多行来描述不同的情况,该代码在概念上也可能非常简单。有时建议将其重构为单独的类,并使用继承/多态性来实现,但是恕我直言,这使OOP太过分了-我宁愿只有一个大的40向switch语句,而不是要处理的40个新类。到40向switch语句来创建它们。
复杂的函数可能具有许多状态变量,如果在不同的函数之间作为参数传递它们,则会变得非常混乱。在这种情况下,如果将所有内容都保留在一个大型函数中,则可以合理地认为该代码更简单,更易于遵循(尽管正如Mark正确指出的那样,这也有可能成为将类封装为逻辑和状态)。有时候,较小或较大的功能都具有性能优势(也许是因为Frank提到的内联或JIT原因)。这高度依赖于实现,但是可以有所作为-请确保您进行基准测试!

因此,基本上,使用常识,在大多数情况下都坚持使用较小的函数,但是如果这样,请不要教条您确实有充分的理由要执行异常大的功能。

评论


也有技术/性能原因要求采用简短的方法:JIT缓存命中。以前更可能调用了许多较小的可重用方法。哦,还有一个额外的好处,StackTraces更专注于流行的逻辑。

–卢克·普普利特(Luke Puplett)
13年8月21日在10:32



“使用常识”是最重要的建议

–西蒙
14年6月18日在10:10

@FrankHileman编写代码时,我每天都将馄饨和意大利面一起吃

–user22815
2014年8月19日下午5:05

@Snowman:这些选择不是互相排斥的……没有意大利面条的短堆垛深度是理想的选择。堆栈深度较深,侧面有意大利面?

–弗兰克·希勒曼
14年8月19日在16:36

与前两个参数的争论(滚动并保持在脑海中):滚动很小的内容比在调用方法和被调用方法之间来回跳转要容易得多,这可能相距很远,尤其是当有很多所谓的数学方法

– g
2016年9月8日在21:59

#2 楼

当我同意其他人的意见时,他们说对正确的LOC号没有硬性规定,但我敢打赌,如果我们回顾过去看过的项目并确定上面的每个功能,比如说150行代码,我我猜我们会达成共识,即其中10个功能中有9个会破坏SRP(很可能还有OCP),局部变量过多,控制流过多并且通常很难读取和维护。

因此,虽然LOC可能不是错误代码的直接指示,但肯定可以说,某些功能可以更好地编写。

在我的团队中,我陷入了一个错误的位置。领导,无论出于何种原因,人们似乎在听我说话。我通常希望告诉团队的是,尽管没有绝对限制,但是任何功能多于50行代码的代码都应至少在代码审阅期间发出危险信号,以便我们重新审视并重新评估它对于复杂性和违反SRP / OCP的问题。经过第二次看,我们可能会不理会它,也可能会对其进行更改,但至少它使人们思考这些事情。

评论


这似乎很明智-LOC在复杂性或代码质量方面没有任何意义,但是它可以很好地标记应重构的内容。

–科里
2012年5月31日19:18

“达成共识,即其中10个功能中有9个会破坏SRP”-我不同意,我很确定这些功能中的10个中有10个会破坏SRP ;-)

–布朗博士
2014年8月14日在8:44



+1表示在代码检查过程中发出标志:换句话说,这不是一成不变的规则,但让我们以小组形式讨论此代码。

–user22815
2014年8月19日下午5:06

#3 楼

我进入了一个与编码准则无关的项目。当我查看代码时,有时会发现类包含超过6000行代码和少于10个方法。当您必须修复错误时,这是​​一个恐怖的情况。

方法的最大数量的一般规则有时不太好。我喜欢罗伯特·C·马丁(鲍勃叔叔)的规则:“方法应该小而不是小”。我一直尝试使用此规则。我试图通过阐明我的方法只做一件事而不做其他事情来保持方法的简单和小巧。

评论


我不明白为什么6000行函数比短函数难调试...除非它也有其他问题(例如重复)

–卡尔马留斯
13年3月14日在15:13

它与调试无关。它的复杂性和可维护性。您如何扩展或更改另一行完成的6000行方法?

–烟熏脚
13年3月15日在16:03

@Calmarius的区别通常是6000个行函数往往包含局部变量,这些局部变量在很远的范围内(在视觉上)被声明为变量,这使程序员难以建立对代码具有高度信心所需的思维环境。您可以确定在任何给定点如何初始化和建立变量吗?在第3879行中设置变量后,您确定不会使变量混乱吗?另一方面,使用15行方法,您可以肯定。

–丹尼尔B
2014年6月20日10:31



@ Calmarius同意,但是这两个语句都是针对6000个LOC函数的参数。

–丹尼尔B
14年6月20日在12:43

600行方法中的局部变量本质上是全局变量。

– MK01
17年11月30日在17:12

#4 楼

这与行数无关,而与SRP有关。
根据这一原理,您的方法应该只做一件事。

如果您的方法做到了AND AND AND OR =>,那么它可能在做很多事情。尝试看一下这种方法并进行分析:“在这里我得到这些数据,对其进行排序并得到我需要的元素”,“在这里我处理这些元素”和“在这里我最终将它们组合起来以获得结果”。这些“障碍”应该重构为其他方法。

如果仅遵循SRP,您的大多数方法将很小且有明确的意图。

说不正确“此方法> 20行,所以它是错误的”。这可能表明此方法可能出了点问题,仅此而已。

方法中您可能有400条线路的切换(通常在电信中发生),但这仍然是单一职责,完全可以。

评论


大型switch语句,输出格式,散列/字典的定义应该硬编码而不是在某些数据库中灵活,这种情况经常发生并且非常好。只要逻辑被划分,那么你们都很好。一种大方法可能会提示您考虑“我应该分开吗”。答案很可能是“不,这样很好”(或者是,这绝对是一团糟)

–马丁(Martijn)
2012年2月5日在15:16

“ SRP”是什么意思?

–thomthom
2013年12月10日14:41

SRP代表“单一责任原则”,并指出每个类(或方法)应仅承担一项责任。它与凝聚力和耦合有关。如果您遵循SRP,您的类(或方法)将具有良好的内聚性,但是由于最终会遇到更多的类(或方法),因此耦合可能会增加。

–克里斯蒂安·杜斯克(Kristian Duske)
14年6月18日在11:31

SRP +1。通过编写内聚函数,可以更轻松地将它们以函数样式进行组合,以实现更复杂的结果。最后,一个功能最好由粘合在一起的其他三个功能组成,而不是让一个功能执行三个离散的(即使有某种关联)事物。

– Mario T. Lanza
2014年6月20日12:05

是的,但这是一个单一的责任。它只是您脑海中创造的一个概念。如果您需要400行来完成一项单一职责,那么您的单一职责概念可能与我的有所不同

– Xitcod13
19年7月25日在20:52

#5 楼

严重地,这确实取决于该问题,因为您使用的语言很重要,该答案中提到的第5至15行可能适用于C#或Java,但在其他语言中却没有与您共事。同样,根据所使用的域,您可能会发现自己在大型数据结构中编写代码设置值。对于某些数据结构,您可能需要设置数十个元素,是否应该仅因为函数运行时间长而将它们分解为单独的函数?

正如其他人所指出的那样,最佳的经验法则是,函数应该是处理单个任务的单个逻辑实体。如果您尝试执行严格的规则,即函数不能超过n行,并且使该值太小,那么随着开发人员尝试使用奇特的技巧绕开规则,代码将变得难以阅读。同样,如果将它设置得太高,这将是一个非问题,并且可能会因懒惰而导致错误代码。最好的选择是仅进行代码审查,以确保函数正在处理单个任务,并将其留在那儿。

#6 楼

我将再加上一个引号。


必须编写程序供人们阅读,而只能偶然地使
机器要执行

-Harold Abelson


增长到100-200的函数遵循此规则是非常不可能的

评论


除非它们包含开关。

–卡尔马留斯
2014年6月20日,12:41

或根据数据库查询的结果构造一个对象,该查询在结果集中每行返回几十个字段...

– jwenting
14年8月19日在13:54

数据库结果当然是可以接受的例外-另外,它们通常是填充类(或其他任何东西)实例的“愚蠢”语句,而不是不需要遵循的逻辑。

–金属迈克斯特
2014年8月19日在17:30

#7 楼

我认为这里的一个问题是函数的长度不能说明其复杂性。 LOC(代码行)是衡量任何事物的不良工具。

方法不应过于复杂,但是在某些情况下,很容易维护长方法。请注意,以下示例并未说明不能将其拆分为方法,只是方法不会改变可维护性。

例如输入数据的处理程序可以具有较大的switch语句,然后变得简单每个案例的代码。我有这样的代码-管理来自提要的传入数据。 70(!)个数字编码的处理程序。现在,人们会说“使用常量”-是的,除了API不提供它们之外,我想在这里与“源”保持紧密联系。方法?当然-遗憾的是,它们全部都处理来自相同2个巨大结构的数据。将它们拆分没有任何好处,除非可能有更多方法(可读性)。该代码本质上并不复杂-一个开关,取决于字段。然后,每个案例都有一个块来解析x个数据元素并将其发布。没有维护噩梦。有一个重复的“ if条件,它确定一个字段是否具有数据(pField = pFields [x],如果pField-> IsSet(){blabla}))-每个字段都差不多...

用一个较小的例程(包含嵌套循环和许多实际的切换语句)替换一个较小的例程,比一个较小的例程更易于维护。

所以,抱歉,LOC不好首先进行测量。如果有的话,则应使用复杂性/决策点。

评论


LOC是用于提供相关度量的一个领域的优秀工具-非常大的项目,可用于帮助估计相似项目可能需要多长时间。除此之外,人们倾向于过多地担心他们。

– rjzii
2012年2月6日在4:03

对。在我如何编写代码,在hformatting需求等方面,LOC并没有像过去那样严重。LOC完全不合适,而且MBA经验丰富。只要。您可以自由地将自己放入不了解LOC为什么不好的度量标准的人员列表中,但是显然,这不会使您看起来像是要听的人。

– TomTom
2012年2月6日下午5:05

请再次回顾我所说的内容,我注意到LOC仅在一个度量和使用领域(即可以用于估算的超大型项目)中是一种很好的工具。任何比大规模规模小的事物,只要能满足人们的快乐,它们就会利用最大的价值(如果不是全部的话),而不仅仅是快速的叮咬声。他们有点像试图用光年来衡量咖啡馆在办公室的公平程度,确定您可以做到,但是这种测量是没有用的。但是,当您需要讨论恒星之间的距离时,它们的效果很好。

– rjzii
2012年2月6日在12:29



+1函数应具有执行该函数所需的所有代码。它应该只做一件事,而是做一件事-但是,如果这需要一千行代码,那就去做。

–詹姆斯·安德森(James Anderson)
13年8月21日在9:38

我已经为传入的套接字数据编写了处理程序,是的,它们可能需要一千个或更多的LOC。但是,我一方面可以指望执行此操作所需的次数,而不能计算这不是适当的编码方式的次数。

–user22815
2014年8月19日下午5:11

#8 楼

自1970年以来,我就一直处于这种疯狂的球拍状态。

在那段时间里,除了我即将谈到的两个例外,我从来没有见过一个好人,设计的“例程”(方法,过程,函数,子例程等),需要多于一个打印页面(约60行)。它们中的绝大多数都很短,大约10到20行。

但是,我看到了很多“意识流”代码,这些代码是由显然从未听说过模块化。

这两个例外是非常特殊的情况。我实际上将一类异常情况汇总在一起:大型有限状态自动机,实现为大型丑陋的switch语句,通常是因为没有更干净的方式来实现它们。这些东西通常会显示在自动测试设备中,从被测设备中解析数据日志。

另一种是Matuszek-Reynolds-McGehearty-Cohen STARTRK游戏中的光子鱼雷子程序,写在CDC 6600 FORTRAN IV。它必须解析命令行,然后模拟每个鱼雷​​的飞行,并进行摄动,检查鱼雷与它可能击中的每种物体之间的相互作用,然后通过模拟递归在链上进行8向连通性用鱼雷切割一颗与其他恒星相邻的恒星。

评论


我从此答案中获得了“下车草坪”氛围的+1。同样,从OOP语言广泛使用之前的个人经验中可以得出。

–user22815
14年8月19日在5:13

这些年来,我观察到很多废话,这与其说是“下车了”,不如说是越来越糟。

– John R. Strohm
2014年8月19日15:41

我的老板习惯于写几百行长的方法,通常会写几层嵌套的if。他还使用局部类(.NET)将一个类“分解”为几个文件,因此他可以声称自己将它们保持简短。这些只是我要处理的两件事。这样做已经有25年了,我可以确认情况正在恶化。现在是时候让我回到混乱中了。

–金属迈克斯特
2014年8月19日在17:34

#9 楼

如果我找到了很长的方法,我敢打赌这种方法没有经过正确的单元测试,或者大多数时候根本没有进行单元测试。如果您开始执行TDD,您将永远不会建立具有25种不同职责和5个嵌套循环的100行方法。
测试迫使您不断重构混乱并编写Bob叔叔的简洁代码。

#10 楼

作者通过“功能”和“例程”指的是同一件事吗?通常,当我说“函数”时,我的意思是一个子例程/操作,该子例程/操作返回一个值,而“过程”则返回不返回的值(并且其调用成为单个语句)。这并不是整个SE在现实世界中的普遍区别,但我已经在用户文本中看到过。

,无论哪种方式,都没有正确的答案。我希望对一种语言的偏好(如果有某种偏好的话)在语言,项目和组织之间会有所不同。就像所有代码约定一样。

我要补充的一点是,整个“长操作不比短操作更容易出错”的断言并不严格。除了更多的代码等于更多的潜在错误空间这一事实之外,显而易见的是,将代码拆分为多个段将使错误更容易避免和更容易定位。否则,就没有理由将代码分解成碎片,而无需重复。但这也许是正确的,前提是上述段的文档记录得足够好,以便您可以确定操作调用的结果而无需通读或跟踪实际代码(根据规范进行按合同设计,而不是基于代码区域之间的具体依赖关系)。

另外,如果您希望较长的操作能正常工作,则可能需要采用更严格的代码约定来支持它们。在一个操作的中间抛出一个return语句对于一个简短的操作可能是好的,但是在更长的操作中,这会创建很大一部分代码,这是有条件的,但显然不是快速阅读的条件(仅举一个例子)。

因此,我认为哪种样式不太可能是一个充满臭虫的噩梦,这在很大程度上取决于其余代码所遵循的约定。 :)

#11 楼

关于方法的长度没有绝对的规则,但是以下规则很有用:函数的主要目的是查找返回值。没有其他原因存在。完成该原因后,不应再插入其他代码。这必然会使功能变小。只有在使返回值的查找变得容易的情况下,才应调用其他函数。
另一方面,接口应较小。这意味着您要么拥有大量的类,要么拥有大量的函数-一旦您开始拥有足够的代码来执行重要的工作,就会发生两者之一。大程序可以同时拥有两者。


评论


副作用-写入文件,重置状态等?

– Vorac
13年8月21日在10:46

#12 楼

恕我直言,您不必使用滚动条来阅读您的功能。一旦需要移动滚动条,就需要花费更多时间来了解该功能的工作原理。

相应地,这取决于团队通常的编程环境(屏幕分辨率,编辑器,字体大小)等)。在80年代,它是25行80列。现在,在我的编辑器上,我显示了近50行。由于我将屏幕一分为二以同时显示两个文件,因此显示的列数没有变化。

总之,这取决于您的同事的设置。

评论


那不是24行吗?我正在考虑3270或9750终端,其中第25个是状态行。终端仿真随之而来。

– ott--
13年5月5日在9:26

一些系统/编辑器从一开始就有40或50行。这些天来150条线并不罕见,而200条以上是可行的,因此这并不是一个很好的指标。

–Móż
2014年6月5日下午5:46

我以纵向方式使用屏幕,一次可以看到200行代码。

–卡尔马留斯
14年6月20日在12:48

如果我不使用任何换行符来拆分行,则可以在一行中编写5000行方法...

– jwenting
2014年8月19日在13:57

#13 楼

我认为TomTom的答案与我的感觉很接近。

我越来越多地发现自己从事的是圈复杂性而不是线条。

我通常只追求每个方法有一个控制结构,除了处理一个多维数组需要进行许多循环外。

我有时会发现自己在切换情况下使用单行ifs,因为出于某种原因,这些趋向于在某些情况下,将其拆分会阻碍而不是帮助。

请注意,我并未将保护逻辑计入此限制。

评论


在大量实际生产代码中,圈复杂度已显示与原始SLOC密切相关,因此圈复杂度的计算会浪费时间,精力和时钟周期。

– John R. Strohm
2014年8月19日15:43

@ JohnR.Strohm我在说每种方法,而不是整体。当然,从总体上看,它是高度相关的-问题是如何将代码拆分为方法。 100条线的10种方法或10条线的100种方法仍将具有相同的总体SLOC和复杂性,但前者将变得很难使用。

–Loren Pechtel
14年8月19日在18:26

我也是。相关研究着眼于代码的批次和例程的批次。 (这是大型的公共存储库之一。)

– John R. Strohm
14年8月19日在19:15

#14 楼

在OOP中,所有事物都具有对象并具有以下功能:


多态性
抽象
继承

当您遵守这些规则时,您的方法将生效通常很小,但是对于很小或很小的规则(例如2-3行)不存在任何规则。
较小方法(较小的单元,例如方法或函数)的优点是:


更好的可读性
保持更好的
修复错误的更好
更改更好的