編程中最難的就是命名?這幾招教你快速上手

你可不能像給狗狗取名字那樣給類、方法、變量命名。僅僅因為它很可愛或者聽上去不錯。
在寫代碼的時候,你要經(jīng)常想著,那個最終維護你代碼的人可能將是一個有暴力傾向的瘋子,并且他還知道你住在哪里。?
一、為什么命名很重要?
在項目中,從項目的創(chuàng)建到方法的實現(xiàn),每一步都以命名為起點,我們需要給變量、方法、參數(shù)、類命名,這些名字出現(xiàn)在代碼的每個角落,隨處可見,混亂或錯誤的命名不僅讓我們對代碼難以理解,更糟糕的是,會誤導(dǎo)我們的思維,導(dǎo)致對代碼的理解完全錯誤。如果整個項目始終貫穿著好的命名,就能給閱讀者一個神清氣爽的開始,也能給閱讀者一個好的指引。
要知道,代碼的閱讀次數(shù)遠遠多于編寫的次數(shù)。請確保你所取的名字更側(cè)重于閱讀方便而不是編寫方便。
二、為什么很難正確命名?
有人稱編程中最難的事情就是命名。我同樣深以為然,中國有句古話叫做萬事開頭難。拋開環(huán)境搭建,真正到了編碼階段第一件事就是命名,而最常見的一種情況,就是毫無目的、僅憑個人的喜好的去決定了一個名字。但因為沒有想清楚目標和具體實施步驟,所以進行過程中往往會面臨無數(shù)次的小重構(gòu)甚至是推倒重來。
1、缺乏意愿
害怕在選擇名字上花時間,對做好命名的意愿不足,隨心所欲,甚至無視團隊對命名的基本規(guī)范,覺得編譯器能編譯通過,代碼能正常運行就成。其實對發(fā)現(xiàn)的命名問題進行重構(gòu)和推倒重來并不可怕,最可怕的是當下程序員不具備發(fā)現(xiàn)問題后肯回過頭來糾偏的意愿。這終將演變成為一場災(zāi)難。
2、缺乏思考
沒想清楚被命名的事物是什么,事物應(yīng)該承擔什么職責,是否會對其他人造成誤解。新手程序員總會花很多時間學(xué)習一門編程語言、代碼語法、技術(shù)和工具。他們覺得如果掌握了這些東西,就能成為一個好程序員。然而事實并不是這樣,事實上,編程不僅僅關(guān)乎掌握技能和工具,更重要的是在特定范疇內(nèi)解決問題的能力,還有和其他程序員合作的能力。因此,能在代碼中準確的表達自己的想法就變得異常重要,代碼中最直觀的表達方式是命名,其次是注釋。
3、缺乏技巧
選一個好的名字真很難,你可能得有較高的描述能力和共同的文化背景。并且知曉一些常見且應(yīng)該避免的命名問題。如果最終還是沒法找到合適的名字,還請?zhí)砑訙蚀_的注釋輔助他人理解,等想到合適的名字后再進行替換,不過往往能夠通過注釋(母語)描述清楚的事物,命名應(yīng)該問題不大,問題大的是連注釋都無法準確表達,那說明可能當前類、函數(shù)、變量承擔的職責太多太雜。
三、如何正確的命名?
這里不討論具體語言的命名規(guī)則,原因是不同編程語言命名規(guī)則各不相同,甚至不同團隊間相同語言的命名規(guī)則也有出入。這里主要從提高可讀性出發(fā),結(jié)合我所在的客戶端團隊日常開發(fā)情況,以Java作為演示語言,給一些關(guān)于命名的建議。
1、名副其實
無論是變量、方法、或者類,在看到他名稱的時候應(yīng)該以及答復(fù)了所有的大問題,它應(yīng)該告訴你,它為什么會存在,他做什么事,應(yīng)該怎么做。如果在看到名稱時,還需要去查找注釋來確認自己的理解,那就不算名副其實。而且在發(fā)現(xiàn)有更好的命名時,記得果斷替換。
Case1:到底怎樣算End?
代碼示例:
大腦活動:onRequestEnd是請求的什么階段?請求成功和失敗任一情況都算 “end”嗎?喔,原來注釋有寫:“只有成功點才認為是真正的結(jié)束”。
修改建議:
2、避免誤導(dǎo)
在每種語言中都有內(nèi)置的標識符,他們都有特定的含義,如果和他們沒有關(guān)聯(lián)就不要在命名中加上他們。
2.1 避免使用令人誤解的名字
Case1:命錯名的集合
代碼示例:
大腦活動:
“dataSet” 在最初一定是為了元素去重選擇了Set類型,肯定后來某一個歷史時刻發(fā)現(xiàn)有bug被偷偷改成了List類型,但是變量名沒變。
代碼跟讀:
跟蹤提交記錄,呃,在18年被剛定義時就是 List<***> dataSet;
修改建議:
Case2:不是View的View類
代碼示例:
大腦活動:
“N”是啥意思?類名只有一個N的字母差別,難道是新舊的差別,新的和舊的有什么區(qū)別呢?
類名以View結(jié)尾,嗯,應(yīng)該是一個視圖,可是,視圖為啥不用繼承視圖基類的?
代碼跟讀:
喔,N確實代表“New”的意思,NRItemOverlayView被首頁推薦使用,RItemOverlayView被購后推薦使用。
這個類主要核心工作是構(gòu)建浮層視圖(職責并不單一),而類本身并不是一個真正的視圖;
修改建議:
Case3:整形變量為啥要用is開頭
代碼示例:
大腦活動:
為什么“is”開頭的變量卻聲明成整形?到底是要計數(shù)還是判斷真假呢?
代碼跟讀:
isFirstEnter < 1 做第一次進入的邏輯
修改建議:
Case4:開關(guān)作用反掉啦
代碼示例:
大腦活動:
為什么開關(guān)名為“delay....”為“true”的時候,走的不是delay邏輯,那開關(guān)要怎么發(fā)?容我多看幾遍,是不是最近沒休息好所以看岔了。
代碼跟讀:
反復(fù)看了幾遍,確實是開關(guān)命名和實際操作完全相反,開關(guān)名意為“延遲隱藏封面視圖”,執(zhí)行的卻是“立即隱藏封面視圖”。
修改建議:
3、做有意義的區(qū)分
如果單純只是為了區(qū)分兩個名稱不能一樣,就使用就使用諸如數(shù)字,字母來做區(qū)分的話,那似乎是毫無意義的區(qū)分。
3.1 避免在名字中使用數(shù)字
case1: 來自包名的暴擊
問題示例:
以下是首頁客戶端的工程目錄節(jié)選,數(shù)字化的包名:recommend、recommend2、recommend3、recommend4

