函數長度
2016 年 11 月 30 日
在我的職涯中,我聽過許多關於函數長度應該多長的論點。這是更重要問題的代理人 - 我們什麼時候應該將程式碼封裝在自己的函數中?其中一些準則基於長度,例如函數不應大於能顯示在螢幕上 [1]。有些則基於重複使用 - 任何使用超過一次的程式碼都應該放入自己的函數中,但只使用一次的程式碼應該保留在內聯程式中。然而,對我來說最有道理的論點是意圖和實作之間的分離。如果您必須花費力氣檢視程式碼片段才能找出它在做什麼,那麼您應該將它萃取到一個函數中,並將函數命名為那個「什麼」。這樣一來,當您再次閱讀它時,函數的目的就會立刻跳出來,而且大部分時間您不需要關心函數如何實現其目的 - 也就是函數的主體。
一旦我接受了這個原則,我就養成了撰寫非常小的函數的習慣 - 通常只有幾行長 [2]。任何超過半打的程式碼行的函數對我來說都會開始有異味,而且我經常有單行程式碼的函數 [3]。Kent Beck 從原始 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。以下是方法主體長度的累積頻率圖

正如你所見,那裡有很多小方法 - 我的程式碼庫中有一半的方法是兩行或更少。(這裡的行是非註解、非空白,且不包括 def
和 end
行。)
以下是粗略表格形式的資料(我懶得將其轉換成適當的 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: 範例在 Kent 出色的 Smalltalk 最佳實務模式 中的揭示意圖訊息