不用前額葉的命名
良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。
¶前額葉
前額葉皮質(prefrontal cortex, PFC)是額葉的前部。
一般會介紹它掌管人類的理性,讓人可以辛苦的努力生活著,而不是讓人朝向舒適、安逸的方向生活著。
不用前額葉命名指的是,命名很糙。
¶命名的重要性
相信任何一本介紹關於程式碼品質的書,都會介紹到命名的重要性。
"the goodness or badness of a variable is largely determined by its name. "
–《Code Complete 2/e》, Ch11 The Power of Variable Names, 11.1 Considerations in Choosing Good Names
意思是「一個變數的表現如何,取決於它的命名。」
舉一些常見的無用命名,如果有縮寫,就一列在一起,不然就太長了
- tmp, temp
- 單一字母 a, b, c, (i, j, k 有特殊情況可使用)
- 字串命名成 str
- 數字命名成 number, num, No
- 變數 value
- doSomething()
- run()
- 無意義的字 cc, aa
- 單字加上數字 name1, age1, user1
- info
- 上述的字當前綴或後綴
如果出現上述的命名,一定是沒有深刻的了解問題,一定是。
出現這樣的命名,我都反問
「哪個變數不是 value」
「哪個字串不是 str」
「哪個數字不是 number」
「number, num, No 唸唸看它們是不是有差別?」
「哪個函數不是 doSomething()」
「哪個物件沒有 info」
「你要解決的是什麼問題 (出現 tmp, temp, aa, cc),用說的給我聽」
如果出現無意義的前後綴,會看看上下文是不是有重複的字眼。
¶名符其實,是好命名
「好的程式要好猜,來自於好的命名。」
好猜中,一直是我個人對程式碼好壞的標準,這其實也是寫作中最重要的事。有些小說讓你好猜中後面的劇情,其實寫小說的人也許是個優秀的程式設計師 (笑)
有些標準可以參考[1]
- doesn’t contain cryptic abbreviations, and it’s unambiguous.
不縮寫,明確 - a fulldescription of the entity, it won’t be confused with something else.
完整描述,不含糊 - it’s easy to remember because the name is similar to the concept.
好記名稱,符合描述目標的概念
好的命名,會幫助程式碼閱讀,加快除錯與維護的速度,降低維護成本提高工程師產值,若無法了解這一件事,別說你是一個「好的工程師」
以學語言來分析使用命名這件事,可以分成聽、說、讀、寫。寫程式就是使用「讀、寫」的技術,而這些其實很常見,往往忽略了語言的「聽、說」特性,很簡單的好命名測試方式: 「用唸的,聽聽看是不是可以唸?聽不聽得懂?」
¶特定型別的命名慣例
在此就不像各家程式碼品質書上這樣的詳細介紹。
¶boolean 的命名
這個情況較常見,不管是什麼語言都可以使用。
而 boolean 並不只適用有型別的語言。
就連 C 語言(沒有 boolean) 都適用。
int isTrue = 1;
if (isTrue) {
//...
}
也就是說,只要符合特定概念,就可以使用該命名。不用管它是什麼程式語言。
¶class, variable
(variable 是 class 的物件形式)
大多數的命名詞性,都是名詞、動名詞,總之是一種「定義」型的命名方式
與 class 或 variable 一起出現的等號運算式,正是一種 is 表現定義的方式。
¶function, method
用詞解釋
- function 是全域可執行的 function
- method 指的是在物件中的 property function 或稱為 member function
funciton 和 method 大多數使用的命名詞性是動詞或動詞片語[2]
getter/setter 要用 get/set 當前綴字 (不過現在有些語言會在 class 定義 getter/setter 並且隱含的自動執行)
¶物件 v.s. 英文文法
學英文句型時,總是會學到的基本句型術語
- S: 主詞
- V: 動詞
- O: 受詞 (option)
用英文的句型來看,好的命名就會符合這樣的句型
S.V(O)
這種 object 配合 method 的做法,沒有被動式的動詞。
被動式
什麼時候使用被動式的語意呢? event 觸發時!!!
const button = document.querySelector('button')
button.addEventListener('click', callback)
在這時會說「當點擊按鈕時,執行 callback」,其實按鈕是「被點擊」。
在此 callback 的命名,會使用主動型態的動詞。(ex: createItem, deleteUser…)
好吧,有點牽強。(笑)
被動式的確很少拿來解式程式語言,一般看來是主動式的命名。
¶特例情況
¶短迴圈允許 i, j, k
for (let i = 0; i < 10; i++) {
//...
}
在此使用 i 原因有兩個可能
index的縮寫,是一個迴圈中的索引值iterate的縮寫,指的是一種迭代的過程
使用了 i 之後,依照數學慣例,接續在後面的字母就會延續一樣的概念使用,而不管字母本身的符號意義。
j 的意思,只是 i 的下一個
k 的意思,依此類推
¶匈牙利命名法(Hungarian notation)
這個惡名昭彰的匈牙利命名法,這一段要來說它的好與壞。
最初由 Charles Simonyi 發明,最早出現在微軟的 Windows 團隊。[3]
最初的用意
相信任何方法的立意都是良善,而不是要讓程式碼更不好。所以匈牙利命名法一開始也是為了解決問題而存在的。
在早期的 C 語言編譯器,不會進行型別檢查,對於 call by value 或 call by address 時,總是產生 bug,對於這個常見的問題,提出了解決方式。
使用方式:
- 型別縮寫當前綴
- 指標要用
p當前綴
所以,匈牙利命名法的前提就是以傳達資訊為主,不管名稱是不是可以唸出來[4]
長得像這樣
void *pvNewBlock(size_t size);
void *pvResizeBlock(void *pv, size_t sizeNew);
優點
在 C 語言的指標操作,常常會因為 * 符號而搞得不知道這變數是不是要加 * 加了會不會出錯,而感到心有疑慮的寫程式。
在下面的程式,只要把成對的 * 和 p 相抵,就可以推算出這一行是不是有錯誤的賦值行為。
*ppb = ppbNew;
// (*p)pb = ppbNew;
// 等效於 pb = pbNew;
pb = &b; // p 和 & 相抵消
b = psym->bLength //p 和 -> 抵消
這樣就可以在一行程式中指出可能會發生的錯誤。
(別忘了,還有寬字元和一般字元的問題,這就更需它了)
缺點
唸不出來(和上述的測試方式矛盾了!!)
犧牲可讀性
為什麼現在很少見?或之後被說得這麼難聽?
也許,有一陣子被濫用。
不過主要的原因,是後來的編譯器進步,會檢查型別並建議使用轉型,就不再需要這樣,犧牲可讀性的命名方式了。
¶前後綴的命名法
匈牙利命名法的延伸應用,其實到現今還有在使用。常見的命名原則像「全域變數要加 g 當前綴」,但是匈牙利命名法給後來的時代什麼樣的啟示呢?
匈牙利命名法,當初之所以好用,是因為它利用命名彌補了語言本身的缺陷,但是因為語言進步了,這樣的命名法就不再適用了。
也就是說,當語言本身有什麼功能尚未有實作時,也許就會出現這樣的命名法。(亂世,就會出英雄的概念?)
將匈牙利命名法再精鍊一下,本質上它是使用前後綴的技巧,達到傳遞某種訊息。
¶拿它來看看 CSS 的 BEM
BEM 是 Block, Element, Modifier 的縮寫,意思要依照這個順序命名,就可以找到 Selector 之間的相依性。
學習 BEM 前,先想想 CSS 這個標記語言有什麼缺陷?
- 任何宣告,都是全域
這也許是它的特色啦,不過對於程式語言來說,對於有 scope 的程式語言來說,對於習慣使用有 scope 的程式語言開發者來說,這是非常不習慣的。(這句很重要,所以要強調)
那麼,在 CSS 中的任何命名方式,都是為了彌補這一點,盡可能的讓命名含有 scope 或區域化,不要造成全域污染而做的努力。
如果遇到任何 CSS 命名有需要加 g (全域) 的前綴,請務必想起 CSS 的語言特性。(笑)
¶參考資料
[1]: 《Code Complete 2/e》, Ch11 The Power of Variable Names, 11.1 Considerations in Choosing Good Names, The Most Important Naming Consideration
[2]: 《Clean Code》, Ch2 Meaningful Names, Method Names
[3]: Hungarian notation
[4]: 《Writing Solid Code》 What are those gobbledygook names