?大腦活動:
2、3、4難道是因為首頁歷史包袱太沉重,推薦迭代的版本實在太多導(dǎo)致Old、New單詞不夠用所以用數(shù)字來代替新舊4個歷史階段的版本嗎?
代碼跟讀:
recommend:推薦的公共工具和模塊;
recommend2:收藏夾場景的推薦實現(xiàn);
recommend3:首頁場景的推薦實現(xiàn);
recommend4:購后場景的推薦實現(xiàn);
修改建議:
這里暫時只討論如何把數(shù)字替換成有意義的命名

3.2 避免使用具有相似含義的名字
case1:同一個類下的“刷新7劍客”
代碼示例:

大腦活動:
為什么一個Adapter類對外有七個刷新數(shù)據(jù)的接口?
"refreshData()" 和 “speedRefreshData()” 是什么區(qū)別?
“mainRefreshData()” + "refreshDeltaData()" =“mainRefreshDeltaData()” ?是一個拆分組合的關(guān)系嗎?
我應(yīng)該在何總場景下如何正確的使用refresh,我在哪,我在做什么?
代碼跟讀:
大部分refresh代碼線上并不會被調(diào)用。閱讀和調(diào)試下來,實際還在生效的方法只有一個:“gatewayRefreshData()”。
修改建議:
實際上這已經(jīng)不是一個單純優(yōu)化命名可以解決的問題,無論叫的多具體,面對7個刷新接口都會懵圈。期望在方法聲明期間,作者多體量后來的閱讀者和維護者,及時的調(diào)整代碼。
后來者可以從實際出發(fā)去假存真,做減法干掉其它無用的6個刷新方法保留一個刷新接口。
case2:4個數(shù)據(jù)源定義,該用誰呢
代碼示例:
聲明1:
聲明2:
聲明3:
聲明4:
大腦活動:
4個推薦數(shù)據(jù)源,其中有3個是接口聲明,為什么接口定義了不能多態(tài),不能復(fù)用接口的聲明?這三代的抽象好像有一丟丟失敗。
代碼跟讀:
homepage 包下的 IR4UDataSource,和非常古老的首頁曾經(jīng)愛過,線上實際不會使用;Recommend2 包下的“RecommendIDataSource” 屬于收藏夾,但也屬于古老版本,收藏夾不在使用;
Recommend3 包下的“IRecommendDataResource” 確實是首頁場景推薦使用,但也是曾經(jīng)的舊愛;
原來當今的真命天子是Recommend3包下的“RecmdDataSource”,一個使用俏皮縮寫未繼承接口的實體類,看來是已經(jīng)放棄偽裝。
修改建議:
......
3.3 避免使用具有不同含義但卻有相似名字的變量
case1 : 大家都是view,到底誰是誰
代碼示例:
代碼跟讀:
代碼中存在3個以view結(jié)尾的局部變量,rootView、view 、 dView,其中 view 和 dView 之間只有一個字母的差異,方法如果長一點,view 和 dView 使用頻率在高一點,摻雜著rootView會讓人抓狂。另外dView也并不是一個view,實際是個DXViewWidget。
修改建議:
4.使用讀的出來的名稱
使用讀的出來的名稱,而不是自造詞,這會給你無論是記憶,還是討論需要說明是哪個方法時,都能帶來便利??梢允褂眠_成共識的縮寫,避免造成閱讀障礙。
4.1 避免使用令人費解的縮寫
Case1:接口定義中的俏皮縮寫
代碼示例:
大腦活動:
R4U是什么? R4和Recommend4這個目錄有什么關(guān)系,難道是購后推薦的數(shù)據(jù)源定義嗎?那U又代表什么?
代碼跟讀:原來R4U是Recommend For You的俏皮寫法
修改建議:
Case2:成員變量命名的縮寫
代碼示例:
大腦活動:
“mTabLL”是什么呢?有注釋!難道m(xù)TabLL是指示器視圖?“LL“”也不像是indicators的縮寫,喔,LL是LinearLayout的首字母縮寫。嗯,使用LinearLayout自定義做成指示器有點厲害!誒,不對,好像TabLayout更像是個選項卡式指示器的樣子。
代碼跟讀:
原來“mTabLL” 下面聲明的 “mTabLayout”才是指示器視圖,“mTabLL”只是指示器視圖的父視圖。還好“mTabLayout”沒有縮寫成“mTabL”,導(dǎo)致和“mTabLL”傻傻分不清,作者已然是手下留情了。
修改建議:
Case3:局部變量命名的縮寫
代碼示例:
大腦活動:
"ss"是什么鬼,是不是寫錯了,GroupBuckets首字母縮寫是“gb”,PageParams和GroupBuckets 的首字母縮寫是“pg”
這難道是,PageParams 和 GroupBuckets 的尾字母縮寫,在一個圈復(fù)雜度為18的方法中看到尾字母縮寫“ss”???!好難受。
修改建議:
5、使用可搜索的名稱
若變量或常量可能在代碼中多處使用,則應(yīng)賦其以便于搜索的名稱。
5.1 給魔法值賜名
Case1: 數(shù)字魔法值沒法搜索也看不懂
代碼示例:
大腦活動:
對于TextUtils.equals(isFestivalOn, "1") ,我還能猜測一下這里的“1” 代表開關(guān)為開的意思。那TextUtils.equals(navStyle, "0"/"1"/"2") 中的“0”,“1”,“2” 我該如何知道代表什么意思?老板,請不要再問我為什么需求吞吐率不高,做需求慢了,可能是因為我的想象力不夠。
修改建議:
實際上,協(xié)議約定時就不應(yīng)該以 “0”,“1”,“2” 這類無意義的數(shù)字做區(qū)分聲明。
5.2 避免在名字中拼錯單詞
Case1:接口拼錯單詞,實現(xiàn)類也被迫保持隊形
代碼示例:

