如何定義一個好的變量名
在日常編程中最煩惱的就是給變量取名,一段好代碼,既要能完美地運行,還要能較容易地維護。這就意味着需要讓日後維護代碼的人能很快地看懂你的代碼,而且,在團隊合作中,其他開發者也會經常閱讀你那部分代碼。如果你的代碼中充滿了a,b,c,a1,a2,a3…那簡直就是一個噩夢。因此,好的變量名非常重要。
選擇好變量名的注意事項
變量和變量名本質上是同一件事物,因此,變量的好與壞就在很大程度上取決於它的命名的好與壞。
下面舉一個糟糕命名的例子
1 2 3 4 | $pp = ($cp > 1) ? ($cp - 1) : $cp; $np = ($cp < $tp) ? ($cp + 1) : $tp; $p = new P($pp, $cp, $np, $tp); |
這段代碼在做什麼呢?也許可以大概知道是在計算一些信息,但是,計算的是什麼信息呢?$p,$pp,np,$tp等等這些變量代表的是什麼呢?講真,如果沒有任何註釋,任何人都無法看不懂這段代碼想表達的意思。如果寫這段代碼的人告訴你,這段代碼是在計算分頁信息,然後實例化一個分頁類,那麼你應該如何命名呢?
下面是這段代碼的另一種寫法,看起來更加清晰:
1
2
3
4
|
$prev_page_num=($curr_page_num>1)?($curr_page_num-1):$curr_page_num);
$next_page_num=($next_page_num<$total_page_num)?($curr_page_num+1):$total_page_num);
$page=newPage($prev_page_num,$curr_page_num,$next_page_num,$total_page_num);
|
從上面兩段代碼可以看出,一個好的變量名在可讀性和可維護性上是極其重要的。而且好的變量名是易記的。可以通過應用多條原則來實現這些目標。
最重要的命名注意事項
- 名字要完全、準確地描述出該變量所代表的事物
- 用名字表達變量所代表的是什麼,不包含晦澀的縮寫,同時也沒有歧義
下表給出一些變量名稱的例子,其中有好的也有差的。
變量用途 | 好名字,好描述 | 壞名字,差描述 |
---|---|---|
到期的支票累計額 | runningTotal,checkTotal | written,ct,checks,CHKTTL,x,x1,x2 |
高速列車的運行速度 | velocity,trainVelocity,velocityInMph | velt,v,tv,x,x1,x2,train |
當前日期 | currentDate,todaysDate | cd,current,c,x,x1,x2,date |
每頁的行數 | linesPerPage | lpp,lines,l,x,x1,x2 |
currentDate和todaysDate都是很好的名字,因爲它們都完全而且準確地描述出了“當前日期”這一概念。
cd和c是很糟糕的命名,因爲它們用了太短的縮寫,而且又不具有描述性。
current也很糟,因爲它並沒有告訴你是當前什麼。
date看上去不錯,但經過最後推敲它也只是個壞名字,因爲這裏所說的日期並不是所有的日期均可,而只是特指當前日期,而date本身並未表達出這層含義。
x,x1和x2永遠都是壞名字–傳統上用x代表一個未知量,如果不希望你的變量所代表的是一個未知量,那麼請考慮取一個更好的名字吧。
名字應該儘可能地明確。像x、temp、i這些名字都泛泛可得可以用於多種目的,它們並沒有像應該的那樣提供足夠信息,因此通常是命名上的敗筆。
以問題爲導向
一個好記的名字反映的通常都是問題,而不是解決方案。即,一個好名字通常表達的是什麼(What),而不是怎麼樣(How)。通常來說,如果一個名字反映了計算機的某些方面而不是問題本身,那麼它反映的就是“How”而非“What”了。
比如,考慮下面這兩個變量命名:inputRec和employeeData。inputRec是一個反映輸入、記錄這些計算概念的計算機術語。employeeData則直指問題領域,與計算機無關。 類似地,printerReady比bitFlag更能表達打印機的狀態;在財務軟件裏,calcVar比sum來得更準確。
最適當的名字長度
經研究發現,變量名的平均長度在10到16個字符的時候,調試花的力氣是最小的。平均名字長度在8到20隔字符的程序也幾乎同樣容易調試。這並不意味着你的變量名一定要在8到20個字符,它強調的是,如果你查看自己寫的代碼時發現了很多更短的名字,那麼你就需要認真檢查,確保這些名字含義足夠清晰。
下面展示變量名太長、太短或剛好的示例:
太長 : numberOfPeopleOnTheUsOlympicTeam; numberOfSeatsInTheStadium; maximumNumberOfPointsInModernOlympics
太短 : n, np, ntm; n, ms, nsisd; m, mp, max, points
正好 : numTeamMembers, teamMemberCount; numSeatsInStadium, seatCount; teamPointsMax, pointsRecord
變量名中的計算值限定詞
很多程序都有表示計算結果的變量:總額、平均值、最大值,等等。如果你要用類似Total、Sum、Average、Max、Min、Record、String、Pointer這樣的限定詞來修改某個名字,那麼請記住把限定詞加到名字的最後。
這種方法的優點:
- 變量名中最重要的那部分,即爲這一變量賦予主要含義的部分應當位於最前面,這樣,這一部分就可以顯得最爲突出,並會被首先閱讀到;
- 避免了由於同時在程序中使用totalRevenue和revenueTotal而產生的歧義
- 使用統一的編碼規範可以提高可讀性,簡化維護工作。比如,revenueTotal、expenseTotal、revenueAverage、expenseAverage這組名字具有非常優雅的對稱性。而totalRevenue、expenseTotal、revenueAverage、averageRevenue這組名字中則看不出什麼規律來。
這條規則也有例外,那就是Num的限定詞的位置已經是約定俗成的。Num放在變量名的開始位置代表一個總數,比如:numCustomers表示員工的總數。Num放在變量名的結束位置代表一個下標:customerNum表示的是當前員工的序號。這樣使用Num常常會帶來麻煩,因此,最好的方法是避開這些問題,使用Count或者Total來代表總數,使用Index來指代某個特定的員工。這樣,customerCount就代表員工的總數,customerIndex代表某個特定的員工。
變量名中的常用對仗詞
對仗詞要使用正確,不然會產生歧義。
常用對仗詞如下:
- begin/end
- first/last
- locked/unlocked
- min/max
- next/previous
- old/new
- opened/closed
- visible/invisible
- source/target
- source/destination
- up/down
爲特定類型的數據命名
爲變量命名,除了通常的考慮事項之外,爲一些特定類型數據的命名還要求作出一些特殊的考慮。比如,循環變量、狀態變量、臨時變量等等。
爲循環下標命名
在循環中,最常見的下標變量就是i,j,k,如:
1 2 3 | for(i = 0; i < arrLen; i++) { // ... } |
如果循環下標變量只在循環內部使用,那麼如此使用是沒問題的,但是,如果該變量需要在循環之外使用,那麼就應該爲它取一個比i,j,k更有意義的名字。舉個栗子,如果你從文件中讀取記錄,並且需要記下所讀取記錄的數量,那麼類似於redcordCount這樣的名字就更合適:
1
2
3
4
5
6
7
|
recordCount=0;
while(moreScores()){
score[recordCount]=GetNextScore();
recordCount++;
}
// using recordCount
|
另一種情況就是嵌套循環,比較常犯的錯誤就是在想寫j的時候寫了i,想用i的時候卻寫了j。
如果你使用了多個嵌套的循環,那麼就應該給循環變量賦予更長的名字以提高可讀性:
1 2 3 4 5 | for ( teamIndex = 0; teamIndex < teamCount; teamIndex++) { for ( eventIndex = 0; eventIndex < eventCount[teamIndex]; eventIndex++) { score[teamIndex][eventIndex] = 0; } } |
score[teamIndex][eventIndex] 比 score[i][j]給出的信息更多。
注意:如果你一定要用i、j、k,那麼不要把它們用於簡單循環的循環下標之外的任何場合,避免造成誤解。要想避免這種問題,最簡單的方法就是使用更好的命名而不是i,j,k。
爲狀態變量命名
爲狀態變量取一個比flag更好的名字。
最好是把標記看作是狀態變量。標記的名字中不應該含有flag,因爲你從中絲毫看不出該標記是做什麼的。
爲清楚可見,標記應該使用枚舉變量、具名常量,或用作具名常量的全局變量來對其賦值。
看看下面比較差的標記命名:
1
2
3
4
5
6
7
8
9
|
if(flag)...
if(statusFlag&0x0F)...
if(printFlag==16)...
if(computeFlag==0)...
flag=0x1;
statusFlag=0x80;
printFlag=16;
computeFlag=0;
|
上面這段代碼反映不出能做什麼,如果沒有文檔,不知道statusFlag = 0x80的含義是什麼。下面是作用相同但更爲清晰的代碼:
1 2 3 4 5 6 7 8 9 | if ( dataReady ) ... if ( characterType & PRINTABLE_CHAR ) ... if ( reportType == ReportTyoe_Annual ) ... if ( recalcNeeded == false ) ... dataReady = true; characterType = CONTRAL_CHARACTER; reportType = ReportType_Annual; recalNeeded = false; |
這段代碼更加清晰。而且說明你可以結合枚舉類型和預定義的具名常量來使用這種方法。
如果你發現自己需要猜測某段代碼的含義的時候,就該考慮爲變量重新命名。代碼應該儘可能直接讀懂。
爲臨時變量命名
臨時變量常用於存儲計算的中間結果,作爲臨時佔位符,以及存儲內部值。它們常被賦予temp,tmp,x或者其他一些模糊且缺乏描述性的名字。通常,臨時變量是一個信號,表明程序緣還沒有完全把問題弄清楚。而且,由於這些變量被正式地賦予了一種“臨時”狀態,因此程序員會傾向於比其他變量更爲隨意地對待這些變量,從而增加了出錯的可能。
警惕臨時變量
臨時地保存一些變量是很有必要的。但無論從哪種角度看,程序中的大多數變量都是臨時性的。把其中幾個稱爲臨時的,可能表明你還沒有弄清它們的實際用途。看看下面的示例:
1
2
3
|
temp=sqrt(b^2-4*a*c);
root[0]=(-b+temp)/(2*a);
root[1]=(-b-temp)/(2*a);
|
更好的做法:
1 2 3 | discriminant = sqrt( b^2 - 4*a*c ); root[0] = ( -b + discriminant ) / ( 2*a ); root[1] = ( -b - discriminant ) / ( 2*a ); |
discriminant,判別式
爲布爾變量命名
典型的布爾變量名:
- done
- error
- found
- success/ok
給布爾變量賦予隱含“真/假”含義的名字。像done和success一樣,它們的值不是true就是false,表示某件事情完成了或者沒有完成;成功或者失敗。另一方面,想status這樣的名字卻是很糟的布爾變量名,因爲它們沒有明確的true或者false。status是true反映的是什麼含義呢?表示某件事情擁有一個狀態嗎?然而,每件事情都有狀態。true表明某件事情的狀態是OK嗎?或者說false表明沒有任何錯誤嗎?對於status,你什麼都說不出。
爲了更好的效果,可以把status命名爲error或者statusOK。
有時,也可以在布爾變量名前加上Is。這樣,變量名就成了一個問題:isDone?isError?isFound?用true或false回答問題也就爲該變量給出了取值。優點是不能用於那些模糊不清的名字,比如:isStatus?毫無意義。缺點就是降低了簡單邏輯表達式的可讀性:if(isFound)的可讀性要略差於if(Found)。
使用肯定的布爾變量名。避免雙重否定:not notFound。
爲枚舉類型命名
在使用枚舉類型的時候,可以通過使用組前綴,如Color_,Planet_或者Month_來明確標識該類型的成員都同屬於一個組。比如:
1
2
3
4
5
6
7
8
9
10
11
|
PublicEnumColor
Color_Red
Color_Green
Color_Blue
EndEnum
PublicEnumPlanet
Planet_Earth
Planet_Mars
Planet_Venus
EndEnum
|
在有些編程語言裏,枚舉類型的處理很像類,枚舉類型也總是被冠以枚舉名字前綴,比如Color.Color_Red或者Planet.Planet_Earth。如果你正在使用這樣的編程語言,那麼重複上述前綴的意義就不大了,可以簡化爲Color.Red和Planet.Earth。
爲常量命名
在具名常量時,應該根據該常量所表示的含義,而不是該常量所具有的數值爲該抽象事物命名。比如FIVE是個很糟糕的常量名,CYCLES_NEEDED是個不錯的名字。
命名規則的力量
很多程序員會抵制標準和約定(有時我也會這樣),並且有很好的理由:有些標準和約定非常刻板並且低效–它們會毀壞創造性和程序質量。
爲什麼要有規則
- 要求你更多地按規矩行事。集中精力關注代碼更重要的特徵;
- 有助於在項目之間傳遞知識;
- 有助於在新項目中更快速地學習代碼;
- 有助於減少名字增生,在沒有規則下,很容易給同一個對象起兩個不同的名字;
- 彌補編程語言的不足之處;
- 強調相關變量之間的關係。
關鍵是,採用任何一項規則都要好於沒有規則。規則可能是武斷的。命名規則的威力並非來源於你所採取的某個特定規則,而是來源於以下事實:規則的存在爲你的代碼增加了結構,減少了你需要考慮的事情。
何時採用命名規則
- 多個程序員合作開發一個項目時
- 計劃把一個程序轉交給另一位程序員來修改和維護的時候
- 你所在組織中的其他程序員評估你寫的程序的時候
- 當你寫的程序規模過大,以致於你無法在腦海裏同時瞭解事情的全貌,而必須分而治之的時候
- 你寫的程序生命期足夠長,長到你可能會在把它擱置幾個星期或幾個月之後又重新啓動有關該程序的工作時
- 當在一個項目中存在一些不常見的術語,並且你希望在編寫代碼階段使用標準的術語或縮寫的時候
非正式命名規則
儘管上面介紹了很多比較標準的命名規則,但是大多數項目採用的都是相對非正式的命名規則。
與語言無關的命名規則的指導原則
- 區分變量名和子程序名字
- 區分類和對象
- 標識全局變量
- 標識成員變量
- 標識類型聲明
- 標識具名常量
- 標識枚舉類型的元素
- 在不能保證輸入參數只讀的語言裏標識只讀參數
- 格式化命名以提高可讀性
儘量不要混用上述方法,那樣會使代碼更難閱讀。老老實實地堅持使用其中任意一種提高可讀性的方法,你的代碼質量一定會有所改善。
與語言相關的命名規則的指導原則
應該遵循你所用語言的命名規則。對於大多數語言,你都可以找到描述其風格原則的參考書,下面給出C的指導原則。
C的命名規則
- c和ch是字符變量
- i和j是整數下標
- n表示某物的數量
- p是指針
- s是字符串
- 預處理宏全部大寫,通常包括typedef
- 變量名和子程序名全部小寫
- 下劃線用作分隔符,如:letters_in_lowercase
標準前綴
對具有通用含義的前綴標準化,爲數據命名提供了一種簡潔、一致並且可讀性好的方法。
標準化的前綴由兩部分組成:用戶自定義類型(UDT)的縮寫和語義前綴。
用戶自定義類型縮寫
UDT縮寫可以標識被命名對象或變量的數據類型。UDT縮寫通常不會表示任何由編程語言所提供的預置數據類型。下面列出一份UDT示例。
UDT縮寫 | 含義 |
---|---|
ch | 字符(Character) |
doc | 文檔(Document) |
pa | 段落(Paragraph) |
scr | 屏幕區域(Screen region) |
sel | 選中範圍(Selection) |
wn | 窗體(Window) |
可以使用上表列出的UDT類型定義下面這樣的數據聲明:
1
2
3
4
5
6
|
CHchCursorPosition;
SCRsrcUserWorkSpace;
DOCdocActive;
PAfirstPaActiveDocument;
PAlastPaActiveDocument;
WNwnMain;
|
語義前綴
語義前綴比UDT更進一步,它描述了變量或者對象是如何使用的。而且語義前綴不會根據項目的不同而不同,對於不同的項目均是標準的。下面列出一組標準的語義前綴。
語義前綴 | 含義 |
---|---|
c | 數量(count) |
first | 數組中需要處理的第一個元素 |
g | 全局變量 |
i | 數組的下標 |
last | 數組中需要處理的最後一個元素,與first對應 |
lim | 數組中需要處理的元素的上限,通常,lim等於last+1 |
m | 類一級的變量 |
max | 數組或其他種類的列表中絕對的最後一個元素 |
min | 數組或其他種類的列表中絕對的第一個元素 |
p | 指針(pointer) |
標準前綴的優劣
- 能更爲精確地描述一些含義比較模糊的名字
- 使名字變得更加緊湊
缺陷:程序員在使用前綴的同時忽略給變量其有意義的名字。
創建具備可讀性的短名字
如果環境真的要求你創建簡短的名字,請注意有些縮短名字的方法要好於其他的方法。
縮寫的一般指導原則
下面列出幾項用於創建縮寫的指導原則。其中一些原則彼此衝突,所以不要試圖同時應用所有的原則。
- 使用標準的縮寫
- 去掉所有非前置元音(computer => cmptr, screen => scrn, apple => appl, interger => intgr)
- 去掉虛詞and,or,the等
- 使用每個單詞的第一個或前幾個字母
- 統一在每個單詞的第一、第二或者第三個字母后截斷
- 保留每個單詞的第一個和最後一個字母
- 使用名字中的每一個重要單詞,最多不超過三個
- 去掉無用的後綴–ing,ed等
- 保留每個音節中最引人注意的發音
- 確保不要改變變量的含義
- 反覆使用上述技術,直到把每個變量名的長度縮減到了8-20個字符。
語音縮寫
有些人倡導基於單詞的發音而不是拼寫來創建縮寫,比如skating => sk8ing, before => b4…但是不提倡這麼做。
有關縮寫的評論
- 下面是一些能夠用來避免犯錯的規則
- 不要用從每個單詞中刪除一個字符的方式來縮寫,要麼刪除不止一個字符,要麼就把單詞拼寫完整
- 縮寫要一致,比如:要麼全部使用Num,要麼全用No,不要兩個都用
- 創建你能讀出來到的名字,比如:用xPos而不用xPstn。可以藉助電話來測試–如果你無法在電話中向他人讀出你的代碼,就請重新給變量起一個更清晰的名字吧
- 避免使用容易看錯或者讀錯的字符組合,比如ENDB和BEND,爲了表示B的結尾,可以用一種好的分隔技術來命名:b_end/BEnd
- 使用辭典來解決命名衝突,使用近義詞來解決命名衝突
- 在代碼裏用縮寫對照表解釋極短的名字的含義
應該避免的名字
- 避免使用令人誤解的名字或縮寫,比如將”Fig and Almond Season”縮寫爲FALSE
- 避免使用具有相似含義的名字
- 避免使用具有不同含義但卻有相似名字的變量
- 避免使用發音相近的名字
- 避免在名字中使用數字
- 避免在名字中拼錯單詞
- 避免使用英語中常常拼錯的單詞
- 不要僅靠大小寫來區分變量名
- 避免使用多種自然語言
- 避免使用標準類型、變量和子程序的名字
- 不要使用與變量含義完全無關的名字
- 避免在名字中包含易混淆的字符,比如1(數字1)和l(字母l),0(數字0)和O(字母O)
總結
好的變量名是提高程序可讀性的一項關鍵要素。代碼閱讀的次數遠遠多於編寫的次數,確保代碼中所取的名字更側重於閱讀方便而不是編寫方便。選擇一種規則,並堅持遵循該規則。