被证明是好的函数最大长度是多少?[关闭]


43

函数长度会影响程序员的生产率吗?如果是这样,为避免生产率损失,最大的最大行数是多少?

由于这是一个高度自以为是的主题,请备份索赔并提供一些数据。


6
长度不应以LOC来度量,而应以准确理解其作用所需的时间来度量。并且该长度不应超过一分钟。如果我无法在几秒钟内弄清楚,它可能做得太多...一分钟后,肯定是。
CaffGeek 2010年


13
最大长度应当是17
ThomasX

1
在SOLID中思考S。
克里斯·克劳斯

1
@CaffGeek也许函数只是在做一些琐碎的事情。我已经看过一些函数,这些函数可能需要几天的时间才能完全理解。即使是我理解所有涉及概念的功能,也很容易花费半个小时来完成所有细节。拥有微不足道的功能固然很好,但是许多问题本质上是固有的困难。
CodesInChaos

Answers:


46

自从我1970年开始使用这种疯狂的球拍以来,我已经确切地看到一个模块确实需要超过一个打印页面(约60行)。我见过很多更长的模块。

为此,我编写了更长的模块,但是它们通常是写为大型开关语句的大型有限状态机。

问题的一部分似乎是这些天没有教程序员如何对事物进行模块化。

最大化垂直空间浪费的编码标准也似乎是问题的一部分。(我尚未见过一位读过Gerald Weinberg的“ 计算机编程心理学 ”的软件经理。Weinberg指出,多项研究表明,程序员的理解力基本上仅限于程序员在任何给定的瞬间所看到的内容。程序员必须滚动或翻页,他们的理解力才会大大下降:他们必须记住并抽象化。)

我仍然坚信,FORTH在许多有据可查的程序员工作效率上的提高是由于源代码的FORTH“块”系统所致:模块硬限制为绝对最多16行64个字符。您可以无限分解,但是在任何情况下都不能编写17行例程。


3
FORTH的整个理念都是为了鼓励这种情况。...您打算设计自己的词汇表,不懈地将您的程序分解成越来越小的片段,最终减少了脚本的使用,而增加了词典的使用。长度限制本身并不能做到这一点-您将看到一个例程被分成任意部分,只是为了满足某种编码标准。我认为您完全正确地怀疑程序员只是“没有被教过将事情模块化”。这本来应该是OOP的一大胜利,但是由于种种原因,它常常被低估为目标。
Shog9

1
@先生。CRT:面向块的FORTH实现FORCED模块化中的长度限制。大多数FORTH的交互性质通过鼓励使用小型模块并快速测试这些模块而有所帮助。D85是基于文件的FORTH,并没有强制模块化,而且我看到使用D85的人用它永远编写了许多程序设计师意识的模块。因此,我坚信。(就其价值而言,莉兹·拉瑟(Liz Rather)与我不同意。她认为,交互作用才使FORTH的生产力得到提高。)
约翰·R·斯特罗姆

2
+1向我介绍了很棒的书“计算机编程心理学” :)
塞缪尔

30

正确的尺寸是多少?

取决于您使用的语言,但总的来说(根据我的个人喜好):

  • 理想情况下,少于25行。
  • 可接受,少于35行。

如果更多,那么我需要稍后再进行修改。

实际上,当您需要交付某些东西时,它的大小可以是任意大小,并且在将它们吐出来的那一刻变得更加有意义,这有时使某些人在装运之前进行审核变得更加容易。(但稍后仍会重新讨论)。

(最近,我的团队在我们的代码库上运行了一个程序:我们发现类中有197个方法,另一个类中只有3个方法,但其中一个是600行。可爱的游戏:这2种弊端有何危害?)


现在,要获得一个更禅宗的答案...通常,引用一两个伟人被认为是一种好习惯(TM),因此,这里是:

一切都应该尽可能简单,但不要简单。-爱因斯坦

最终,不是不再需要添加任何东西,而是不再需要获取任何东西时,才可以达到完美。-圣艾修伯里


评论样式附录

作为对此的补充,您的函数应具有清晰的名称,以说明其意图。关于注释,我通常不在函数内部进行注释:

  • 评论“为什么?”
  • 代码“如何?”

