方法对您来说理想的长度是多少?[关闭]


123

在面向对象的程序设计中,方法的最大长度当然没有确切的规则,但是我仍然发现这两个引号有些矛盾,因此我想听听您的想法。

罗伯特·马丁(Robert Martin)在《干净代码:敏捷软件工艺手册》中说:

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

并列举了肯特·贝克(Kent Beck)的Java代码示例:

他程序中的每个函数只有两行,三行或四行。每个明显透明。每个人都讲一个故事。每个人都以引人注目的顺序将您引向了下一个。那就是您的功能应该有多短!

这听起来不错,但另一方面,在“代码完成”中,史蒂夫·麦康奈尔(Steve McConnell)说了很不一样的话:

例程应允许有机增长到100-200行,数十年的证据表明,这种长度的例程不会比较短的例程容易出错。

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

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


17
功能应该易于理解。长度应随情况而定,具体取决于情况。
Henk Holterman '02

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

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

9
而且,如果您想限制函数的复杂度,则应测量其圈复杂度,而不是长度。阿switch100个声明case条件比10级的更易于维护的if相互嵌套的语句。

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

Answers:


114

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

  • 无需滚动即可轻松放在屏幕上
  • 这是可以容纳在脑海中的概念尺寸
  • 它足够有意义,可以单独要求一个功能(作为独立的,有意义的逻辑块)
  • 少于5行的函数表明您可能将代码分解太多(如果您需要在函数之间导航,这会使阅读/理解变得更加困难)。要么就是您忘记了特殊情况/错误处理!

但是我认为设置绝对规则没有帮助,因为总会有有效的例外/原因与规则相背离:

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

因此,基本上,使用常识可以在大多数情况下坚持使用较小的函数,但是如果您确实有充分的理由来执行异常大的函数,则不要过于拘泥于此。


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

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

应该知道,在调试时,由于断点处堆栈的深度,这种启发式方法会导致抱怨“馄饨代码”。
Frank Hileman

5
@FrankHileman在编写代码时,我每天都将馄饨涂在意大利面条上

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

29

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

因此,尽管LOC可能不是不良代码的直接指示器,但肯定可以说,某些功能可以更好地编写,这是一个不错的间接指示器。

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


3
这似乎很明智-LOC并不意味着复杂性或代码质量,但是它可以很好地标记应重构的事物。
cori 2012年

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

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

21

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

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


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

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

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

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

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

10

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

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

如果仅遵循SRP,您的大多数方法将很小而且用途很明确。

说“此方法> 20行,所以它是错误的”是不正确的。它可以是一个标志的东西可能是错用此方法,没有更多的。

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


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

“ SRP”是什么意思?
thomthom 2013年

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

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

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

9

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

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


8

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

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

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

用一个更小的包含嵌套循环和大量实际切换语句的例程来替换该例程,并且比一个较小的例程更易于维护。

因此,很抱歉,LOC并不是一个很好的衡量标准。如果有的话,应该使用复杂性/决策点。


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

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

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

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

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

6

我再加上一个报价。

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

-哈罗德·阿伯森

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


1
除非它们包含开关。
Calmarius 2014年

1
或根据数据库查询的结果构造一个对象,该查询返回结果
集中

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

6

自1970年以来,我一直都在这种疯狂的球拍中。

在那段时间里,除了一会儿我要提到的两个例外,我从未见过一个精心设计的“例程”(方法,过程,函数,子例程等),需要超过一个打印页面(大约60行)长。它们中的绝大多数都相当短,大约10到20行。

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

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

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


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

这些年来,我观察到很多废话,这与其说是“下车了”,不如说是越来越糟。
约翰·斯特罗姆

我的老板习惯于编写几百行长的方法,并且经常使用多层嵌套的ifs。他还使用局部类(.NET)将一个类“分解”为几个文件,因此他可以声称自己将它们保持简短。这些只是我要处理的两件事。这样做已经有25年了,我可以证实情况正在恶化。现在我该回到那个烂摊子了。
MetalMikester,2014年

5

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


2

关于方法的长度没有绝对的规则,但是以下规则很有用:

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

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

2

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

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

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

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

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


1

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

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

简而言之,这取决于您的同事的设置。


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

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

我以纵向使用屏幕,一次可以看到200行代码。
Calmarius 2014年

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

1

我认为TomTom的答案接近于我的感受。

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

通常,我的目标是每个方法不超过一个控制结构,除了处理多维数组需要进行许多循环外。

我有时会发现自己在切换案例中使用单行ifs,因为出于某种原因,这些案例往往是将其分割成障碍而不是帮助的情况。

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


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

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

我也是。相关研究着眼于代码的LOTS和例程的LOTS。(这是最大的公共存储库之一。)
John R. Strohm 2014年

-3

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

  1. 多态性
  2. 抽象化
  3. 遗产

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

  1. 可读性更好
  2. 保持更好
  3. 更好地修复错误
  4. 变化更好
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.