編寫優雅代碼的最佳實踐


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規范"即可獲得相關資料。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM