一些能夠提升代碼質量的編程規範

算是讀書筆記吧

極客時間--設計模式之美


命名

命名的一個原則就是以能準確達意爲目標,長短主要取決於給誰看。
學會換位思考,假設自己不熟悉這塊代碼,從代碼閱讀者的角度去考量命名是否足夠直觀。

Github

或者有兩個網站可以推薦:
CODELF可以搜索到很多Git上的代碼命名
Free App可以搜索到很多App以及Web國際化翻譯

長度

  1. 默認的、大家都比較熟知的詞,可以用縮寫
  2. 作用於小的,比如臨時變量用短命名
  3. 作用域比較大的,比如全局變量、類名用長的命名

這裏的短,並不是讓你用a1、a2這種無法辨認的名字
而是比如用sec 表示 second、str 表示 string、num 表示 number、doc 表示 document這樣的縮寫。

利用上下文簡化命名

public class User {
  private String userName;
  private String userPassword;
  private String userAvatarUrl;
  //...
}
//利用上下文簡化爲:
User user = new User();
user.getName(); // 藉助user對象這個上下文
public void uploadUserAvatarImageToAliyun(String userAvatarImageUri);
//利用上下文簡化爲:
public void uploadUserAvatarImageToAliyun(String imageUri);

命名可讀

選用的單詞。最優先的是能大家看懂、實在不行最起碼要能讀出來

命名可搜索

比如數組是用array,還是list。插入是用insertXXX,還是addXXX。
這個更多的依賴統一規約,能減少很多不必要的麻煩

特殊前綴

抽象類、接口、常量的前綴在項目裏能夠統一


註釋

類的註釋要寫得儘可能全面、詳細。
讓人一眼就能看明白這個類的大致作用和實現思路,而不需要進去查閱大量的代碼

public函數/接口聲明

如果他會給其他業務方使用,寫上註釋吧。是個好習慣

普通函數聲明

通常我們可以通過良好的命名替代註釋
但是對於一些特殊細節,比如參數和錯誤的特殊描述,需要進行註釋

函數內部

函數內部的通常也是通過良好的命名以及解釋性變量讓代碼自解釋

但是對於內部比較複雜的函數,總結性註釋可以很好的對函數進行拆分,使邏輯更清晰

public boolean isValidPasword(String password) {
  // check if password is null or empty
  if (StringUtils.isBlank(password)) {
    return false;
  }

  // check if the length of password is between 4 and 64
  int length = password.length();
  if (length < 4 || length > 64) {
    return false;
  }
    
  // check if password contains only a~z,0~9,dot
  for (int i = 0; i < length; ++i) {
    char c = password.charAt(i);
    if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.')) {
      return false;
    }
  }
  return true;
}

註釋的維護成本

註釋本身有一定的維護成本,所以並非越多越好。撰寫精煉的註釋,也是一門藝術


函數、類的大小

大小這個東西很主觀,就像對於“放鹽少許”中的“少許”,即便是大廚也很難告訴你一個特別具體的量值。

函數代碼行數,不要超過一個顯示屏的垂直高度

一般來講大概是50

超過一屏之後,在閱讀代碼的時候,爲了串聯前後的代碼邏輯,就可能需要頻繁地上下滾動屏幕,閱讀體驗不好不說,還容易出錯

類的大小,以不影響閱讀爲準

當一個類的代碼讀起來讓你感覺頭大了,實現某個功能時不知道該用哪個函數了,想用哪個函數翻半天都找不到了,只用到一個小功能要引入整個類(類中包含很多無關此功能實現的函數)的時候,這就說明類的行數過多了。

善用空行分割單元塊

對於比較長的函數,如果

  1. 邏輯上可以分爲幾個獨立的代碼塊
  2. 不方便將這些獨立的代碼塊抽取成小函數的情況下:

通過添加空行的方式,讓這些不同模塊的代碼之間,界限更加明確。


一些具體的函數編寫技巧

這些技巧,可以顯著的提升同一份代碼的質量

將代碼拆分成更小的單元塊

要有模塊化和抽象思維,善於將大塊的複雜邏輯提煉成類或者函數,屏蔽掉細節
讓閱讀代碼的人不至於迷失在細節中,這樣能極大地提高代碼的可讀性,聚焦於業務


// 重構前的代碼
public void invest(long userId, long financialProductId) {
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  calendar.set(Calendar.DATE, (calendar.get(Calendar.DATE) + 1));
  if (calendar.get(Calendar.DAY_OF_MONTH) == 1) {
    return;
  }
  //...
}

// 重構後的代碼:提煉函數之後邏輯更加清晰
public void invest(long userId, long financialProductId) {
  if (isLastDayOfMonth(new Date())) {
    return;
  }
  //...
}

public boolean isLastDayOfMonth(Date date) {
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  calendar.set(Calendar.DATE, (calendar.get(Calendar.DATE) + 1));
  if (calendar.get(Calendar.DAY_OF_MONTH) == 1) {
   return true;
  }
  return false;
}

避免函數參數過多

