閱讀《程式碼整潔之道》總結

編碼磚家發表於2019-07-27

 很早就閱讀過《程式碼整潔之道》(英文版Clean Code),當時博主是個青澀的菜鳥,正在為團隊創造著混亂的程式碼。多年的工作中,屢次被別人的程式碼坑的苦不堪言,回想起當年我留下的程式碼,肯定也坑害了後來的同僚。當閱讀JDK原始碼或者其他優秀開源工程時,歎服作者程式碼構建之精良,他們都有共同的特點:精確的變數名、恰到好處的設計模式、詳細而不贅述的註釋等等。如今重讀本書,總結一下內容並加上自己的一些見解與大家分享。

程式碼是團隊溝通方式

 工作的溝通,不只是電子郵件或者面對面語言交流,程式碼也是溝通方式之一。用程式碼實現需求,只是萬里長征走完了第一步,必須讓程式碼表達自己的設計思想。試想一下,你負責的功能被另外一個同事接手,如果你的程式碼結構清晰、註釋合理,他就不用頻繁的詢問程式碼疑點,不用打斷你的工作。編寫程式碼的時候,應該考慮到別人的閱讀感受,減少閱讀障礙,為整個團隊創造程式碼,而不是你自己。

讓營地比來時更乾淨

 這是美國童子軍規的諺語,美國童子軍相當於半軍事化管理的青少年夏令營。夏令營結束後孩子們離開營地,要打掃衛生保持整潔,讓營地比來時更乾淨。在軟體開發過程中,可以理解為不要破壞規則,不要引入混亂。如果團隊已經制定了程式碼規範,比如類名必須有子系統字首比如BiOrderService(Bi指BI業務部門),就繼續遵循下去;再比如,團隊已經提供了公共庫比如MD5的加密,那就不要再次引入新的MD5庫。很多新手程式設計師接活兒後,看到不喜歡的規範就另起爐灶,需要某些工具類也不詢問老司機公共庫有沒有,直接引入自己熟悉的庫,造成相容性或者其他問題。

合適的命名

 合適的命名是頭等大事,正如給新生兒起個好名字那樣重要。不合適的命名通常是詞不達意、誤導觀眾、過度縮寫等,由於英文並非我們的母語,找個合適的單詞命名似乎真的很難。我建議是先把業務弄清楚,組織會議定下常用業務領域的單詞,禁止組員各自發明。比如程式碼裡使用canteen表示飯堂,那就不要再發明DinnerHall,既囉嗦又誤導同僚。

看看反例:
// 手機號
String phone = “13421800409”;
// 獲取地址
private String getDiZhi();
//修改密碼
private void modifyPassword(String password1 ,String password2)
看看正例:
// 手機號 mobileNo比phone更精確
String mobileNo= “13421800409”;

// 避免英文拼音混雜
private String getAddress();

// 引數的命名要區分意義
private void modifyPassword(String oldPassowrd,String newPassword)

短小的方法

  方法有多短小才合適沒有定論,但是長達500行的一個方法,絕對讓閱讀者起殺人之心。過長的方法,讓閱讀者不知道從何看起,看了前面忘記後面。將複雜的方法,拆分成邏輯相對簡單的短方法。

看看反例:
//  獲取個人資訊
private UserDTO getUserDTO(Integer userId)
{
    //獲取基本資訊 
    … 此處寫了10行

    //獲取最近的一次訂單資訊
    … 此處寫了30行

   // 獲取錢包餘額、可用優惠券張數等
    … 此處寫了30行

   return userDTO;
}
看看正例:
//  獲取個人資訊
private UserDTO getUserDTO(Integer userId)
{
    //獲取基本資訊 
    UserDTO userDTO= getUserBasicInfo(userId);

    //獲取最近的一次訂單資訊
    userDTO.setUserLastOrder(getUserLastOrder(userId));

    // 獲取錢包、可用優惠券張數等
    userDTO.setUserAccount(getUserAccount(userId));  
    return userDTO;
}

private UserDTO getUserBasicInfo(Integer userId);
private UserLastOrder getUserLastOrder(Integer userId);
private UserAccount getUserAccount(Integer userId);

減少if/else巢狀

  為什麼要減少巢狀,難道巢狀看上去不時尚嗎?我曾經看到某位同事的一段程式碼巢狀達到9層,他自己再去維護的時候都看暈了。程式碼過度巢狀的結果是隻有原作者才能讀懂,接盤俠一臉茫然。

看看反例:
// 修改使用者密碼,這個例子只有3層巢狀,很溫柔了
public boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
      if (userId != null && StringUtils.isNotBlank(newPassword) && SpringUtils.isNotBlank(oldPassword)) {
    User user = getUserById(userId);
    if (user != null) {
         if (user.getPassword().equals(oldPassword) {
              return updatePassword(userId, newPassword)
         }
     }
      }
}
看看正例:
// 修改使用者密碼 
Public Boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
     if (userId == null || StringUtils.isBlank(newPassword) || StringUtils.isBlank(oldPassword)) {
            return false;
     }
     User user = getUserById(userId);
     if(user == null) {
           return false;
      }
     if(!user.getPassword().equals(oldPassword) {
           return false;    
     }
     return updatePassword(userId, newPassword);
}

正例採用衛語句減少了巢狀,但是並非所有場景都適合這樣改寫。如果不適合,可以將關聯性高的邏輯抽取成一個獨立的方法減少巢狀。

