選單

程式設計5分鐘,命名2小時!探討一下C語言變數函式的命名規則!

在軟體中隨處可見命名:要給變數、函式、引數、類和封包命名,還要給原始碼及原始碼所在目錄命名,甚至還有jar檔案、war檔案和ear檔案命名。

但是,看似簡單的命名,也是讓不少程式設計師頭疼的問題。有一些小夥伴,在進行變數命名的時候,對於自己熟悉的英文,可能還會用英文命名一下,如果需要命名的部分不會用英文表達,或許就直接用拼音了。

有的童鞋一下想不起來怎麼命名,直接用拼音直接用aa,bb等這樣沒有任何代表意義的字母來命名,可讀性非常差,可能自己今天寫的,一個星期後回來再看,也忘記其具體代表的含義了。

因此,許多人在寫程式碼之前,總會在想啊想啊,用什麼命名法好呢?對於經常在C++、Java、Python等主流語言上切換的強迫症來說,換個語言換種命名風格簡直不要太混亂。

程式設計5分鐘,命名2小時!探討一下C語言變數函式的命名規則!

既然有這麼多命名要做,不妨做好它。本期內容中,非同步君為大家帶來了起個好名字應遵從的幾條簡單規則,一起來看看吧

01

名副其實

名副其實說起來簡單。我們想要強調,這事很嚴肅。選個好名字要花時間,但省下來的時間比花掉的多。注意命名,而且一旦發現有更好的名稱,就換掉舊的。這麼做,讀你程式碼的人(包括你自己)都會更開心。

變數、函式或類的名稱應該已經答覆了所有的大問題。它該告訴你,它為什麼會存在,它做什麼事,應該怎麼用。如果名稱需要註釋來補充,那就不算是名副其實。

名稱d什麼也沒說明。它沒有引起讀者對時間消逝的感覺,更別說以日計了。我們應該選擇指明瞭計量物件和計量單位的名稱:

選擇體現本意的名稱能讓人更容易理解和修改程式碼。下列程式碼的目的何在?

為什麼難以說明上述程式碼要做什麼事?裡面並沒有複雜的表示式,空格和縮排中規中矩,只用到三個變數和兩個常量,甚至沒有涉及任何其他類或多型方法,只是(或者看起來是)一個數組的列表而已。

問題不在於程式碼的簡潔度,而在於程式碼的模糊度:即上下文在程式碼中未被明確體現的程度。上述程式碼要求我們瞭解類似以下問題的答案:

(1)theList中是什麼型別的東西?

(2)theList零下標條目的意義是什麼?

(3)值4的意義是什麼?

(4)我怎麼使用返回的列表?

問題的答案沒體現在程式碼段中,可程式碼段就是它們該在的地方。比方說,我們在開發一種掃雷遊戲,我們發現,盤面是名為theList的單元格列表,那就將其名稱改為gameBoard。

盤面上每個單元格都用一個簡單陣列表示。我們還發現,零下標條目是一種狀態值,而該種狀態值為4表示“已標記”。只要改為有意義的名稱,程式碼就會得到相當程度的改進:

注意,程式碼的簡潔性並未被觸及。運算子和常量的數量全然保持不變,巢狀數量也全然保持不變,但程式碼變得明確多了。

還可以更進一步,不用int陣列表示單元格,而是另寫一個類。該類包括一個名副其實的函式(稱為isFlagged),從而掩蓋住那個魔術數[1]。於是得到函式的新版本:

只要簡單改一下名稱,就能輕易知道發生了什麼。這就是選用好名稱的力量。

02

避免誤導

程式設計師必須避免留下掩藏程式碼本意的錯誤線索。應當避免使用與本意相悖的詞,例如,hp、aix和sco都不該用作變數名,因為它們都是Unix平臺或類Unix平臺的專有名稱。即便你是在編寫三角計算程式,hp看起來是一個不錯的縮寫[2],但那也可能會提供錯誤資訊。

別用accountList來指稱一組賬號,除非它真的是List型別。List一詞對程式設計師有特殊意義。如果包納賬號的容器並非真是一個List,就會引起錯誤的判斷。

所以,用accountGroup或bunchOfAccounts,甚至直接用accounts都會好一些。

提防使用外形相似度較高的名稱。例如,想區分模組中某處的XYZControllerFor-EfficientHandlingOfStrings和另一處的XYZControllerForEfficientStorage-OfStrings,會花多長時間呢?這兩個詞的外形實在太相似了。