每个功能顶部的注释块(需要说明)就足够了。如果您的函数很小,并且函数名称足够明确,那么您只需要说出要实现的目标以及原因。我仅对某些语言的字段使用内联注释,或者对于意图不明确的违反25-35行规则的函数,在块开头使用内联注释。当发生特殊情况时,我在代码内使用块注释(例如,在您不需要或不想做任何事情的catch块中,应添加注释以说明原因)。

有关更多信息,请阅读样式的评论和注释代码的建议


@haylem我想这是Freddy vs. Jason的程序员版本:-)
Gaurav 2010年

我同意,但是我要添加的大小应为一个屏幕页面的大小。如果在一个屏幕页面中有72行,则该功能不应超过72行。
显示名称2010年

@有一个问题要确保单个功能也可以保存在屏幕页面中,以免滚动,但这通常不是我的主要考虑。我担心的是读取函数时处理信息所花费的时间,如果它清楚地以25行写成,那几乎是瞬间的。这只是遵循函数调用的问题。72对我来说太大了(加上,如果您
有分

1
当然,有时候您可以使用一些功能将70个不同的字段复制到70个不同的位置。我主要使用tt这些函数来生成这些函数,但有时您会遇到一个长屁股函数(或一个长屁股函数),而该函数实际上并没有做任何有意义的事情,因此这并不是真正的问题。
configurator 2010年

1
例如Map(x => x.Property1); Map(x => x.Property2); Map(x => x.Property3);,当函数很明显时,它们都是完全相同的。(请注意,这只是一个例子;这种功能会不时弹出)
configurator

12

我认为,每个功能都应尽可能小。每个功能只能做一件事,并且做得很好。这并不能真正回答最大长度问题,但是更多的是我对函数长度的感觉。

用鲍伯叔叔的话来说,“直到提取不再提取为止。提取直到下降为止。”


2
尽可能小是什么意思?将每个函数只包含两行会不会变得尽可能小:一个执行一个操作,而另一个调用一个函数来执行其余操作?
凯尔米克拉

鲍伯叔叔又来了。为自己考虑;为自己想。不要听叔叔的话。他们使您误入歧途。
gnasher729

10

建筑物的最大高度应该是多少?取决于构建的位置或所需的高度。
您可能从不同城市来的不同人得到不同的答案。
一些脚本功能和内核中断处理程序非常长。


我完全同意你的看法。我喜欢隐喻。:)愚蠢的建筑师可能建造了三层楼的建筑物,他们不知道在哪里放置有效的安全出口,而其他建筑物可能有十层楼,是一个完美的建筑设计。我们应该始终牢记,可读性和可维护性应该是重构减少其尺寸而不是尺寸本身的方法的主要原因。除科​​幻电影外,无法用90%的摩天大楼建造一座城市。:)
Samuel

10

一个对我有用的方法是:是否可以让较长函数的一部分给出有意义的名称。我认为方法的长度并不像好的命名那样重要。该方法应该做到的就是名称所说的内容,不要多也不少。而且您应该能够给出一个好名字。如果您不能将您的方法命名为好方法,那么将代码放在一起可能就不好了。


而且您的方法应该只做一件事就可以拥有一个好名字...不能使用'And','Or'或其他任何使方法名称长50个字符的名称。
塞缪尔

10

只要它需要做它需要做的事情,就不再需要。


正如他们在c中所说的:“在c中,没有任何问题可以通过向该指针添加另一个指针来解决。” 您总是可以在其下添加另一个功能,他的问题将是您的顿悟“它在哪里结束?”
显示名称2010年

1
我实际上会翻转它并说“只要它需要,但不要更短”,但是由于足够接近,我得到了+1 :)
Ben Hughes 2010年

6

我认为这是一个权衡。如果您有很多短方法,那么调试它们通常比长方法要难。如果您必须在编辑器中跳出20或30个不同的时间来跟踪一个方法调用,那么很难将其全都牢记在心。同时,如果有一种写得很好的清晰方法,即使它是100行,通常也更容易掌握。

真正的问题是为什么项目应该采用不同的方法,并且上面给出的答案是代码的重用。如果您不重复使用(或不知道)代码,则可以将其保留在一个易于遵循的巨型方法中,然后在需要重复使用时,将需要重新使用的部分分开即可。使用较小的方法。

