程式碼質量隨想錄(三)名字好,誤會少
寫完前兩篇之後,有點小倦怠,因為一方面要整理讀書筆記,一方面還要結合自己的思路加以重新表述,頗費周張。不過前兩日看到有小朋友過來讚我的文章,說對實際程式碼有所幫助,還是滿欣慰的,本系列隨想錄的目的之一,就是要營造一個努力改良程式碼質量的思維環境。
要想讓識別符號的名稱更易理解,就應該多考慮考慮此名稱是否會被誤讀。
先看兩個很容易誤讀的例子。
Object[] results = Database.getAllObjects().filter("year<=2011");
到底是要選出year小於等於2011的那部分物件,還是選出year大於2011的那部分呢?filter到底是排除(exclude),還是遴選(select)呢?我自己在日常編碼中也愛用filter,多半由於習慣。現在自己思量,是得改正了。 再看
public String clip(String text,int length); //裁掉文字的末尾
clip方法有歧義:到底是去掉文字後的length個字元,還是從頭開始擷取最大length個字元呢?比如clip("Java",2);
到底是"va"還是"Ja"?如果是前者應該叫removeLast,如果是後者則應叫truncate。而且length也有毛病,到底以什麼為單位?位元組?字元?還是詞語?如果是字元,應該是truncate(String text,int maxCharCount)。
歸納起來說,以下幾種情形應格外注重選取避免誤解的名稱。
1.以常量表示包含端點的上限或下限時,應分別用MAX與MIN做字首。
例如CART_TOO_BIT_LIMIT=10
到底是說購物車中最多放10件商品還是11件?抑或是9件?改為MAX_ITEMS_IN_CART = 10
則很清楚:最多10件。
2.在表達包含左右端點的區間時,應用first及last。
public void printIntegerInRange(int start,int stop){...}
...
printIntegerInRange(2,6);
到底列印[2,3,4,5]還是[2,3,4,5,6]?如果是後者,應該是printIntegerInRange(int first,int last)。
3.在表達包含左端點而不含右端點的區間時,應當使用begin與end。
英文中沒有哪個常用詞的字面意思能表示“區段內最後一個值的緊下一個值”這個意思,所以使用end只是約定成俗而已,並不精確。例如public void printEventsInRange(String begin,String end),可以使用如下引數來呼叫:printEventsInRange("OCT 16 00:00", "OCT 17 00:00"),這樣的話,一般人都能理解右端點("OCT 17 00:00")不含在範圍內。如果用public void printEventsInRange(String first,String last),則是printEventsInRange("OCT 16 00:00", "OCT 16 23:59")。
4.使用判斷詞來消除boolean變數的歧義。
為boolean變數起名時一定注意是否有歧義:
bool readPassword = true;
到底是當前需要讀取密碼,還是密碼已經被讀取過了?前者應是needPassword,後者應是userIsAuthenticated。
使用is、has、can、should等詞彙來讓boolean變數與方法的意圖更加清晰,尤其是在那些不需要申明方法或函式返回型別的程式語言中。例如:spaceLeft()到底是返回剩下的空間大小,還是返回是否有剩餘空間?根據是簡單獲取還是複雜計算,前者應命名為getLeftSpaceInPixel()或calcLeftSpacePx(),分別指示輕量級(get)和重量級(calculate或compute)的兩種獲取辦法;而後者則應是hasSpaceLeft(),只說有沒有剩餘空間,不談具體的量。
5.避免在boolean命名中使用否定形式。
例如:
bool disableSSL = false;
不如下面這種命名方式清晰:
bool useSSL = true;
6.不要同約定成俗的命名方式相違逆。
例如getXXX()格式的方法一般有兩個隱含意義:1.該操作為輕量級。2.該操作返回所在類的某個成員。
如下統計算數平均數的方法名稱即為不宜:
public class SampleCollector {
public void add(double sample) { ... }
public double getMean() {
... // 疊加所有采樣值並返回“總和/樣本數”
}
...
}
getMean()並非輕量級操作,且不返回本類某個成員。不如叫它computeMean()更好,compute會引人聯想該操作是不是稍為複雜一些,耗時一些。如果非要用getMean做名稱的話,那麼mean應被納入快取機制。例如:
private boolean meanCached;//計算完樣本後置為true,樣本改動時置為false
...
public double getMean() {
if(!meanCached){
... // 疊加所有采樣值
mean=sampleSum/sampleCount;
meanCached=true;
}
return mean;
}
void ShrinkList(list<Node>& list, int max_size) {
while (list.size() > max_size) {
FreeNode(list.back());
list.pop_back();
}
}
size()操作的時間複雜度為O(1)應是大多數人的共識,可是恰恰list的size()是時間複雜度為O(n)的操作,這導致整個函式的複雜度變為O(n2)。按理說size()應該叫為countSize() 或countElements(),以體現其重量級運算的特質來,不過,為了和其餘容器類相符合,還是叫成size了。所幸新版C++規範強制要求size操作的時間複雜度為O(1)了(ARC書的作者這麼說的,我未查證。大家幫忙在C++11規範中查證此事。原有規範只是“建議”它應具有常數時間複雜度,並未強制)。
小翔以為,如果某個抽象介面定義了一個貌似輕量級的簡單操作,如Collection的size(),則子類物件在實現時應該儘量降低時間複雜度。實在不能時甚至可以考慮丟擲異常或對客戶提出警告。根本的解決辦法還是學習C++規範那樣,給出一個建議的時間複雜度來。
7.在多個候選名稱中取捨時應該仔細質詢其可能帶來的歧義。
例如有兩份相似的伺服器配置引數檔案:
config_id: 100
description: "increase font size to 14pt"
traffic_fraction: 5%
...
config_id: 101
description: "increase font size to 13pt"
[其餘引數與前一份相同]
我們現在想通過某個機制複用整套引數,例如這樣:
config_id: 101
想要複用的配置檔案id: 100
[其餘引數與前一份相同]
那麼,這個“想要複用的配置檔案id”,應該怎麼起名呢?備選關鍵詞有:template、reuse、copy和inherit。
template很模糊:“template: 100”到底是說自己是一份名叫“100”模板,還是說使用一個名叫“100”的模板作為其基礎引數?況且模板這個概念太過抽象,給人感覺需要以具體內容填充它。
“reuse: 100”到底是說這份引數最多可以使用100次,還是說複用名為“100”的那份配置檔案中的引數?
“copy: 100”是第100份拷貝嗎?還是說拷貝自編號為“100”的那套配置?後者不如叫copy_config_from
更好。
“inherit: 100”,inherit這個詞,大多數程式設計師很熟悉,且與日常生活的“財產繼承”概念可相比擬,所以引起的誤解相對較少。可以擴充為inherit_config_from
來更精確地闡明這個意思。
綜上,copy_config_from
或inherit_config_from
應為最終中選名稱。
總之,好的識別符號名稱可以儘量消除程式碼閱讀者的誤解,提高程式碼可讀性與可維護性,亦能促進業務交流。所以應當仔細考究,儘量選取免於誤會的名稱,尤其是遇到“filter、length和limit”這些模稜兩可的詞語時。此外,區間與上下限含不含端點、boolean型別的識別符號會不會引起誤解、方法名稱所隱含的意義是否符合常識,這些問題也應該在起名時反覆考量。
用了兩篇文章才講完給識別符號起名的事情,可見其的確關乎程式碼質量的提升。下一篇我們談談程式碼的排版問題。
愛飛翔 2012年6月4日
本文使用Creative Commons BY-NC-ND 3.0協議(創作共用 自由轉載-保持署名-非商業使用-禁止衍生)釋出。
原文網址:http://agilemobidev.net/eastarlee/code-quality/think_in_code_quality_3_good_name_zh_cn/
相關文章
- 程式碼隨想錄leetcodeLeetCode
- 程式碼隨想錄2
- 【程式碼隨想錄】完全揹包
- 程式碼隨想錄:兩數之和
- 程式碼隨想錄- Day01
- 程式碼隨想錄-day3
- 程式碼隨想錄-day2
- 程式碼隨想錄-day1
- 20240505記錄《程式碼隨想錄》筆記筆記
- 【程式碼隨想錄】零錢兌換
- 程式碼隨想錄移除元素二刷
- 程式碼隨想錄演算法-回溯4演算法
- 程式碼隨想錄:移除連結串列元素
- 程式碼隨想錄:設計連結串列
- 程式碼隨想錄演算法 - 回溯3演算法
- 程式碼隨想錄day 53 || 圖論4圖論
- KMP演算法(基於程式碼隨想錄)的隨筆KMP演算法
- 【程式碼隨想錄】廣度優先搜尋
- 程式碼隨想錄:用棧實現佇列佇列
- 程式碼隨想錄:用佇列實現棧佇列
- 程式碼與質量的思考與隨筆
- 程式碼隨想錄演算法 - 二叉樹演算法二叉樹
- 程式碼隨想錄day35 || 416 分割等和子集
- 【程式碼隨想錄】一、陣列:5.螺旋矩陣陣列矩陣
- 「程式碼隨想錄八股訓練營總結」
- 【程式碼隨想錄】二、連結串列:理論基礎
- 程式碼隨想錄day9-棧和佇列1佇列
- 程式碼隨想錄演算法訓練營第三天 | 熟悉連結串列演算法
- 程式碼可讀性隨想
- 程式碼隨想錄演算法訓練營 | 200.島嶼的數量(dfs/bfs)演算法
- 【程式碼隨想錄】一、陣列:4.滑動視窗陣列
- 程式碼隨想錄演算法訓練營第9天 |演算法
- 程式碼隨想錄day13 || 樹定義以及遍歷
- 「程式碼隨想錄演算法訓練營」第三天 | 連結串列 part1演算法
- 程式碼隨想錄演算法day4 - 連結串列2演算法
- 【程式碼隨想錄】二、連結串列:2、設計連結串列
- 【程式碼隨想錄】二、連結串列:1、移除連結串列元素
- 程式碼隨想錄第10天 | 棧與佇列part01佇列
- 程式碼隨想錄演算法訓練營第六天演算法