以同樣的方式拼寫出同樣的概念才是資訊。拼寫前後不一致就是誤導。我們很享受現代Java程式設計環境的自動程式碼完成特性。鍵入某個名稱的前幾個字母,按一下某個熱鍵組合(如果有的話),就能得到一列該名稱的可能形式。

假如相似的名稱依字母順序放在一起,且差異很明顯,那就會相當有助益,因為程式設計師多半會壓根不看你的詳細註釋,甚至不看該類的方法列表就直接看名字挑一個物件。

誤導性名稱真正可怕的例子,是用小寫字母l和大寫字母O作為變數名,尤其是在組合使用的時候。當然,問題在於它們看起來完全像是常量“壹”和“零”。

讀者可能會認為這純屬虛構,但我們確曾見過充斥這類名稱的程式碼。有一次,程式碼作者建議用不同字型寫變數名,好顯得更清楚些,但前提是這種方案得要透過口頭和書面傳遞給未來所有的開發者才行。後來,只是做了簡單的重新命名操作,就解決了問題,而且也沒引起別的問題。

03

做有意義的區分

如果程式設計師只是為滿足編譯器或直譯器的需要而寫程式碼,就會製造麻煩。例如,因為同一作用範圍內兩樣不同的東西不能重名,你可能會隨手改掉其中一個的名稱,有時乾脆以錯誤的拼寫充數,結果就會出現在更正拼寫錯誤後導致編譯器出錯的情況。

程式設計5分鐘,命名2小時!探討一下C語言變數函式的命名規則!

光是新增數字系列或是廢話遠遠不夠,即便這足以讓編譯器滿意。如果名稱必須相異,那麼其意思也應該不同才對。

以數字系列命名(a1、a2…aN)是依義命名的對立面。這樣的名稱純屬誤導——完全沒有提供正確資訊,沒有提供導向作者意圖的線索。試看:

如果引數名改為source和destination,這個函式就會像樣許多。

廢話是另一種沒意義的區分。假設你有一個Product類,如果還有一個名為ProductInfo或ProductData的類,那它們的名稱雖然不同,意思卻無區別。Info和Data就像a、an和the一樣,是意義含混的廢話。

注意,只要體現出有意義的區分,使用a和the這樣的字首就沒錯。例如,你可能把a用在域內變數,而把the用於函式引數[5]。但如果你已經有一個名為zork的變數,又想呼叫一個名為theZork的變數,麻煩就來了。

廢話都是冗餘。variable一詞永遠不應當出現在變數名中。table一詞永遠不應當出現在表名中。NameString會比Name好嗎?難道Name會是一個浮點數?如果是這樣,就違反了關於誤導的規則。

設想有一個名為Customer的類,還有一個名為CustomerObject的類,它們的區別何在呢?哪一個是表示客戶歷史支付情況的最佳方式?

有一個應用反映了這種狀況。為當事者諱,我們改了一下,不過犯錯的程式碼的確就是這個樣子:

程式設計師怎麼知道該呼叫哪個函式呢?

如果缺少明確約定,那麼變數moneyAmount與money就沒區別,customerInfo與customer沒區別,accountData與account沒區別,theMessage也與message沒區別。要區分名稱,就要以讀者能鑑別不同之處的方式來區分。

04

使用讀得出來的名稱

人類長於記憶和使用單詞。大腦的相當一部分就是用來容納和處理單詞的。單詞能讀得出來。人類的大腦中有那麼大的一塊地方用來處理言語,若不善加利用,實在是種恥辱。

如果名稱讀不出來,討論的時候就會像個傻鳥。“哎,這兒,鼻涕阿三喜摁踢(bee cee arr three cee enn tee)[6]上頭,有個皮挨死極翹(pee ess zee kyew)[7]整數,看見沒?”這不是小事,因為程式設計本就是一種社會活動。

有一家公司,程式裡面寫了一個genymdhms(生成日期,年、月、日、時、分、秒),他們一般讀作“gen why emm dee aich emm ess”[8]。我有見字照拼讀的惡習,於是開口就唸“gen-yah-mudda-hims”。

