Robert Martin曾說過"在程式碼閱讀中說髒話的頻率是衡量程式碼質量額唯一標準"。同時,程式碼的寫法應當使別人理解它所需的時間最小化,也就是說我們寫的程式碼是給人看的而不是給機器看的。那麼,如何編寫優雅程式碼呢?可以從思想層面和具體技巧層面來優化程式碼,思想層面指的是遵循物件導向設計原則,本期介紹的是具體技巧。
##1. 程式碼總是越短越好嗎?
assert((!(bucket = findBucket(key))) || !bucket.isOccupied());
複製程式碼
上面這行程式碼雖然比較短,但是難以閱讀。為了更好地閱讀,我們做如下修改:
bucket = findBucket(key);
if(bucket != null){
assert(!bucket.isOccupied());
}
複製程式碼
減少程式碼行數是一個好目標,但是讓閱讀程式碼的事件最小化是個更好的目標。
2. 給重要語句新增註釋
// Fast version of "hash = (65599*hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c
複製程式碼
上面這行程式碼如果沒有新增註釋,我們根本不知道是什麼意思,但是有了這行註釋,我們就知道通過移位操作來提升效能。
## 3. tmp的使用 tmp是我們經常用的,譬如說兩個變數置換,都已變成約定俗成了。
tmp = right;
right = left;
left = tmp;
複製程式碼
String tmp = user.getName();
tmp += " " + user.getPhoneNumber();
tmp += " " + user.getEmail();
template.set("user_info",tmp);
複製程式碼
4.i,j,k,iter,it
:只用做索引或者迴圈迭代
i,j,k,iter,it
被用做索引或者迴圈迭代已成為業界規範了(i是index的縮寫),例如:
for(int i=0;i<100;i++){
for(int j=0;j<100;j++){
......
}
}
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
......
}
複製程式碼
如果我們在其他地方使用i,j,k,那麼就會增加閱讀者的時間。
5. 附帶重要屬性
我們把命名當做一種註釋的方式,讓它承載更多的資訊!
6. 名字需要多長?
- 在小的作用域中使用簡短的名字
- 在作用域大的可以使用長名字
if(debug){
Map<String,Integer> m = new HashMap<>();
lookUpNamesNumbers(m);
print(m);
}
複製程式碼
7. 不要使用容易誤解的名字
results = Database.all_objects.filter("year<=2011")
複製程式碼
上面這行程式碼結果現在包含哪些資訊?filter是把年份小於等於2011年的資料過濾掉?還是保留?
8. 推薦用min和max來表示極限
MAX_ITEMS_IN_CART = 10;
if (shoppingCart.numOfItems()> MAX_ITEMS_IN_CART){
error("Too many items in cart");
}
複製程式碼
9. 推薦用begin和end來表示包含/排除範圍
begin表示包含,end表示排除,在Java中典型的例子就是String.substring()
String s = "Hello world";
s.substring(2,5);-> "llo"
複製程式碼
10.與使用者的期望相匹配
一般來說,getter方法就是獲取一個欄位的值,使用者期待的是輕量級的方法,如果你要是在其中做了太多的計算,就應該考慮改名。
public double getMeanPrice(){
//遍歷所有條目計算總價,然後計算平均價格
}
public double computeMeanPrice(){
//遍歷所有條目計算總價,然後計算平均價格
}
複製程式碼
11.不要為那些從程式碼本身就能快速推斷的事實寫註釋
public class Account {
// Constructor
public Account(){
}
// Set the profit member to a new value
void setProfit(double profit){
…….
}
// Return the profit from this Account
double getProfit(){
….
}
};
複製程式碼
12. 不要給不好的名字加註釋--應該把名字改好
// Releases the handle for this key.This doesn't modify the actual registry.
void deleteRegistry(RegistryKey key)
複製程式碼
乍一看我們會誤認為這是一個刪除登錄檔的函式,可是註釋裡澄清它不就改動真正的登錄檔。因此,我們可以用一個更加自我說明的名字,例如:
void releaseRegistryHandle(registryKey key);
複製程式碼
13.為程式碼中的瑕疵寫註釋
// TODO:採用更快演算法或者當程式碼沒有完成時 // TODO(dustin):處理除JPEG以外的影像格式
14.為常量寫註釋
// users thought 0.72 gave the best size/quality tradeoff
image_quality = 0.72;
// as long as it's >= 2*num_processors,that's good enough
NUM_THREADS = 8;
// impose a reasonable limit - no human can read that much anywhere
const int MAX_RSS_SUBSCRIPTIONS = 1000;
複製程式碼
15. 站在讀者的角度寫註釋
struct Recoder {
vector<float> data;
......
void clear(){
// 每個人讀到這裡都會問,為啥不直接呼叫data.clear()
vector<float>().swap(data);
}
}
複製程式碼
如果有一個好的註釋可以解答讀者的疑問,將上述進行如下修改:強制Vector真正地把記憶體歸還給記憶體分配器,詳情請查閱STL swap trick。
16. 公佈可能的陷阱
void sendMail(String to,String subject,String body);
複製程式碼
這個函式由於需要呼叫外部伺服器傳送郵件,可能會很耗時,有可能導致使用者的執行緒掛起。需要將這段描述放到註釋中。
17. 條件語句中引數的順序
一般原則:將變數放在左邊,常量放在右邊。更寬泛地說,將比較穩定的變數放在右邊,變化較大的放在左邊。如 if ( length >= 10) 而不是 if ( 10 <= length)。但是,在非“大小”比較的情況下,上面的原則似乎不起作用,例如驗證一個請求引數是否為某個特定值:if ( request.getParameterValue("name")).equals("Brandon"))
,此時將常量"Brandon"可以避免出現空指標的情況(上行的引數沒有name或者值為空)。
18. if/else語句塊的順序
if/else書寫規範:首先處理正邏輯而不是負邏輯,例如 if(ok),而不是if(!ok);其次處理掉簡單的情況,這有利於讓if和else處理程式碼在同一個螢幕內可見。
19. 通過提早返回減少巢狀
使用提前返回的機制,可以把函式的巢狀層級變淺。舉個例子,沒有使用提前返回的程式碼:
static bool checkUserAuthority()
{
bool a, b, c, d, e;
if (a)
{
if (b)
{
if (c)
{
if (d)
{
if (e)
{
return true;
}
}
}
}
}
return false;
}
複製程式碼
使用了提前返回的程式碼:
static bool checkUserAuthority()
{
bool a, b, c, d, e;
if (!a)
return false;
if (!b)
return false;
if (!c)
return false;
if (!d)
return false;
if (!e)
return false;
return true;
}
複製程式碼
##20. 通過 "總結變數" 增加可讀性
if(request.user.id == document.owner_id){
// user can edit this document ...
}
if(request.user.id != document.owner_id){
// document is read-only...
}
複製程式碼
通過觀察,我們提取一個變數final boolean user_owns_document=(request.user.id == document.owner_id)
,接著程式碼就可以修改成:
if(user_owns_document){
// user can edit this document ...
}
if(!user_owns_document){
// document is read-only...
}
複製程式碼
21. 減少控制流變數
在while、for等迴圈語句中,我們通常使用自定義的bool變數,來控制流轉。
boolean done = false;
while(/* condition */ && !done){
...
if(...){
done = true;
continue;
}
}
複製程式碼
以我們的經驗,"控制流變數" 可以通過優化程式結構、邏輯來消除。
while(/* condition */){
...
if(...){
break;
}
}
複製程式碼
22. 縮小變數的作用域
void foo(){
int i = 7;
if(someCondition){
// i is used only within this block
}
}
void foo(){
if(someCondition){
int i = 7;
// i is used only within this block
}
}
複製程式碼
23. 不要為了共享而把變數設定為類的欄位
public class LargeClass{
String s;
void method1(){
s = ...
method2();
}
void method2(){
//使用s
}
}
複製程式碼
通過引數傳遞來實現資料共享
public class LargeClass{
void method1(){
String s = ...
method2(s);
}
void method2(String s){
//使用s
}
}
複製程式碼
24. 不要把所有變數都定義在開頭
把所有變數定義在開頭是C語言的風格,面嚮物件語言習慣將變數定義在離它開始使用的地方。
public void foo(){
boolean debug = false;
String[] pvs;
String pn;
String pv;
...
}
複製程式碼
除了上述建議之外,我們還可以參考阿里Java規範,關注微訊號:"木可大大",傳送"阿里Java規範"即可獲得相關資料。
歡迎關注微信公眾號:木可大大,所有文章都將同步在公眾號上。