修改建議:
6、類的命名
應(yīng)該總是名詞在最后面,名詞決定了這個類代表什么,前面的部分都是用于修飾這個名詞;比如,假如現(xiàn)在你有一個服務(wù),然后又是一 個關(guān)于訂單的服務(wù),那就可以命名為OrderService,這樣命名就是告訴我們這是一個服務(wù),然后是一個訂單服務(wù);再比如 CancelOrderCommand,看到這個我們就知道這是一個Command,即命令,然后是什么命令呢?就是一個取消訂單的命令,CancelOrder表示取消訂單。
類的命名可以參考前面講述過的規(guī)則。實際上往往了解一個類更多需要通過查看類的方法定義,而僅僅通過類名無法知曉類是如何工作的。關(guān)于類的更多內(nèi)容,會在后續(xù)章節(jié)詳細展開。
7、方法的命名
可以用一個較強的動詞帶目標的形式。一個方法往往是對某一目標進行操作,名字應(yīng)該反映出這個操作過程是干什么的,而對某一目標進行操作則意味著我們應(yīng)該使用動賓詞組。比如:addOrder()。當方法有返回值的時候,方法應(yīng)該用它所返回的值命名,比如currentPenColor()。
《代碼大全》:變量名稱的最佳長度是 9 到 15 個字母,方法往往比變量要復(fù)雜,因而其名字也要長些。有學(xué)者認為恰當?shù)拈L度是 20 到 35 個字母。但是,一般來說 15 到 20 個字母可能更現(xiàn)實一些,不過有些名稱可能有時要比它長。
7.1 避免對方法使用無意義或者模棱兩可的動詞
避免無意義或者模棱兩可的動詞 。有些動詞很靈活,可以有任何意義,比如 HandleCalculation(),processInput()等方法并沒有告訴你它是作什么的。這些名字最多告訴你,它們正在進行一些與計算或輸入等有關(guān)的處理。
所用的動詞意義模糊是由于方法本身要做的工作太模糊。方法存在著功能不清的缺陷,其名字模糊只不過是個標志而已。如果是這種情況,最好的解決辦法是重新構(gòu)造這個方法,弄清它們的功能,從而使它們有一個清楚的、精確描述其功能的名字。
Case1: 名不副實的process
代碼示例:
大腦活動:
1、方法名的字面意思是處理主圖(暫不糾結(jié)縮寫Pic了),但是是如何處理主圖的呢?
2、返回值是bool類型,是表示處理成功或失敗嗎?
3、查看注釋解釋,當前方法是在處理主圖的數(shù)據(jù),返回為是否存在浮層數(shù)據(jù),為什么一個處理主圖數(shù)據(jù)的方法檢查的是浮層數(shù)據(jù)呢?
看完發(fā)現(xiàn),這個方法原來是拿主圖數(shù)據(jù)檢查其中是否存在浮層數(shù)據(jù),名不副實呀。
修改建議:
額外說明:既然工程默認“Float”是浮層,這里不做額外修改,但實際上不合理,畢竟Float在Java中表示浮點型數(shù)據(jù)類型,會引起誤解。
Case2: 我該如何正確使用這個方法
代碼示例:
大腦活動:
好多地方調(diào)用工具類的processTime,processTime到底是在處理些什么呢?
如果入?yún)魅氲牟皇?System.currentTimeMillis() 而是 SystemClock.uptimeMillis() 或者隨意傳入一個long值,方法的返回值會是什么呢?
修改建議:
7.2 避免返回和方法名定義不一致的類型
Case1: 私有方法就可以亂定義嗎?
碼示例:
大腦活動:
check方法如果有返回值的話不應(yīng)該是bool類型嗎?
“Avaliable”拼錯了誒,正確的單詞拼寫是:“Available”。
“IPageProvider” 和 “ActivityAvaliable” 是什么關(guān)系,為什么校驗可用的Activity返回的是“IPageProvider”。
代碼跟讀:
原來方法里面偷偷做了一個銷毀“PopCenter”的動作。把獲取“PageProvider”和銷毀“PopCenter”兩件事情放在了一起。確實沒看懂方法名和方法所做任何一件事情有什么關(guān)系。
修改建議:
干掉checkActivityAvaliable()方法。(這里不展開討論高質(zhì)量的函數(shù)相關(guān)內(nèi)容)
四、養(yǎng)成良好的命名習慣一些建議
1.對自己嚴格自律,自己寫代碼時要有一種希望把每個名稱都命名好的強烈意識和嚴格的自律意識;
2.要努力分析和思考當前被你命名的事物或邏輯的本質(zhì);這點非常關(guān)鍵,思考不深入,就會導(dǎo)致最后對這個事物的命名錯誤,因為你還沒想清楚被你命名的事物是個什么東西;
3.你的任何一個屬性的名字都要和其實際所代表的含義一致;你的任何一個方法所做的事情都要和該方法的名字的含義一致;
4.要讓你的程序的每個相似的地方的命名風格總是一致的。不要一會兒大寫,一會兒小寫;一會兒全稱一會兒簡寫;一會兒帕斯卡(Pascal)命名法,一會兒駱駝(Camel)命名法或匈牙利命名法。