後來好些設計師和分析師都有樣學樣,聽起來傻乎乎的。我們知道典故,所以會覺得很搞笑。搞笑歸搞笑,實際是在強忍糟糕的命名。在給新開發者解釋變數名的意義時,他們總是讀出傻乎乎的自造詞,而非恰當的英語詞。比較

現在讀起來就像人話了:“喂,Mikey,看看這條記錄!生成時間戳(generation timestamp)[9]被設定為明天了!不能這樣吧?”

05

使用可搜尋的名稱

對於單字母名稱和數字常量,有一個問題,就是很難在一大篇文字中找出來。

找MAX_CLASSES_PER_STUDENT很容易,但想找數字7就麻煩了,它可能是某些檔名或其他常量定義的一部分,出現在因不同意圖而採用的各種表示式中。如果該常量是個長數字,又被人錯改過,就會逃過搜尋,從而造成錯誤。

同樣,e也不是一個便於搜尋的好變數名,它是英文中最常用的字母,在每個程式、每段程式碼中都有可能出現。由此而見,長名稱勝於短名稱,搜得到的名稱勝於用自造編碼代寫就的名稱。

竊以為單字母名稱僅用於短方法中的本地變數。名稱長短應與其作用域大小相對應 [N5]。若變數或常量可能在程式碼中多處使用,則應賦予其便於搜尋的名稱。再比較:

注意,上面程式碼中的sum並非特別有用的名稱,不過至少搜得到它。採用能表達意圖的名稱,貌似拉長了函式程式碼,但要想想看,WORK_DAYS_PER_WEEK比數字5好找得多,而列表中也只剩下了體現作者意圖的名稱。

06

避免使用編碼

編碼已經太多,無謂再自找麻煩。把型別或作用域編進名稱裡面,徒然增加了解碼的負擔。沒理由要求每位新人都在弄清要應付的程式碼之外(那算是正常的),還要再搞懂另一種編碼“語言”。這對解決問題而言,純屬多餘的負擔。帶編碼的名稱通常也不便發音,容易打錯。

匈牙利語標記法

在往昔名稱長短很重要的時代,我們毫無必要地破壞了不編碼的規矩,如今後悔不迭。Fortran語言要求首字母體現出型別,導致了編碼的產生。BASIC語言的早期版本只允許使用一個字母再加上一位數字。匈牙利語標記法[10](Hungarian Notation,HN)將這種態勢愈演愈烈。

在Windows的C語言API的時代,HN相當重要,那時所有名稱要麼是一個整數控制代碼,要麼是一個長指標或者void指標,要不然就是string的幾種實現(有不同的用途和屬性)之一。那時候編譯器並不做型別檢查,程式設計師需要匈牙利語標記法來幫助自己記住型別。

現代程式語言具有更豐富的型別系統,編譯器也記得並強制使用型別。而且,程式設計師趨向於使用更小的類、更短的方法,好讓每個變數的定義都在視野範圍之內。

Java程式設計師不需要型別編碼,因為物件是強型別的,程式碼編輯環境已經先進到在編譯開始前就能監測到型別錯誤的程度!所以,如今HN和其他的型別編碼形式都純屬多餘。它們增加了修改變數、函式或類的名稱或型別的難度,它們增加了閱讀程式碼的難度,它們製造了讓編碼系統誤導讀者的可能性。

成員字首

也不必用m_字首來標明成員變數。應當把類和函式做得足夠小,以消除對成員字首的需要。你應當使用某種可以高亮或用顏色標出成員的編輯環境。

此外,人們會很快學會無視字首(或字尾),而只看到名稱中有意義的部分。程式碼讀得越多,眼中就越沒有字首。最終,字首變作了不入法眼的廢料,變作了舊程式碼的標誌物。

介面和實現

有時也會出現採用編碼的特殊情形。比如,你在做一個建立形狀用的抽象工廠(Abstract Factory),該工廠是一個介面,要用具體類來實現。你怎麼來命名工廠和具體類呢?IShapeFactory和ShapeFactory嗎?我喜歡不加修飾的介面。前導字母I被濫用到了說好聽點兒是干擾,說難聽點兒根本就是廢話的程度。

我不想讓使用者知道我給他們的是介面,而就想讓他們知道那是一個ShapeFactory。如果在介面和實現中必須選其一來編碼的話,我寧肯選擇實現。ShapeFactoryImp,甚至是醜陋的CShapeFactory,都比對介面名稱編碼好。