算是讀書筆記吧
極客時間--設計模式之美
命名
命名的一個原則就是以能準確達意爲目標,長短主要取決於給誰看。
學會換位思考,假設自己不熟悉這塊代碼,從代碼閱讀者的角度去考量命名是否足夠直觀。
Github
或者有兩個網站可以推薦:
CODELF可以搜索到很多Git上的代碼命名
Free App可以搜索到很多App以及Web國際化翻譯
長度
- 默認的、大家都比較熟知的詞,可以用縮寫
- 作用於小的,比如臨時變量用短命名
- 作用域比較大的,比如全局變量、類名用長的命名
這裏的短,並不是讓你用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
超過一屏之後,在閱讀代碼的時候,爲了串聯前後的代碼邏輯,就可能需要頻繁地上下滾動屏幕,閱讀體驗不好不說,還容易出錯
類的大小,以不影響閱讀爲準
當一個類的代碼讀起來讓你感覺頭大了,實現某個功能時不知道該用哪個函數了,想用哪個函數翻半天都找不到了,只用到一個小功能要引入整個類(類中包含很多無關此功能實現的函數)的時候,這就說明類的行數過多了。
善用空行分割單元塊
對於比較長的函數,如果
- 邏輯上可以分爲幾個獨立的代碼塊
- 不方便將這些獨立的代碼塊抽取成小函數的情況下:
通過添加空行的方式,讓這些不同模塊的代碼之間,界限更加明確。
一些具體的函數編寫技巧
這些技巧,可以顯著的提升同一份代碼的質量
將代碼拆分成更小的單元塊
要有模塊化和抽象思維,善於將大塊的複雜邏輯提煉成類或者函數,屏蔽掉細節
讓閱讀代碼的人不至於迷失在細節中,這樣能極大地提高代碼的可讀性,聚焦於業務
// 重構前的代碼
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)