函數包含 3、4 個參數的時候還是能接受的
再多,就需要重構代碼進行優化

通常我們會將參數封裝成對象


public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);

// 將參數封裝成對象
public class Blog {
  private String title;
  private String summary;
  private String keywords;
  private Strint content;
  private String category;
  private long authorId;
}
public void postBlog(Blog blog);

不過,這樣在使用的時候,不同環境下的參數定義可能隨着迭代就不那麼明確了,有利有弊吧。

迭代時,bool值傳入方法作爲隔離依據

這個情況對於很多開發人員都很常見,尤其在修改迭代他人代碼的時候。
這種打補丁的方式,隨着補丁的增多,對項目也是毀滅性的。

void configXXXX() {
   //基本操作...
}

//迭代後
void configXXXX(isModeA , isModeB) {
   //基本操作...
  if (isModeA) {
    //特殊操作...
  }
  //基本操作...
  if (isModeB) {
    //特殊操作
  }

  //基本操作...
  if (isModeA && !isModeB) {
    //特殊操作...
  } else if (isModeA){
    //特殊操作...
  }
  //基本操作...
}

函數設計要職責單一

不要設計一個大而全的函數,而在函數的內部做一大堆的盤點。
最起碼,具體的功能實現函數不要耦合其他職責的邏輯

比如字符串的校驗時,電話、用戶名、郵箱三者的實現邏輯(將來)很可能是不同的,要分開定義。

public boolean checkUserIfExisting(String telephone, String username, String email)  { 
  if (!StringUtils.isBlank(telephone)) {
    User user = userRepo.selectUserByTelephone(telephone);
    return user != null;
  }
  
  if (!StringUtils.isBlank(username)) {
    User user = userRepo.selectUserByUsername(username);
    return user != null;
  }
  
  if (!StringUtils.isBlank(email)) {
    User user = userRepo.selectUserByEmail(email);
    return user != null;
  }
  
  return false;
}

// 拆分成三個函數
public boolean checkUserIfExistingByTelephone(String telephone);
public boolean checkUserIfExistingByUsername(String username);
public boolean checkUserIfExistingByEmail(String email);

移除過深的嵌套層次

通常是if-else、switch-case、for 循環過度嵌套導致
我們可以最開始按照執行順去去編寫多層代碼,功能開發完畢函數穩定之後,再回過頭來着手移除

  • 去掉多餘的 else 語句
public double caculateTotalAmount(List<Order> orders) {
  if (orders == null || orders.isEmpty()) {
    return 0.0;
  } else { // 此處的else可以去掉
    double amount = 0.0;
    for (Order order : orders) {
      if (order != null) {
        amount += (order.getCount() * order.getPrice());
      }
    }
    return amount;
  }
}
  • 去掉多餘的if
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null) {
    for (String str : strList) {
      if (str != null) { // 跟下面的if語句可以合併在一起
        if (str.contains(substr)) {
          matchedStrings.add(str);
        }
      }
    }
  }
  return matchedStrings;
}
  • 提前退出嵌套
// 重構前的代碼
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null){ 
    for (String str : strList) {
      if (str != null && str.contains(substr)) {
        matchedStrings.add(str);
        // 此處還有10行代碼...
      }
    }
  }
  return matchedStrings;
}

// 重構後的代碼:使用return和continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList == null || substr == null){ 
    return matchedStrings;
  }
  for (String str : strList) {
    if (str == null || !str.contains(substr)) {
      continue; 
    }
    matchedStrings.add(str);
    // 此處還有10行代碼...
  }
  return matchedStrings;
}
  • 將部分嵌套邏輯封裝成函數調用

對於無法通過邏輯順序進行優化的代碼,可以通過把局部功能封裝成小的函數來減輕主函數的嵌套層級。


// 重構前的代碼
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }
  
  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    if (password.length() < 8) {
      // ...
    } else {
      // ...
    }
  }
  return passwordsWithSalt;
}

// 重構後的代碼:將部分邏輯抽成函數
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }

  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    passwordsWithSalt.add(appendSalt(password));
  }
  return passwordsWithSalt;
}

private String appendSalt(String password) {
  String passwordWithSalt = password;
  if (password.length() < 8) {
    // ...
  } else {
    // ...
  }
  return passwordWithSalt;
}

解釋性變量

  • 常量取代魔法數字

public double CalculateCircularArea(double radius) {
  return (3.1415) * radius * radius;
}

// 常量替代魔法數字
public static final Double PI = 3.1415;
public double CalculateCircularArea(double radius) {
  return PI * radius * radius;
}
  • 解釋性變量來解釋複雜表達式
if (date.after(SUMMER_START) && date.before(SUMMER_END)) {
  // ...
} else {
  // ...
}

// 引入解釋性變量後邏輯更加清晰
boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END);
if (isSummer) {
  // ...
} else {
  // ...
} 
  • 多個複合條件的bool,最好加上註釋
// ((團購 &&  非折扣 && 有銷售價格) || 有團購列表)
boolean showGroupBuyButton = ((isGroupbuy && !isSale && (price>0)) || groupBuyList.count)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章