抽離try/catch

  大家有沒有見過一個超長的方法,從頭到尾被一個try/catch照顧著?博主經歷過的專案中,這種不負責的寫法比比皆是。並非每行程式碼都會丟擲錯誤,只要將會丟擲錯誤的業務放在一個獨立的方法即可。

看看反例:
//  獲取個人資訊
private UserDTO getUserDTO(Integer userId)
{
   try { 
       //獲取基本資訊 
       ... 此處寫了10行
       //獲取最近的一次訂單資訊.
       ...此處寫了20行
       // 獲取錢包、可用優惠券張數等
       ...此處寫了20行
    }catch (Exception e) {
        logger.error(e);
        return null;
    }
}
   return userDTO;
}
看看正例:
//  獲取個人資訊
private UserDTO getUserDTO(Integer userId)
{
    //獲取基本資訊 
    UserDTO userDTO= getUserBasicInfo(userId);

    //獲取最近的一次訂單資訊
    userDTO.setUserLastOrder(getUserLastOrder(userId));

    // 獲取錢包、可用優惠券張數等
    userDTO.setUserAccount(getUserAccount(userId));  
    return userDTO;
}
private  UserDTO getUserBasicInfo(Integer userId);
private  UserLastOrder getUserLastOrder(Integer userId);
private  UserAccount getUserAccount(Integer userId){
      try{
          // TODO
      } catch ( Exception e) 
       { //TODO }
}

封裝多個引數

 如果方法引數將超過3個,建議放在類中包裝起來,否則再增加引數時,由於語義的強耦合會導致呼叫方語法錯誤。在後臺管理中的分頁查詢介面,常常會有很多查詢引數,而且有可能增加,封裝起來是最好的。

看看反例:
// 分頁查詢訂單 6個引數
public Page<Order> queryOrderByPage(Integer current,Integer size,String productName,Integer userId,Date startTime,Date endTime,Bigdecimal minAmount ,Bigdecimal maxAmount) {

}
看看正例:
@Getter
@Setter
public class OrderQueryDTO extends PageDTO {
 private String productName;
 private Integer userId;
 private Date startTime;
 private Date endTime;
 private Bigdecimal minAmount ;
 private Bigdecimal maxAmount;
}
// 分頁查詢訂單 6個引數
Public Page<Order> queryOrderByPage(OrderQueryDTO orderQueryDTO) {

}

第三方庫

Lombok

Lombok元件通過註解的方式,在編譯時自動為屬性生成構造器、getter/setter、equals、hashcode、toString方法
舉例如下:
@Setter 註解在類或欄位,註解在類時為所有欄位生成setter方法,註解在欄位上時只為該欄位生成setter方法。
@Getter 使用方法同上,區別在於生成的是getter方法。
@ToString 註解在類,新增toString方法。
@EqualsAndHashCode 註解在類,生成hashCode和equals方法。
@NoArgsConstructor 註解在類,生成無參的構造方法。
@RequiredArgsConstructor 註解在類,為類中需要特殊處理的欄位生成構造方法,比如final和被@NonNull註解的欄位。
@AllArgsConstructor 註解在類,生成包含類中所有欄位的構造方法。
@Data 註解在類,生成setter/getter、equals、canEqual、hashCode、toString方法,如為final屬性,則不會為該屬性生成setter方法。

常規寫法:
Public class Order {
     private Integer userId;
     
     public Integer getUserId() {
          return userId;
    } 

    public void setUserId(Integer userId) {
          return this.userId = userId; 
 }
}
採用Lombok:
@Getter
@Setter
Public class Order {
     private Integer userId;
}

Apache Commons系列

  Apache Commons系列元件給我們提供了關於字串、集合、IO操作等工具方法。這些元件是個大寶庫,提供了不少輪子。

元件 介紹
beanUtils JavaBean進行各種操作,克隆物件、屬性等等
codec 處理常用的編碼方法的工具類包,例如DES、SHA1、MD5、Base64等.
collections java集合框架操作
configuration java應用程式的配置管理類庫
io io工具的封裝
lang Java基本物件方法的工具類包 如StringUtils、ArrayUtils等等.
logging 提供的日誌介面
net 提供了客戶端和伺服器端的資料驗證框架
看看例子:
例1: 判斷集合是否為空:
CollectionUtils.isEmpty(null): true
CollectionUtils.isEmpty(new ArrayList()): true
CollectionUtils.isEmpty({a,b}): false

例2: 判斷集合是否不為空:
CollectionUtils.isNotEmpty(null): false
CollectionUtils.isNotEmpty(new ArrayList()): false
CollectionUtils.isNotEmpty({a,b}): true

例3:2個集合間的操作: 
集合a: {1,2,3,3,4,5}
集合b: {3,4,4,5,6,7}
CollectionUtils.union(a, b)(並集): {1,2,3,3,4,4,5,6,7}
CollectionUtils.intersection(a, b)(交集): {3,4,5}
CollectionUtils.disjunction(a, b)(交集的補集): {1,2,3,4,6,7}
CollectionUtils.disjunction(b, a)(交集的補集): {1,2,3,4,6,7}
CollectionUtils.subtract(a, b)(A與B的差): {1,2,3}
CollectionUtils.subtract(b, a)(B與A的差): {4,6,7}

相關文章