自下向上的編寫容易閱讀的代碼(上) 頂 原 薦

我在 關於極簡編程的思考 中曾提到要編寫可閱讀的代碼。因爲代碼是編寫一次,閱讀多次。 閱讀者包括代碼編寫者,以及後來的維護人員。能讓閱讀代碼更輕鬆,有利於增強項目或者產品的可維護性。

本博客分爲上下倆部分,第一部分講解在代碼層次 編寫可閱讀的代碼, 第二部分講解方法,類,以及一些設計上的考慮 讓代碼更適合閱讀。這些都是我在實際工作的一些體會以及代碼審查過程中跟同事一起得出的一些經驗。沒有太高深的理論,適合所有人借鑑交流。

代碼層次(上)

if 語句保持主流程暢通

if(xxx){
    return false;  
}

if(yyy){
    return false;  
}


if(zzz){
  throw new Exception();
}

//主邏輯代碼在下面
.......
return true;

使用if語句,對於不符合主邏輯的,要儘早返回,這樣可以減輕代碼閱讀者的負擔,下次再看,直接就可以從主邏輯開始。直接跳過不關心的代碼塊(這樣代碼塊必然返回都是fasle) 如下是一個不好的例子

if(xxx){
    return false;  
}

if(yyy){
    return true;  
}

//主邏輯代碼在下面

在主邏輯前面分別返回了true 或者 false,閱讀者會造成混亂,因爲說明這個方法任何一處都有可能返回不同的情況,更糟糕的是

if(!xxx){
    return true
}else{
    
    if(yyy){
        return false;
    }else{
        //主邏輯代碼在下面
        .......
        return true;
    }
    
}

顯然這段代碼會造成較大的閱讀負擔

儘可能多的去掉代碼行的註釋

/*
int c = getBalance(userId); 
if(isGreat(date){
    
    
}
*/
int c = getBalance(userIddate);

顯然,有多行是無用的註釋,如果當初爲了調試保留在這裏,那麼調試成功後要儘快刪除,不要給後來人留下疑惑。

代碼註釋會隨時過時,但IDE並沒有像代碼那樣能充分管理註釋,不需要的註釋應該立即刪除,如下注釋剛開始看起來不錯

public class User{
    // 0 表示性別爲女,1表示爲男
    int gender = 0;
}

項目中,狀態值會隨着項目發展而不斷增加,上面的註釋會誤導閱讀者以爲性別只有倆個狀態,正確的做法是

public class User{
   
    int gender = Gender.MALE.value();
}

這樣,閱讀者可以通過枚舉類找到性別對應所有的狀態,比如 Gender.Male,Gender.FEMALE,Gender.UNKNOWN

使用一些中間變量來增強代碼可讀性

int total = a*b+c/rate+d*e;

上面的代碼一氣呵成,且只用了一行,但沒有下面的代碼更容易閱讀

int yearTotal = a*b;
int lastYearTotal = c/rate;
int todayTotal = d*e;
int total = yearTotal+lastYearTotal+todayTotal;

看似其他人一行代碼完成似乎更牛,你用了多行代碼才完成了一個功能,但你的代碼顯然更容易被後來人閱讀。我一直覺得寫代碼就跟寫小說一樣,要看得懂纔是真正的小說,如果從任何地方切入都能看懂,那就是本好小說。

甚至可以將一部分代碼封裝到一個方法裏,通過方法名和參數來提高可閱讀性。後來者雖然第一閱讀到這樣的代碼還需要進入方法體瞭解用法,但下次再次閱讀,或者再次修改,就可以跳過他已經熟悉的方法,比如如下解析excel的文件,需要讀出多個片段數據

public void parse(Sheet sheet){

User user = readUserInfo(sheet);
List<Order> orders = readUserOrderInfo(sheet);
UserCredit credit = readUserCreditInfo(sheet);
    
}