实际上,好的方法设计的一部分是制作功能一致的方法(本质上说,它们只能做一件事)。方法的长度无关紧要。如果一个函数做一个定义明确的事情,并且有1,000行,那么它是一个好方法。如果一个函数执行3或4件事并且只有15行,那么这是一个不好的方法...


我喜欢简短的方法。
Marcie 2010年

我喜欢您的发言,因为我认为说一种方法最多只能包含10行。好的,每次编写方法时都要牢记一个好规则,但这不应该是1 + 1 = 2的数学规则。如果您遵守KISS,DRY,YAGNI等原则,并且您的方法是没有足够的注释来解释一些细节,因为方法太长了,方法可以包含100行代码,并且完全可以理解和维护。但是,它应该是一种例外,而不是一种习惯。我认为在工厂方法中切换大小写是一个很好的例外示例。
塞缪尔

5

如果可以一次看到整个功能,我发现跟踪自己的工作更容易。因此,这就是我更喜欢编写函数的方式:

  1. 足够短,可以用合理的字体装在我的显示器上。
  2. 如果需要长于#1,则应短到足以以合理的字体在纸上打印。
  3. 如果需要比#2长,则应足够短以在纸张上进行2张打印。

我写函数的时间很少超过该时间。其中大多数是巨大的C / C ++开关语句。


足够短以适合监视器,这很好,但是可以指定字体类型和大小。我认为纸张不应该成为规则,因为我们已经在2013年了,谁仍然在纸上打印代码,谁来打印只是看它是否适合纸张尺寸?使用像Visual Studio这样的工具,智能感知,就不再需要用纸来分析代码了。
塞缪尔

5

对我来说,函数的长度是任意的。在大多数情况下,我会重新使用代码。

基本上,我会坚持原则“高内聚,低耦合”,并且长度没有限制。


5

问题应该是一个函数应该执行多少操作。通常,很少需要100行来完成“一件”事情。再次取决于您查看代码的级别:哈希密码是一回事吗?还是散列和保存密码是一回事?

我会说,首先将密码保存为一项功能。当您觉得哈希不同时,您便重构了代码。我绝对不是专家程序员,但是恕我直言,函数的整体思想从小开始,就是您的函数越原子化,代码重用的机会就越高,而不必在一个以上的地方进行相同的更改等

我已经看到运行1000行以上的SQL 存储过程。存储过程的行数是否也少于50?我不知道,但是它使阅读代码变得很糟糕。您不仅需要不断向上和向下滚动,还需要给几行代码起一个名称,例如“ thisvalidation1”,“ this update in database”等,这是程序员应该完成的工作。


+1仅针对第一段。其他一切都与您正在处理的情况有关。
显示名称2010年

并将其分为50个功能有什么帮助?当功能必须进行通信时,您将不会完成500行,而是数千行?
gnasher729

5

圈复杂度(维基百科):

一段源代码的循环复杂性是通过源代码的线性独立路径数的计数。

  • 我建议您以一种方法将该数字保持在10以下。如果达到10,那么该重新构造了。

  • 有一些工具可以评估您的代码并为您提供圈复杂度数字。

  • 您应该努力将这些工具集成到构建管道中。

  • 不要从字面上追逐方法的大小,而是尝试着看它的复杂性和责任。如果它承担的责任不止一个,那么重构是一个好主意。如果其圈复杂性增加,那么可能是时候进行重构了。

  • 我敢肯定,还有其他工具可以为您提供类似的反馈,但是我还没有机会对此进行研究。


在来自大型公共存储库之一(而非人为的示例)的真实代码上,显示出复杂的循环复杂性与原始SLOC非常相关。这使得它基本上一文不值,因为计算回车收益要容易得多。(是的,可以玩SLOC。老实说,在这里:允许在您的雇主面前玩SLOC指标的人可以继续提取工资吗?)
John R. Strohm

4

通常,我尝试将方法/功能保持在1680x1050显示器的屏幕上。如果不合适,请使用辅助方法/函数将任务打包。

它有助于提高屏幕和纸张的可读性。


