Pages

Tuesday, May 28, 2013

[雜談] 程式中的命名風格

很久以前一直在規劃要弄個雜談區,方向大概就是放一些「看起來雜亂無章,不曉得能做什麼的嘴砲文」... 其實就是把一些放在D槽多年的資料移到雲端備份XD 但既然要丟上雲端,還是會盡量讓內容看起來有條理一點。作為系列的第一篇,先來個時事文吧。



前幾天在 BBS 上看到命名風格的討論,由於持續了幾天,就稍微看了一下大家在聊些什麼。那一串的原點是有人提到對專案中出現「型別前綴」這種參數命名原則不太滿意,結果很多人就爆炸了XD

工作或專案上的 coding rule 通常沒有什麼對錯可言,就是一組大家都能接受的規則而已。在團隊成員對於程式碼的表達有一致的認知,而不是各自表述時,才有機會繼續談後面的合作。而「命名風格」雖然帶有不少藝術與美感(順眼)等主觀成份,也不是因此就沒有討論的空間。

編寫程式不完全是讓電腦系統可以運作那麼單純,也是程式設計師們傳達自己想法的管道。程式的編寫跟寫作一樣,雖然不必使用到華麗的詞藻,但我相信一份敘事簡明的程式,閱讀起來也是賞心悅目。作為個人能力的養成,還是值得花一點時間研究... 至於在團隊中使用,以及成員間的溝通協調,就真的是政治問題或人品問題啦~




通常在決定依循哪一種風格時都有很多考量,可能是客戶的要求、團隊的傳統、被什麼書籍文章洗腦、或是開發平台與程式語言的影響。不過在實際的開發中,我的經驗是命名只要不是太誇張的縮寫或使用冷僻的英文單字,對可讀性的影響微乎其微。曾經遇過比較誇張例子是用德文來命名變數,這對於完全不懂德文的我還真的是「不夠直覺」的命名。除了那次經驗外,即使接觸到好幾種不同風格也不會真的造成什麼困擾。

那為什麼還要寫這篇文章呢?雖然這種風格觀點是很容易被戰到爆的萬年戰文,而且我也討厭寫個程式還要有一堆規約來限制自己,但最近也常常需要向別人說明為什麼程式會寫成某種樣子,就趁這個機會整理一下,順便也檢視一下自己的習慣。其實程式寫久了通常會慢慢有自己的習慣,例如我私底下愛用的縮寫風格已經蠻固定了,就算現在看到多年前的變數命名,還是可以直覺理解用途。會需要特別留意命名方式主要還是為了和別人溝通,以及維護性與一致性吧。

順帶一提,整理這篇文章其實花了好幾天時間,因為一直以來都是覺得有問題就自動調整一下,完全沒特別選擇要用什麼風格,只好把手邊的程式都抓出來整理一下XD



一般提到「型別前綴」的命名風格通常會直接聯想到「(系統)匈牙利命名法」(Systems Hungarian Notation),對於以前接受過微軟平台技術洗禮的人多半不陌生。到了現在的 .Net Framework Naming convention 則改為建議 DO NOT use 了。我個人並不討厭加上綴字,命名時適當地加上綴字其實是有助於理解的。匈牙利命名法這種風格,最早也曾經嘗試使用過一陣子,但隨著 C 以外的語言越用越多,尤其是在可以大量創造型別的語言中(例如 C++ 和 Python),型別要怎麼加上去就會是個大問題;再加上後來覺得這樣的命名其實也曝露了實作細節,遇到要改型別時很麻煩,就漸漸地不再使用了。真的需要在命名中說明型別時,也會盡量以抽象型別為主,避免直接使用基礎型別。

除了團隊和專案的規範外,我私底下慣用的命名風格應該還算單純,沒什麼麻煩的規定。大部份情況都是很隨性地命名,整理一下大概就是幾個規則是比較固定會使用的:
命名
原則上只要沒有特別限制,使用全小寫、hyphen 分隔的命名方式,不支援的時候則改用 camel case。在可能的範圍內盡量用不影響可讀性或比較通用的縮寫,以字少的優先。主要是在使用不同語言時可能會有一些差異。舉例來說,在符號使用限制少的 forth 或 scheme / lisp 中可以用全小寫、hyphen 或斜線符號區隔的命名風格:
(define module-do-something ...)
(defun module/do-something ...)

而在 C 中只能用底線(underscore)分隔 namespace / module name 與 tag name:
RetType Module_Method(ArgType ...);

遇到需要標明存取控制範圍的時候,例如 private,在 C 中是在前面或後面加底線,在 Emacs lisp 這種沒有模組概念的語言可能改成在 module name 後面寫成 --,Python 則是在前面加上底線... 大概也是依語言而異。

型別
可以的話盡量定義抽象型別來使用:例如在 C 裡透過 typedef 定義新型別,通常會加上 _t 作為後綴。新的 C++11 也可以定義 type alias。命名時盡量以大寫字母開頭,以前在 Io 中接觸到這種風格,覺得還不錯就繼續用了。

變數
全部小寫,有必要時使用 lower camel case。全域變數會加前綴,大概會是 g_ 之類的,不過通常很少出現全域變數所以不常用。

遇到代表容器的變數,通常以英文的複數型式命名,例如 users。有需要的話可以加上輔助說明用的前綴,例如表達數量時用 numBooks

需要特別提醒操作方式或用途的時候,可能會加上一點修飾,例如一個暫時使用的變數可能會加上 tmp 前綴;另外就是在 C/C++ 中被 wild pointer 戳死的機會太多了,這時也會特別在前面加上 p 提醒自己。

常數
包括列舉(enum)用的常數,通常全部以大寫命名。因為沒辦法 camel case 所以單字之間可能會使用 hyphen 或底線分隔。不過在 scheme 或 lisp 中多半還是小寫。

函式
通常是動詞開頭的型式,例如 do-create-send-。如果是判斷用的函式則看各語言風格,一般是 is 開頭的型式,這也是從 Io 來的風格,但在 forth 和 scheme 中則是在最後加上 ? 後綴:
(if (buffer-empty? ...)

寫 lisp 時則改用 -p
(if (buffer-empty-p ...)

其他還有像轉換資料用的函式命名,依語言的不同可能會用 >->-to- 等分隔。
談這種東西很容易混到其他撰寫風格的討論,命名的部份大概就是這樣吧。各語言的細節太多了,一時也講不完,原則上我會盡量先參考語言慣例,這樣在搭配標準函式庫使用時才不會變成混合風格。至於很多人建議的底線,因為輸入實在很麻煩,對可讀性好像也沒有什麼決定性的幫助... 自從我改用 emacs 寫程式後就盡量少用了,反正編輯可以靠 subword,閱讀時真的不行就開 glasses-mode 吧 :-p

不過說到命名,在規則受限的語言中還真的很麻煩,我覺得與其用底線,還不如用 hyphen(可以少按一個 shift),但在 C 連 - 都不准用。在 forth 中只要不是空白字元都可以用,命名時的表達力就豐富多了,適時使用 ->>>? 等符號對可讀性還是有些幫助的。單就這一點來看,haskell 很不錯,可以用符號設計很多可愛的運算子XD

這樣整理下來,我的適應力應該還算不錯吧,大概都是語言慣例就用了XD 但實際上在寫的時候,除了團隊要求的情況外也不是很嚴格地要求自己遵守啦,反正寫久變成習慣的,不必太要求就會成形;真的需要違反原則時,大概也不會出現太誇張的命名。硬是要規定套用某些規定反而失去發揮的空間,我想一般寫程式的人並不是單純的打字員,應該都不會喜歡的吧。

No comments:

Post a Comment