如上parse方法將分成三個方法完成,這對於性能和代碼行數,可能略有影響(其實根本算不上影響),但增強了代碼的可閱讀性,如果後來人重構了用戶積分部分,可以直接修改readUserCreditInfo方法,而不需要從上百行代碼裏找到自己應該修改的地方。

提倡使用一些短小方法來劃分代碼

......省略50行代碼
int year =c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH)+1;
int month_date= c.get(Calendar.DATE);
String message ="今天"+year+"年"+month+"月"+month_date+"日,您在【業務系統】中有【"+paymentTotal+"】客戶需要還款、有【"+settleTotal+"】客戶需要結清、【"+verdueTotal+"】客戶已經逾期,請儘快處理。";
.......省略50行代碼

這段代碼如果單獨看尚可,如果這是在成百行代碼的一部分,建議放到一個小方法裏,比如,重構爲

......
String str = getPayMessage(paymentTotal,settlleTotal,verdueTotal)
.......

protected String getPayMessage(BigDecimal paymentTooal,BigDecimalBigDecimal settlleTotal,verdueTotal){
    int year =c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH)+1;
    int month_date= c.get(Calendar.DATE);
    String message ="今天"+year+"年"+month+"月"+month_date+"日,您在【業務系統】中有【"+paymentTotal+"】客戶需要還款、有【"+settleTotal+"】客戶需要結清、【"+verdueTotal+"】客戶已經逾期,請儘快處理。";
}

重構後,代碼閱讀者每次看到這裏,都會放心的跳過這部分代碼。因爲從方法名已經瞭解其作用,能很快的掃過這片代碼區域

不要使用數組

程序裏的數組只適合代碼編寫者看,閱讀者無法判斷數組代表的業務含義,比如

Object[] rets = call();
boolean  success = (Boolean)rets[0];
String msg = (String)rets[1];

較好的方法是定義一個對象代替數組

CallResult rets = call();
boolean  success = rets.isSuccess();
String msg =  rets.getMessage();

也許你覺得調用後立刻轉化成有意義的參數名會不影響閱讀,這確實是一種補救辦法,但不及CallResult好。代碼編寫者應該能時刻想到給閱讀者減輕負擔。

類似的列子還有JPA的查詢,對於不能映射爲實體的,總是返回一個數組,比如

Object[] array = jpa.query("select * from xxx ,yyy .....");
Integer id = (Integer)array[0];
String name = (String)array[1];
.....
String tradeId = (String)array[22];


返回這樣一個數組,如果sql要改寫,那麼代碼對array的的處理也肯定要修改。看代碼的人也不得不閱讀這些無聊的代碼。

相對於MyBatis和我寫的BeetlSql,這一點JPA就不行了-提供了一個返回數組的查詢接口。

我發現我每次在博客提到我寫的開源,就有人說我想宣傳自己的開源。我想強調一下,我只是踐行知行合一,我不會輕易評判一個我不熟悉領域技術,除非我真的實踐過。如果噴子對此不爽,你大可以忽略我“自我宣傳部分”,僅看到我博客其他內容。

不一定要取有意義的變量名

java 裏的for循環一般都是使用i變量,這說明了有些情況下,可以用一些簡單的變量名字代替有意義的變量名字。前提是這些名字約定俗成,或者能在閱讀代碼的人眼裏,這個變量就是幾行之內就使用完畢,比如

boolean success = calc();
if(success){
    return false;
}

success 變量可能不如paySuccess更好,但鑑於很快使用完畢,還是可以接受。相反,如果在sucess變量定義後面的100行,還用到了這個變量,那麼“success” 就可能讓人疑惑了,代碼閱讀者不得不再翻回去瞭解success的含義

總結

代碼是一次寫入,多次閱讀,從代碼層次去看待如何編寫容易閱讀代碼,可能還能列出更多的規則,我個人覺得這些規則並不重要,重要的是能時刻想到後來人會如何閱讀你的代碼纔是最重要的,如果他閱讀你的代碼,毫無障礙的達到一目十行,覺得你寫的代碼沒什麼高深,那就是好代碼。

博文參考了 王垠的《編程的智慧》

下篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章