我做同样的事情,但是值得指定您使用的字体类型和大小。就我自己而言,我更喜欢Scott hanselman建议的大小为14的“ consolas”。hanselman.com/blog/…第一次使用如此大的字体很难,但最好的做法是始终记住您的方法应尽可能地小。
塞缪尔

4

我对任何事物都没有强加限制,因为某些函数实现的算法本质上是复杂的,任何使它们更短的尝试都会使新的较短函数之间的交互变得如此复杂,以至于最终结果不会降低简单性。我也不相信函数只能做“一件事情”的想法是一个很好的指导,因为在较高抽象级别上的“一件事情”在较低层次上可能是“很多事情”。

对我来说,如果函数的长度立即导致对DRY的细微违反,则该函数肯定会太长,而将部分函数提取到新函数或类中可以解决此问题。如果不是这种情况,函数可能会太长,但是可以轻松提取函数或类,这会使代码更具模块化,从而在面对可预见的变更时很有用。


函数在特定的抽象级别上执行“一件事”,而您只担心该抽象级别。这就是重点。如果您不能理解这一点,那么我认为您不会理解抽象。
Zoran Pavlovic '10

4

足够短,可以正确优化

方法应该简短到可以做一件事。原因很简单:因此可以适当地优化代码。

在像Java或C#这样的JIT语言中,简单的方法很重要,这样JIT编译器才能快速生成代码。更长,更复杂的方法自然需要更多的JIT时间。而且,JIT编译器仅提供少数优化,只有最简单的方法才能从中受益。甚至在Bill Wagner的Effective C#中也提到了这一事实。

在较低级的语言(例如C或C ++)中,拥有短方法(大约十几行)也很重要,因为这样可以最大程度地减少将局部变量存储在RAM中而不是在寄存器中的需求。(又称为“寄存器溢出”。)但是请注意,在这种不受管理的情况下,每个函数调用的相对成本可能会很高。

即使使用像Ruby或Python这样的动态语言,使用简短的方法也有助于编译器的优化。在动态语言中,功能越“动态”,优化就越困难。例如,使用X并可以返回Int,Float或String的long方法可能比三个分别返回单个类型的单独方法的执行速度慢得多。这是因为,如果编译器确切地知道函数将返回哪种类型,它也可以优化函数调用站点。(例如,不检查类型转换。)


大约99.999%的应用程序中有更多的东西会减慢程序的速度,例如数据库访问,文件访问或网络延迟。在设计方法时考虑速度可能是进行游戏,实时应用或使用大量数据进行报告的有效理由,但在其他情况下则不是。但是,这很不错,但是和我作为程序员一样小,所以我很少需要在应用程序中进行这种优化。
塞缪尔

当您测量了代码的速度并且发现它太慢时,如果知道自己在做什么(这并不像您想的那样简单),并且随后进行了测量并且速度有所提高,就可以执行这种操作。
gnasher729

3

这在很大程度上取决于代码中的内容。

我看过一千行的例程,没有问题。这是一个巨大的switch语句,没有选项超过十二行,并且任何选项中唯一的控制结构是单个循环。如今,它本来是用对象编写的,但那不是当时的选择。

我还在前面的交换机中查看120条线。案件不超过3行-一名后卫,一名任务和一名休息。它正在解析文本文件,没有对象。任何其他选择都很难阅读。


2

大多数编译器不介意函数的长度。一个功能应该是有功能的,但对人类来说既易于理解,更改又可以重用。选择最适合您的长度。


1

我的一般规则是,功能应适合屏幕。我发现只有三种情况倾向于违反此规定:

1)调度功能。在过去,这些很常见,但是如今大多数都被对象继承所取代。不过,对象仅在程序内部运行,因此,在处理从其他位置到达的数据时,您仍然会偶尔看到调度功能。

2)执行一整套步骤以实现目标的功能,而这些步骤缺少很好的细分。您最终得到的功能只是简单地按顺序调用一长串其他功能。

3)像#2一样,但各个步骤是如此之小,以至于它们只是内联而不是单独调用。


1

函数长度可能不是一个很好的指标。我们也尝试在方法上使用圈复杂度,并且未来的源代码控制检入规则之一是,类和方法上的圈复杂度必须低于X。

对于方法,X设置为30,这非常紧密。

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.