ExplainThis 全端開發雙週報 #62 寫程式時如何做好命名 (naming)?
寫程式時如何做好命名 (naming)?
嗨~ 歡迎閱讀第 62 期 ExplainThis 全端開發雙週報!
這期的雙週報主題是《寫程式時如何做好命名 (naming)?》當提到軟體開發的命名時,相信多數人都會想的 Phil Karlton 當初說過的「在電腦科學中只有兩件困難的事:快取撤銷與命名」。
"There are only two hard things in Computer Science: cache invalidation and naming things."
— Phil Karlton, a former Netscape developer.
命名這件事雖然看似簡單,但事實上要做好並不容易;同時雖然命名看似一件小事,但實際卻對整體程式碼可讀性、可維護性影響很大。
特別是在大型軟體專案中,有成千上萬的變數名、物件名、方法名、函式名,如果其中一個沒命好,可能不會怎麼樣,但假如多個命名都沒做好,疊加在一起造成的問題,會成為不容忽視的技術債。
除了命名外,要寫出好維護的程式碼,除了命名外還有很多要訣,我們在 E+ 的主題文都有談到,感興趣的讀者歡迎加入閱讀 (連結)。
以上,讓我們進到這期的主題文吧!
寫程式時如何做好命名 (naming)?
在開頭我們提到,如果沒有做好命名,會讓整體的程式碼變得難維護,甚至很可能讓人寫出有 bug 的程式。但這樣講可能會讓人覺得很抽象,所以先讓我們看一個具體的案例。
先前史丹佛大學電腦科學系教授 John Ousterhout 在他的經典著作《A Philosophy of Software Design》一書中分享過他親身經歷過的慘痛教訓。他提到當年在開發一個分散式作業系統遇到一個離奇的 bug,在某些時候,檔案會遇到資料丟失的狀況,某一個區塊的資料,即使沒有被使用者改動,也會突然全部歸零。
這個 bug 花了 John Ousterhout 教授與他的研究生團隊,整整半年的時間,才總算找出問題所在。而這個問題所在,就是因為命名不清楚,導致變數被誤用。但也因為命名不清楚,所以被誤用後,不容易被察覺,導致要找出 bug 所在,變得特別花時間。
具體來講,當時在作業系統中的文件系統會用到 block 這個變數,但事實上這個 block 是有兩個不同的作用,一個是物理磁碟上的區塊編號,另一個是作為檔案中的邏輯區塊編號;所以當兩種混用在一起,就導致某個應該不該被碰到區塊,被覆寫歸零。
在看完上面這個問題,讀者們會覺得,假如要用比較好的命名方式,要如何重構這兩個變數名稱呢?
John Ousterhout 教授在重構時,選擇用 fileBlock 與 diskBlock 來重新命名。透過這個重構,這兩個變數名稱就變得非常清晰,該用來處理文件中邏輯的,就用 fileBlock;如果是處理物理磁碟的,則用 diskBlcok,兩者不會有搞混的時候。
希望透過上面的案例,有讓讀者們感受到,假如沒有做好命名,很可能會要面對這種意料之外的 debug 成本。假如想要避免的話,掌握一些命名的原則,會很有幫助。以下彙整了一些推薦讀者們在命名時,可以用上的原則。
推薦的命名原則:精確 (precise)
首先,命名最重要的原則之一就是要精確。精確的意思是不能模糊,要讓人能夠一眼看懂。
一個有效讓命名精確的方式,是問自己「如果某個人看到這個名稱,假如不看其他細節,例如不看文件、不看變數或函式內容,或者不看這個變數或函式用在哪,那個人有辦法推測出這個變數或函式在做什麼嗎?」
針對上面這個問題,如果沒有辦法,那就很可能代表函式不夠精確。
會不精確,通常是因為某個名稱試圖涵蓋太多東西,就以上面 John Ousterhout 教授分享的例子來說,block 這個變數涵蓋的面太廣,所以可能被有不同的詮釋,而當有了詮釋的空間,就可能讓這個變數被誤用。
除了上面的 block 案例,讓我們多看幾個例子,來具體瞭解如何把不精確的命名,調整成足夠精確的。
首先,很多人可能會覺得用 result 來表達回傳的結果,或者用 count 來表達某個正在被計算的數,這樣的命名就足夠精確。但是通常這樣的命名,仍是不夠精確地,因此可能導致像上面提到的問題。要調整的話,可以問「result 是什麼的結果? count 是在算什麼的?」,然後把這些資訊融入到命名中,就會更精確。
除此之外,不同性質的變數名,也要盡可能做到讓人一眼能看出其性質。舉例來說,const error = true; 或者 const status = false 都是不理想的布林值命名,因為這很難讓人第一眼就看出這兩個變數是布林值。
假如用 error 很可能讓人詮釋成不同的錯誤類別,而假如是 status 則可能會讓人詮釋成某個聯合型別,例如很常見的 status 會有 "pending" | "completed" | "failed" 這樣的表達。
但其實可以很簡單透過 is 或 has 這類字首,讓人一眼看出該變數是布林值。例如 hasRegistrationError 或者 isAccountActive。當改成這種方式命名,就能夠更精確,也更容易讓讀到這個命名的其他維護者,能不看細節內容,也知道這是布林值。
精確這個原則,不只在一般的變數,在函式與方法的命名也同樣適用。具體來說,需要確保該函式或方法在做的事情,能夠精確地反映到命名上。
有些書籍會說,函式的命名要用動詞加受詞,來表達對某件事情做某個操作。然而,如果只是這樣命名函式,仍然可能會不夠精確。舉例來說,下面這個例子的 handleUsers 函式,就相對不是太好的命名方式。因為假如只看名稱的話,不知道 handle 是要處理什麼。
function handleUsers(users: User[]): User[] {
return users.filter(user => user.isActive);
}但是假如看該函式的內容,顯然是在把 isActive 的使用者挑出來,所以比較好的命名會是改成 filterActiveUsers。因此,比起公式化地用動詞加受詞,更關鍵的還是回到該函式的本質,然後檢視命名是否反映其本質。
要多精確? 看涉及的範圍
上一段我們談到,在命名上要盡可能精確。然而,可能會有讀者問,命名到底要到多精確才行? 假如在一個 for 迴圈中用 i 與 j 恰當嗎?
關於這個問題,在目前社群中普遍推薦的判斷原則,是看要命名的東西影響的範圍有多大。以上面談到的 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+ 閱讀 (連結)。
本期推薦
Zed 的 Conrad Irwin 先前寫了《Why LLMs Can't Really Build Software》一文分析大型語言模型在軟體開發的侷限,也談到工程師存在的關鍵作用,非常推薦大家針對這些點加強 (連結)
TanStack 推出 TanStack DB 後,整個技術棧變得更完整,社群中有人寫了《An Interactive Guide to TanStack DB》一文,讓人能用互動的方式學會用 TanStack DB (連結)
Redis 的原作者 antirez 在他的個人 YouTube 頻道,發了一系列的 C 語言教學影片。雖然他是用義大利文教學,但現在 YouTube 的翻譯配音,讓課程能夠用英文來上,我們實際看過後,覺得教得非常好 (連結)
分析工具 PostHog 這週推出官網改版,在社群掀起廣泛討論。該官網突破了傳統官網的形式,在文案中也充滿巧思,真的非常佩服 (連結),也希望社群能有更多這種重新定義的標準的案例
上週 npm 又有現套件攻擊的案例,由於這次被攻擊的一位開發者,底下的套件每週下載次數超過 20 億,所以成為社群討論的焦點之一。我們有寫一篇貼文彙整社群中關於「如何降低被攻擊波及」的討論,感興趣的讀者推薦閱讀 (連結)
今天看到 GitHub 的主任工程師 Sean Goedecke 分享了 Everything I know about good API design 一文,從實務的角度談設計 API 時要注意的事項 (連結)。先前我們也有寫過同個主題的內容,還沒讀過的讀者推薦溫習 (連結)
Nicolas Carlo 寫了《The key points of Working Effectively with Legacy Code》談面對歷史悠久的遺留程式碼 (legacy code),可以如何有效做修改與重構 (連結),如果在工作上也要面對這類程式碼,可以一讀
文末彩蛋
最近在社群中有人引用了美式足球傳奇教練 Vince Lombardi 說過的一句話
想要成功的唯一方式,就是全力以赴、把自己有的一切都投入其中 There’s only one way to succeed in anything, and that is to give it everything.
然而,這句話不是所有人都認同,在社群中看到一個值得思考的回覆是
I’ve never met a person who embodied this sentiment and wasn’t miserable. Very successful in one thing, and often a failure in everything else. If you’re truly ambitious you should want to have it all: lifestyle, family, work, health, hobbies. True success is multi-dimensional.
回覆這句話的人提到,過去觀察到真的只全力投入單一事情的人,即使在該事情做到很成功,在其他面向也往往一蹋糊塗。
他進一步說,人生是多面向的,工作之外還有家庭、健康、生活、興趣等等;如果想要在人生成功,不能忽略其他的面向。
當然,成功的定義是很主觀的,上面兩個說法不能說哪一個就是絕對的正確。不過,也因為是主觀的,推薦大家在思考如何定義自己的成功時,不要被單一說法給侷限,而是回到自己的價值觀,定義出能讓自己感到有意義的成功。

