如何給變量取個簡短且無歧義的名字
灣區日報上分享的一篇文章,文章的作者在Google設計Dart語言,就變量命名方面給了4點建議,文中也列出了好變量名、壞變量名的對比。不管作者的看法與你實際中的命名習慣是否一致,看完這篇文章,相信可以在變量命名方面有一些新的思考。
英文原文:Long Names Are Long
譯/Giraffe
Google做的最明智的規定之一就是嚴格執行code review。每一個改動在上線之前,都要經過兩種形式的review。首先,團隊中的人會進行常規的review,以確保代碼完成了它應該完成的功能。
接下來還會進行可讀性層面的review。顧名思義,它是為了確保代碼是可讀性高的:是否利于理解和維護?是否符合該編程語言的一些慣例?是否有良好的文檔?
Dart已經開始Google內部使用,所以我有幸參與了n次上面類型的code review。作為該語言的設計者,這是一項令人著迷的工作。我可以直接看到人們是如何使用Dart的,這對語言的進一步發展很有幫助。在reivew的同時,我也能夠清晰的了解到那些比較常見的錯誤和使用最多的特性,我就好像是一個記錄本地居民生活的人類學者。
當然,上面說的與本文的主旨無關,這并不是一篇關于Dart的文章。本文主要是想討論我看到過的一些令人抓狂的代碼:這些代碼的變量命名實在是太尼瑪的長了。。。
是的,變量的名稱可以很短。回到當C語言中外部標識符僅需要由前六個字符來唯一的區分;自動補全功能還沒有發明;每次按鍵盤都像在雪地上坡一樣艱難的時候,長的命名就會帶來很多問題。不過幸運的是,我們現在生活的世界太美好了,鍵盤操作變得如此簡單。
但我們現在似乎走上了另一個極端,我們不應該做海明威,但我們也無需成為田納西·威廉斯。代碼中使用了超長的命名會影響代碼的清晰性。同時,超長的變量命名會造成換行,這會影響代碼的結構,不易于閱讀。
- 長的類名會使開發者不易聲明該類型的變量。
- 長的方法命名會使它變得晦澀難懂.
- 長的變量命名不利于代碼重用,導致過長的方法鏈。
我曾見過超過60個字符的變量命名,你甚至可以寫首詩。別慌,下面我們來看看如何解決這一問題。
選擇一個好的命名
命名有兩個目標:
- 清晰:你要知道該命名與什么有關
- 精確:你要知道該命名與什么無關
當一個命名完成上面兩個目標之后,其余的字符就是多余的了。下面是我在開發時的一些命名原則:
命名中無需含有表示變量或參數類型的單詞
如果使用如Java之類的靜態類型語言,開發者通常知道變量的類型。由于方法的實現一般都比較簡短,所以即便是在查看一個需要推斷才知道類型的本地變量,或者在code review等靜態分析器不可用的情況下,我們也可以通過多看很少的幾行代碼就能知道變量的類型。
所以將類型說明加入到變量名中是多余的。我們應該舍棄匈牙利命名法,如下:
// 不好的: String nameString; DockableModelessWindow dockableModelessWindow; // 改進: String name; DockableModelessWindow window;
特別是對于集合來說,最好使用名詞的復數形式來描述其內容,而不是使用名詞的單數形式來描述。如果開發者更在乎集合中存儲的內容, 那么變量命名應當反映這一點。
// 不好的: List<DateTime> holidayDateList; Map<Employee, Role> employeeRoleHashMap; // 改進: List<DateTime> holidays; Map<Employee, Role> employeeRoles;
這一點也同樣適用于方法的命名。方法名不需要描述它的參數及參數的類型——參數列表已經說明了這些。
// 不好的: mergeTableCells(List<TableCell> cells) sortEventsUsingComparator(List<Event> events, Comparator<Event> comparator) // 改進: merge(List<TableCell> cells) sort(List<Event> events, Comparator<Event> comparator)
省略命名中不是用來消除歧義的單詞
有些開發者傾向于將他們知道的有關這個變量的所有信息都塞到命名里。要記住,命名只是一個標識符:只是告訴你該變量是在哪定義的。并不是用來告訴閱讀者所有他們想知道的有關這個對象的詳細信息。這是定義應該做的事情的。 命名只是讓你找到它的定義。
當我看到一個叫recentlyUpdatedAnnualSalesBid
(最近更新的全年銷售投標)的標識符時,我會問:
- 存在不是最近更新的全年銷售投標么?
- 存在沒有被更新的最近的全年銷售投標么?
- 存在最近更新的非全年的銷售投標么?
- 存在最近更新的全年非銷售的投標么?
- 存在最近更新的全年銷售非投標的東東嗎?
上面任何一個問題的回答是“不存在”,就意味著命名中引入了無用的單詞。
// 不好的: finalBattleMostDangerousBossMonster; weaklingFirstEncounterMonster; // 改進: boss; firstMonster;
當然,你可能會覺得這有一些過了。比如將第一個例子的標識符簡化為bid,
會讓人覺得有點模糊不清。但你可以放心大膽的這樣做,如果在之后的開發中覺得該命名會造成沖突或不明確,可以添加些修飾詞來完善它。反之,如果一開始就取了一個很長的命名,你是不可能在之后重新回來簡化它的。
省略命名中可以從上下文獲取的單詞
我可以在文章中使用”我”,因為讀者都知道這是一篇由Bob Nystrom所做的博客。我蠢萌的臉就掛在那,我無需不停的說我的名字。寫代碼也是一樣,類中的方法/屬性和方法中的變量,都是存在在上下文中的,無需重復。
// Bad: class AnnualHolidaySale { int _annualSaleRebate; void promoteHolidaySale() { ... } } // Better: class AnnualHolidaySale { int _rebate; void promote() { ... } }
實際上,一個命名嵌套的層次越多, 它就有更多的相關的上下文,也就更簡短。換句話說,一個變量的作用域越小,命名就越短。
省略命名中無任何含義的單詞
我常常在許多游戲開發中看到包含無任何含義的單詞的命名,一些開發者喜歡在命名中添加一些看起來有點嚴肅的單詞。我猜可能他們覺得這樣做可以讓他們的代碼顯得重要,或者說讓他們覺得自己更重要。
實際上,有一些詞語并沒有實際意義,只是一些套話。比如:data, state, amount, value, manager, engine, object, entity和instance。
一個好的命名能夠在閱讀者的腦海中描畫出一幅圖畫。而將某變量命名為”manager”并不能向讀者傳達任何有關該變量是做什么的信息。它是用來做績效評估的嗎? 它是管理加薪的嗎?
在命名時可以問一下自己,把這個單詞去掉含義是不是不變?如果是,那就果斷把它剔除吧~~
實際例子——華夫餅
為了讓大家了解以上的命名原則在實際中如何應用,這有個違法了以上所有原則的反例。這個例子和我實際上review過的一段代碼一樣令人心碎。。。。
// 好吃的比利時華夫餅 class DeliciousBelgianWaffleObject { void garnishDeliciousBelgianWaffleWithStrawberryList( List<Strawberry> strawberryList) { ... } }
首先,通過參數列表我們可以知道方法是用來處理一個strawberry的列表,所以可以在方法命名中去掉:
class DeliciousBelgianWaffleObject { void garnishDeliciousBelgianWaffle( List<Strawberry> strawberries) { ... } }
除非程序中還包含不好吃的比利時華夫餅或者其他國家的華夫餅,不然我們可以將這些無用的形容詞去掉:
class WaffleObject { void garnishWaffle(List<Strawberry> strawberries) { ... } }
方法是包含在WaffleObject
類中的,所以方法名中無需Waffle的說明:
class WaffleObject { void garnish(List<Strawberry> strawberries) { ... } }
很明顯它是一個對象,任何事物都是一個對象,這也就是傳說中的“面向對象”的含義,所以命名中無需帶有Object
:
class Waffle { void garnish(List<Strawberry> strawberries) { ... } }
好了,這樣看起來好多了。
以上就是我總結的相當簡潔的命名原則。可能有些人會覺得無需在命名上耗費太多的精力,但我認為命名是開發過程中最基本的任務之一。
————————————————–我是萌萌噠分界線————————————————
感覺變量或者方法的命名,看似簡單,實際很難,特別是想一個簡潔明了、可讀性高的命名。自己也經常用什么data
, xxxlist
來命名,作者說的挺對的,前者沒什么意義,后者又有點啰嗦。不過對于集合類型的變量,統一用名詞復數命名容易混淆。舉個例子對于Apple這個類來說,可能存在List和Map兩種集合類型的變量。個人覺得對List類型的變量可以采用名詞復數來命名,Map類型的變量可以采用valueByKey格式來命名,比較容易區分。