1. 程式人生 > >【新手必備】:高質量 程式碼命名規則

【新手必備】:高質量 程式碼命名規則

本文與大家聊一聊程式設計中非常關鍵的一個點,如何更好的對程式碼命名。

一、引言

《程式碼整潔之道》這本書提出了一個觀點:程式碼質量與其整潔度成正比,乾淨的程式碼,既在質量上可靠,也為後期維護、升級奠定了良好基礎。書中介紹的規則均來自作者多年的實踐經驗,涵蓋從命名到重構的多個程式設計方面,雖為一“家”之言,然誠有可資借鑑的價值。

但我們知道,很多時候,理想很豐滿,現實很骨感,也知道人在江湖,身不由己。因為專案的緊迫性,需求的多樣性,我們無法時時刻刻都寫出整潔的程式碼,保持自己輸出的都是高質量、優雅的程式碼。

但若我們理解了程式碼整潔之道的精髓,我們會知道怎樣讓自己的程式碼更加優雅、整潔、易讀、易擴充套件,知道真正整潔的程式碼應該是怎麼樣的,也許就會漸漸養成持續輸出整潔程式碼的習慣。

而且或許你會發現,若你一直保持輸出整潔程式碼的習慣,長期來看,會讓你的整體效率和程式碼質量大大提升。

二、本文涉及知識點思維導圖

先放出這篇文章所涉及內容知識點的一張思維導圖,就開始正文。大家若是疲於閱讀文章正文,直接看這張圖,也是可以Get到本文的主要知識點的大概。

 

三、高質量程式碼的命名法則

名副其實

名副其實說起來貌似很簡單,但真正做起來,似乎沒那麼容易。選個好名字要花一些時間,但其實選好名字之後省下來的時間,要比之前選名字時花掉的時間多得多。

我們一旦發現有更好的名稱時,就應該換掉之前的舊名稱,這樣做讀你程式碼的人(包括你自己),都會很開心。

一個好的變數、函式或類的名稱應該已經幾乎答覆了所有的大問題。它應該告訴你,這個名稱所代表的內容,為什麼會存在,做了什麼事情,應該如何用等。

如果一個名稱需要註釋來補充才能讓大家明白其真正含義,那其實就不算是名副其實。(並不是說不需要註釋,恰如其分的註釋是程式設計師讓自己程式碼錦上添花的好方法,關於註釋的一些注意事項,稍後會有文章專門涉及。)

舉個栗子:

以下的這句程式碼裡的d就不算是個好命名。名稱d什麼都沒說,它沒引起我們對時間消逝的感覺,更別說單位是天了:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. int d; // elapsed time in days||經過了幾天時間
int d; // elapsed time in days||經過了幾天時間

我們應該選擇這樣的指明瞭計量物件和計量單位的名稱:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. int elapsedTimeInDays;  
  2. int daysSinceCreation;  
  3. int daysSinceModification;  
  4. int fileAgeInDays;
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

避免造成誤導

我們應該避免留下隱藏程式碼本意的錯誤線索,也應該避免使用與本意相悖的詞。例如,別用accountList來指一組賬號,除非它真的是List型別,用accountGroup、bunchOfAccounts,或者直接用accounts,都是更好的選擇。

儘量提防長得太像的名稱。想區分XYZControllerForEfficientHandlingOfStrings和XYZControllerForEfficientStorageOfStrings,會花費我們太多的時間。因為這兩個詞,實在太相似了。

以同樣的方式拼寫出同樣的概念才是資訊,拼寫前後不一致就是誤導。

儘量做有意義的區分

        1.儘量避免使用數字系列命名(a1、a2…….aN)。這樣的名稱純屬誤導,因為很多時候完全沒有提供正確的資訊,沒有提供導向作者意圖的線索。

        2.廢話是另一種沒有意義的區分。如果我們有一個Product類,還有一個ProductInfo或ProductData類,那麼他們的名稱雖然不同,但意思卻無區別。這裡的Info、Data就像a、an和the一樣,是意義含混的廢話。

        注意,只要體現出有意義的區分,使用a、the這樣的字首就沒錯。例如,將a用在域內變數,把the用於函式引數。

