寫出好維護的程式碼 — 寫程式時如何做好命名 (naming)?

2025年9月13日

💎 加入 E+ 成長計畫 與超過 750+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

先前史丹佛大學電腦科學系教授 John Ousterhout 在他的經典著作《A Philosophy of Software Design》一書中分享過他親身經歷過的慘痛教訓。他提到當年在開發一個分散式作業系統遇到一個離奇的 bug,在某些時候,檔案會遇到資料丟失的狀況,某一個區塊的資料,即使沒有被使用者改動,也會突然全部歸零。

這個 bug 花了 John Ousterhout 教授與他的研究生團隊,整整半年的時間,才總算找出問題所在。而這個問題所在,就是因為命名不清楚,導致變數被誤用。但也因為命名不清楚,所以被誤用後,不容易被察覺,導致要找出 bug 所在,變得特別花時間。

具體來講,當時在作業系統中的文件系統會用到 block 這個變數,但事實上這個 block 是有兩個不同的作用,一個是物理磁碟上的區塊編號,另一個是作為檔案中的邏輯區塊編號;所以當兩種混用在一起,就導致某個應該不該被碰到區塊,被覆寫歸零。

在看完上面這個問題,讀者們會覺得,假如要用比較好的命名方式,要如何重構這兩個變數名稱呢?

John Ousterhout 教授在重構時,選擇用 fileBlockdiskBlock 來重新命名。透過這個重構,這兩個變數名稱就變得非常清晰,該用來處理文件中邏輯的,就用 fileBlock;如果是處理物理磁碟的,則用 diskBlcok,兩者不會有搞混的時候。

希望透過上面的案例,有讓讀者們感受到,假如沒有做好命名,很可能會要面對這種意料之外的 debug 成本。假如想要避免的話,掌握一些命名的原則,會很有幫助。以下彙整了一些推薦讀者們在命名時,可以用上的原則。

推薦的命名原則:精確 (precise)

首先,命名最重要的原則之一就是要精確。精確的意思是不能模糊,要讓人能夠一眼看懂。

一個有效讓命名精確的方式,是問自己「如果某個人看到這個名稱,假如不看其他細節,例如不看文件、不看變數或函式內容,或者不看這個變數或函式用在哪,那個人有辦法推測出這個變數或函式在做什麼嗎?」

針對上面這個問題,如果沒有辦法,那就很可能代表函式不夠精確。

會不精確,通常是因為某個名稱試圖涵蓋太多東西,就以上面 John Ousterhout 教授分享的例子來說,block 這個變數涵蓋的面太廣,所以可能被有不同的詮釋,而當有了詮釋的空間,就可能讓這個變數被誤用。

除了上面的 block 案例,讓我們多看幾個例子,來具體瞭解如何把不精確的命名,調整成足夠精確的。

首先,很多人可能會覺得用 result 來表達回傳的結果,或者用 count 來表達某個正在被計算的數,這樣的命名就足夠精確。但是通常這樣的命名,仍是不夠精確地,因此可能導致像上面提到的問題。要調整的話,可以問「result 是什麼的結果? count 是在算什麼的?」,然後把這些資訊融入到命名中,就會更精確。

除此之外,不同性質的變數名,也要盡可能做到讓人一眼能看出其性質。舉例來說,const error = true; 或者 const status = false 都是不理想的布林值命名,因為這很難讓人第一眼就看出這兩個變數是布林值。

假如用 error 很可能讓人詮釋成不同的錯誤類別,而假如是 status 則可能會讓人詮釋成某個聯合型別,例如很常見的 status 會有 "pending" | "completed" | "failed" 這樣的表達。

但其實可以很簡單透過 ishas 這類字首,讓人一眼看出該變數是布林值。例如 hasRegistrationError 或者 isAccountActive。當改成這種方式命名,就能夠更精確,也更容易讓讀到這個命名的其他維護者,能不看細節內容,也知道這是布林值。

精確這個原則,不只在一般的變數,在函式與方法的命名也同樣適用。具體來說,需要確保該函式或方法在做的事情,能夠精確地反映到命名上。

有些書籍會說,函式的命名要用動詞加受詞,來表達對某件事情做某個操作。然而,如果只是這樣命名函式,仍然可能會不夠精確。舉例來說,下面這個例子的 handleUsers 函式,就相對不是太好的命名方式。因為假如只看名稱的話,不知道 handle 是要處理什麼。

function handleUsers(users: User[]): User[] {
  return users.filter((user) => user.isActive);
}

但是假如看該函式的內容,顯然是在把 isActive 的使用者挑出來,所以比較好的命名會是改成 filterActiveUsers。因此,比起公式化地用動詞加受詞,更關鍵的還是回到該函式的本質,然後檢視命名是否反映其本質。

要多精確? 看涉及的範圍

上一段我們談到,在命名上要盡可能精確。然而,可能會有讀者問,命名到底要到多精確才行? 假如在一個 for 迴圈中用 ij 恰當嗎?

關於這個問題,在目前社群中普遍推薦的判斷原則,是看要命名的東西影響的範圍有多大。以上面談到的 block 來說,假如是在整個系統中,許多地方都會用到,意味著影響範圍很大,這時候就要越精確越好。換句話說,一個變數 (或函式) 的「宣告位置」與「使用位置」之間的程式碼距離越遠,它的名稱就應該越長、越具描述性。

反之,假如某一個 for 迴圈中的索引,只會被用在該 for 迴圈當中,不會被用在其他地方,這種時候命名簡短一點其實不會有太大的問題。所以假如用 for (let i = 0, i < array.length; i++) 這種寫法,多數時候是沒問題的。

如果覺得難精確命名,代表程式可能該重構

讀完前一個段落,相信有些讀者可能會問「假如沒辦法決定一個精確的命名該怎麼辦? 」

假如有這種問題,往往意味著程式碼可能寫得有問題、需要重構。因為通常會覺得無法精確命名,是因為可能某個函式或方法,太包山包海了,所以導致該函式或方法,沒有辦法被單一個名稱定義出來。

先前我們在 寫出好維護的程式碼 — 高內聚 一文就有談到,為函式或方法設定邊界,確保在該函式與方法內的所有內容相關性都是高的,不僅會讓程式好維護,也能減少「因為函式做太多事,所以很難精確命名」的狀況。

前面談到的 John Ousterhout 教授就提過「如果你很難為一個變數或方法找到一個簡單的名稱,來清楚描繪它背後的實體 (物件或概念) ,這通常是一個警訊,代表那個實體本身的設計可能不夠簡潔。」

If it’s hard to find a simple name for a variable or method that creates a clear image of the underlying object, that’s a hint that the underlying object may not have a clean design. — John Ousterhout

閱讀更多

關於命名,除了精確外,還有其他要關注的重點,我們在 E+ 成長計畫的主題文有更深入的討論,感興趣的讀者,歡迎加入 E+ 閱讀 (連結)。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們