函数长度

2016年11月30日

在我的职业生涯中,我听到过很多关于函数应该有多长的争论。这是一个更重要问题的替代方案——我们什么时候应该将代码封装到自己的函数中?其中一些指南是基于长度的,例如函数不应超过屏幕大小[1]。有些是基于重用的——任何使用超过一次的代码都应该放在自己的函数中,但只使用一次的代码应该保留在内联。然而,对我来说最有意义的论点是意图和实现之间的分离。如果你必须花费精力查看一段代码来弄清楚它在做什么,那么你应该把它提取到一个函数中,并用这个“做什么”来命名函数。这样,当你再次阅读它时,函数的目的就会立即显现出来,而且大多数时候你不需要关心函数如何实现其目的——也就是函数体。

一旦我接受了这个原则,我就养成了编写非常小函数的习惯——通常只有几行代码[2]。任何超过六行代码的函数对我来说都开始变得难闻,而且我经常会写只有一行代码的函数[3]。肯特·贝克向我展示了最初 Smalltalk 系统中的一个例子,这让我意识到大小并不重要。当时的 Smalltalk 运行在黑白系统上。如果你想突出显示一些文本或图形,你会反转视频。Smalltalk 的图形类有一个名为“highlight”的方法,它的实现只是对“reverse”方法的调用[4]。方法的名称比它的实现更长——但这并不重要,因为代码的意图和它的实现之间存在很大差距。

有些人担心短函数,因为他们担心函数调用的性能成本。在我年轻的时候,这偶尔是一个因素,但现在这种情况非常罕见。优化编译器通常在较短的函数上运行得更好,这些函数可以更容易地缓存。一如既往,关于性能优化的通用指南才是最重要的。有时,你可能需要在以后内联函数,但通常较小的函数会建议其他提高速度的方法。我记得人们反对为列表使用isEmpty方法,而常见的习惯用法是使用aList.length == 0。但是,在这里,在函数上使用揭示意图的名称也可能支持更好的性能,如果判断集合是否为空比确定其长度更快。

只有当名称很好时,这样的短函数才有效,因此你需要认真注意命名。这需要练习,但一旦你精通了它,这种方法可以使代码变得非常自文档化。更大规模的函数可以像故事一样阅读,读者可以根据需要选择哪些函数深入了解更多细节。

致谢

Brandon Byars、Karthik Krishnan、Kevin Yeung、Luciano Ramalho、Pat Kua、Rebecca Parsons、Serge Gebhardt、Srikanth Venugopalan 和 Steven Lowe 在我们内部邮件列表中讨论了这篇文章的草稿。

Christian Pekeler 提醒我,嵌套函数不符合我的大小观察结果。

注释

1: 或者在我的第一份编程工作中:两页行式打印机纸——大约 130 行 Fortran IV 代码

2: 许多语言允许你使用函数来包含其他函数。这通常用作范围缩减机制,例如使用函数作为对象模式来实现类。这样的函数自然要大得多。

3: 我的函数的长度

最近,我对构建这个网站的工具链中的函数长度感到好奇。它主要是 Ruby,大约有 15 KLOC。以下是方法体长度的累积频率图

如你所见,这里有很多小方法——我代码库中的一半方法只有两行或更少。(这里的行是指非注释、非空白行,不包括defend行。)

以下是数据的粗略表格形式(我感觉太懒了,不想把它变成合适的 HTML 表格)。

              lines.freq lines.cumfreq lines.cumrelfreq
[1,2)          875           875        0.4498715
[2,3)          264          1139        0.5856041
[3,4)          195          1334        0.6858612
[4,5)          120          1454        0.7475578
[5,6)          116          1570        0.8071979
[6,7)           69          1639        0.8426735
[7,8)           75          1714        0.8812339
[8,9)           46          1760        0.9048843
[9,10)          50          1810        0.9305913
[10,15)         98          1908        0.9809769
[15,20)         24          1932        0.9933162
[20,50)         12          1944        0.9994859
      

4: 这个例子来自肯特出色的Smalltalk 最佳实践模式中的揭示意图的消息