儘量使用讀得出來的名稱

我們要使用讀得出來的名稱。如果名稱讀不出來,討論的時候就會不方便且很尷尬,甚至讓旁人覺得很蠢。

例如,變數名稱是beeceearrthreecee,討論的時候讀起來簡直像沒吃藥。

儘量使用可搜尋的名稱

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

找MAX_CLASSED_PER_STUDENT很容易,但想找數字7,就很麻煩。

同樣,字母e也不是個便於搜尋的好變數名。因為作為英文中最常用的字母,在每個程式、每段程式碼中都有可能出現。

名稱長短應與其作用域大小相對應,若變數或常量可能在程式碼中多處使用,應賦予其以便於搜尋的名稱。

舉個栗子,比較如下兩段程式碼:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. for (int j=0; j<34; j++)  
  2. {  
  3.     s += (t[j]*4)/5;  
  4. }  
for (int j=0; j<34; j++)
{
	s += (t[j]*4)/5;
}

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. constint WORK_DAYS_PER_WEEK = 5;  
  2. int sum = 0;  
  3. for (int j=0; j < NUMBER_OF_TASKS; j++)  
  4. {  
  5. int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;  
  6. int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);  
  7.     sum += realTaskWeeks;  
  8. }  
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++)
{
	int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
	int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
	sum += realTaskWeeks;
}

按整潔程式碼的要求來評判,第一段程式碼會讓讀者不知所云,第二段程式碼比第一段好太多。第二段程式碼中,sum並非特別有用的名稱,但至少他搜尋得到。採用能表達意圖的名稱,貌似拉長了函式程式碼,但要想想看,WORK_DAYS_PER_WEEK要比數字5好找得多,而列表中也只剩下了體現我們意圖的名稱。

取名不要繞彎子

我們取名的時候要避免思維對映,不應當讓讀者在腦中把你的名稱翻譯為他們熟知的名稱,也就是說取名不要繞彎子,而是要直白,直截了當。

在多數情況下,單字母不是個好的命名選擇,除非是在作用域小、沒有名稱衝突的地方,比如迴圈。迴圈計數器自然有可能被命名為i,j或k(最好別用字母l),這是因為傳統上我們慣用單字母名稱做迴圈計數器。

程式設計師通常都是聰明人,聰明人有時會藉助腦筋急轉彎炫耀其聰明。而聰明程式設計師和專業程式設計師之間的區別在於,專業程式設計師瞭解,明確就是王道。專業的程式設計師善用其能,能編寫出其他人能理解的程式碼。

類名儘量用名詞

類名儘量用名詞或名詞短語,比如Customer, WikiPage,Account, 或 AddressParser。

類名最好不要是動詞。

方法名儘量用動詞

方法名儘量用動詞或動詞短語。比如postPayment, deletePage, 或者save。

屬性訪問器、修改器和斷言應該根據其value來命名,並根據標準加上get、set和is字首。

舉個栗子,這裡的getName、setName等命名都很OK:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. string name = employee.getName();  
  2. customer.setName("mike");  
  3. if (paycheck.isPosted())...  
string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())...

而過載構造器時,使用描述了引數的靜態工廠方法名。如:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. Complex fulcrumPoint =Complex.FromRealNumber(666.0);  
Complex fulcrumPoint =Complex.FromRealNumber(666.0);

通常好於:

[cpp]view plaincopy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. Complex fulcrumPoint = new Complex(666.0);  
Complex fulcrumPoint = new Complex(666.0);

我們也可以考慮將相應的構造器設定為private,強制使用這種命名手段。

每個概念對應一詞,並一以貫之

我們需給每個概念選一個詞,並且一以貫之。

例如,使用fetch、retrieve和get來給在多個類中的同種方法命名,你怎麼記得住哪個類中是哪個方法呢?

同樣,在同一堆程式碼中混用controller、manager,driver,就會令人困惑。DeviceManager和Protocol-Controller之間有何根本區別?為什麼不全用controller或者manager?他們都是Driver嗎?這就會讓讀者以為這兩個物件是不同的型別,也分屬不同的類。

所以,對於那些會用到你程式碼的程式設計師,一以貫之的命名法簡直就是天降福音。

10 通俗易懂

我們應盡力寫出易於理解的變數名,把程式碼寫得讓別人能一目瞭然,而不必讓人去非常費力地去揣摩其含義。我們想要那種大眾化的作者盡責地寫清楚的通俗易懂的暢銷書風格,而不是那種學者學院風的晦澀論文寫作風格。

11 盡情使用解決方案領域專業術語

記住,只有程式設計師才會讀你寫的程式碼。所以,儘管去用那些電腦科學(Computer Science,CS)領域的專業術語、演算法名、模式名、數學術語。

對於熟悉訪問者(Visitor)模式的程式來說,名稱AccountVisitor富有意義。給技術性的事物取個恰如其分的技術性名稱,通常就是最靠譜的做法。

12 新增有意義的語境

很少有名稱是可以自我說明的。所以,我們需要用有良好命名的類,函式或名稱空間來放置名稱,給讀者提供語境。若沒能提供放置的地方,還可以給名稱新增字首。

舉個栗子,假如我們有名為firstName、lastName、street、houseNumber、city、state和zipcode的變數。當他們擱一塊兒的時候,的確是構成了一個地址。不過,假如只是在某個方法中看到一個孤零零的state呢?我們會推斷這個變數是地址的一部分嗎?

我們可以新增字首addrFirstName、addrLastName、addrState等,以此提供語境。至少,讀者可以知道這些變數是某個更大變數的一部分。當然,更好的方案是建立名為Address的類。這樣,即便是編譯器也會知道這些變數是隸屬於某個更大的概念了。

另外,只要短名稱足夠好,對含義的表達足夠清除,就要比長名稱更合適。新增有意義的語境甚好,別給名稱新增不必要的語境。

四、小結

其實,取一個好名字最難的地方在於需要良好的描述技巧和共有的文化背景。與其說這是一種技術、商業或管理問題,還不如說這是一種教學問題。

不妨試試上面列出的這十二條規則與要點,看看你的程式碼可讀性是否有所提升。而如果你是在維護別人的程式碼,或者是在重構,效果應該會是立竿見影的。

五、本文涉及知識點提煉整理

文章開頭部分已經用思維導圖的方式展現了本文的知識點,這邊再貼出一個文字列表版,方便大家整理:

要點一:要名副其實。一個好的變數、函式或類的名稱應該已經答覆了所有的大問題。一個好名稱可以大概告訴你這個名稱所代表的內容,為什麼會存在,做了什麼事情,應該如何用等。

要點二:要避免誤導。我們應該避免留下隱藏程式碼本意的錯誤線索,也應該避免使用與本意相悖的詞。

要點三:儘量做有意義的區分。儘量避免使用數字系列命名(a1、a2…….aN)和沒有意義的區分。

要點四:儘量使用讀得出來的名稱。如名稱讀不出來,討論的時候會不方便且很尷尬。

要點五:儘量使用可搜尋的名稱。名稱長短應與其作用域大小相對應,若變數或常量可能在程式碼中多處使用,應賦予其以便於搜尋的名稱。

要點六:取名不要繞彎子。取名要直白,要直截了當,明確就是王道。

要點七:類名儘量用名詞。類名儘量用名詞或名詞短語,最好不要是動詞。

要點八:方法名儘量用動詞。方法名儘量用動詞或動詞短語。

要點九:每個概念對應一詞,並一以貫之。對於那些會用到你程式碼的程式設計師,一以貫之的命名法簡直就是天降福音。

要點十:通俗易懂。應盡力寫出易於理解的變數名,要把程式碼寫得讓別人能一目瞭然,而不必讓人去非常費力地去揣摩其含義。

要點十一:盡情使用解決方案領域專業術語。儘管去用那些電腦科學領域的專業術語、演算法名、模式名、數學術語。

要點十二:要新增有意義的語境。需要用有良好命名的類,函式或名稱空間來放置名稱,給讀者提供語境。若沒能提供放置的地方,還可以給名稱新增字首。