大規(guī)模高層次重構(gòu)
解耦代碼
“解耦”為何如此重要?
過(guò)于復(fù)雜的代碼往往在可讀性、可維護(hù)性上都不友好。解耦保證代碼松耦合、高內(nèi)聚,是控制代碼復(fù)雜度的有效手段。代碼高內(nèi)聚、松耦合,也就是意味著,代碼結(jié)構(gòu)清晰、分層模塊化合理、依賴關(guān)系簡(jiǎn)單、模塊或類之間的耦合小,那代碼整體的質(zhì)量就不會(huì)差。
代碼是否需要“解耦”?
間接的衡量標(biāo)準(zhǔn)有很多,比如,看修改代碼是否牽一發(fā)而動(dòng)全身。直接的衡量標(biāo)準(zhǔn)是把模塊與模塊、類與類之間的依賴關(guān)系畫(huà)出來(lái),根據(jù)依賴關(guān)系圖的復(fù)雜性來(lái)判斷是否需要解耦重構(gòu)。
如何給代碼“解耦”?
給代碼解耦的方法有:封裝與抽象、中間層、模塊化,以及一些其他的設(shè)計(jì)思想與原則,比如:?jiǎn)我宦氊?zé)原則、基于接口而非實(shí)現(xiàn)編程、依賴注入、多用組合少用繼承、迪米特法則等。當(dāng)然,還有一些設(shè)計(jì)模式,比如觀察者模式。
小規(guī)模低層次重構(gòu)
編碼規(guī)范
命名
命名的關(guān)鍵是能準(zhǔn)確達(dá)意。對(duì)于不同作用域的命名,我們可以適當(dāng)?shù)剡x擇不同的長(zhǎng)度。
-
我們可以借助類的信息來(lái)簡(jiǎn)化屬性、函數(shù)的命名,利用函數(shù)的信息來(lái)簡(jiǎn)化函數(shù)參數(shù)的命名。
public class User { private String userName; private String name;//借助User對(duì)象上下文簡(jiǎn)化命名 public void uploadUserAvatarImageToAliyun(String userAvatarImageUri); public void uploadUserAvatarImageToAliyun(String imageUri); // 借助函數(shù)上下文簡(jiǎn)化命名 } 命名要可讀、可搜索。不要使用生僻的、不好讀的英文單詞來(lái)命名。
命名要符合項(xiàng)目的統(tǒng)一規(guī)范,也不要用些反直覺(jué)的命名。
接口有兩種命名方式:一種是在接口中帶前綴“I”;另一種是在接口的實(shí)現(xiàn)類中帶后綴“Impl”。對(duì)于抽象類的命名,也有兩種方式,一種是帶上前綴“Abstract”,一種是不帶前綴。這兩種命名方式都可以,關(guān)鍵是要在項(xiàng)目中統(tǒng)一。
注釋
-
注釋的內(nèi)容主要包含這樣三個(gè)方面:做什么、為什么、怎么做。對(duì)于一些復(fù)雜的類和接口,我們可能還需要寫(xiě)明“如何用”。
/** * (what) Bean factory to create beans. * * (why) The class likes Spring IOC framework, but is more lightweight. * * (how) Create objects from different sources sequentially: * user specified object > SPI > configuration > default object. */ public class BeansFactory { // ... } -
類和函數(shù)一定要寫(xiě)注釋,而且要寫(xiě)得盡可能全面詳細(xì)。函數(shù)內(nèi)部的注釋要相對(duì)少一些,一般都是靠好的命名、提煉函數(shù)、解釋性變量、總結(jié)性注釋來(lái)提高代碼可讀性。
/** * 密碼校驗(yàn) * * 對(duì)于邏輯比較復(fù)雜的代碼或者比較長(zhǎng)的函數(shù),如果不好提煉、不好拆分成小的函數(shù)調(diào)用, * 那我們可以借助總結(jié)性的注釋來(lái)讓代碼結(jié)構(gòu)更清晰、更有條理。 * * @param password * @return */ 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,A~Z for (int i = 0; i < length; i++) { char c = password.charAt(i); if(!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')){ return false; } } return true; }
代碼
- 風(fēng)格函數(shù)、類多大才合適?函數(shù)的代碼行數(shù)不要超過(guò)一屏幕的大小,比如 50 行。類的大小限制比較難確定。
- 一行代碼多長(zhǎng)最合適?最好不要超過(guò) IDE 的顯示寬度。當(dāng)然,也不能太小,否則會(huì)導(dǎo)致很多稍微長(zhǎng)點(diǎn)的語(yǔ)句被折成兩行,也會(huì)影響到代碼的整潔,不利于閱讀。善用空行分割單元塊。
- 對(duì)于比較長(zhǎng)的函數(shù),為了讓邏輯更加清晰,可以使用空行來(lái)分割各個(gè)代碼塊。四格縮進(jìn)還是兩格縮進(jìn)?我個(gè)人比較推薦使用兩格縮進(jìn),這樣可以節(jié)省空間,尤其是在代碼嵌套層次比較深的情況下。不管是用兩格縮進(jìn)還是四格縮進(jìn),一定不要用 tab 鍵縮進(jìn)。
- 大括號(hào)是否要另起一行?將大括號(hào)放到跟上一條語(yǔ)句同一行,可以節(jié)省代碼行數(shù)。但是將大括號(hào)另起新的一行的方式,左右括號(hào)可以垂直對(duì)齊,哪些代碼屬于哪一個(gè)代碼塊,更加一目了然。
- 類中成員怎么排列?在 Google Java 編程規(guī)范中,依賴類按照字母序從小到大排列。類中先寫(xiě)成員變量后寫(xiě)函數(shù)。成員變量之間或函數(shù)之間,先寫(xiě)靜態(tài)成員變量或函數(shù),后寫(xiě)普通變量或函數(shù),并且按照作用域大小依次排列。
編碼技巧
-
將復(fù)雜的邏輯提煉拆分成函數(shù)和類。
// 1. 把代碼分割成更小的單元塊 也可理解為 將復(fù)雜的邏輯提煉拆分成函數(shù)和類 public void invest(long userId, long financialProductId){ //判斷當(dāng)前時(shí)間是否為本月最后一天 Calendar instance = Calendar.getInstance(); instance.setTime(new Date()); instance.set(Calendar.DATE,(instance.get(Calendar.DATE)+1)); if(instance.get(Calendar.DAY_OF_MONTH) == 1){ return; } } //優(yōu)化后代碼 public void invest(long userId, long financialProductId){ //判斷當(dāng)前時(shí)間是否為本月最后一天 if(isLastDayOfMonth(new Date())){ return; } } private boolean isLastDayOfMonth(Date date){ Calendar instance = Calendar.getInstance(); instance.setTime(date); instance.set(Calendar.DATE,(instance.get(Calendar.DATE)+1)); return instance.get(Calendar.DAY_OF_MONTH) == 1; } -
通過(guò)拆分成多個(gè)函數(shù)或?qū)?shù)封裝為對(duì)象的方式,來(lái)處理參數(shù)過(guò)多的情況。
public class User { public User getUser(String userName, String telephone, String email) { return null; } // 方法一:根據(jù)函數(shù)職責(zé)單一的特性,將函數(shù)拆分成多個(gè) public User getUsetByName(String name) {return null;} public User getUsetByTelephone(String Telephone) {return null;} public User getUsetByEmail(String email) {return null;} // 場(chǎng)景二: 將參數(shù)封裝為對(duì)象的方式 public User getUser(UserReqest userReqest) { return null; } } @Data class UserReqest{ private String userName; private String telephone; private String email; } 函數(shù)設(shè)計(jì)要職責(zé)單一。
-
移除過(guò)深的嵌套層次,方法包括:去掉多余的 if 或 else 語(yǔ)句,使用 continue、break、return 關(guān)鍵字提前退出嵌套,調(diào)整執(zhí)行順序來(lái)減少嵌套,將部分嵌套邏輯抽象成函數(shù)。
/** * KeywordDemo類 * 編程規(guī)范之移除過(guò)深的嵌套層次 * */ public class KeywordDemo { //案例一:去掉多余的 if 或 else 語(yǔ)句 public List<String> matchStrings(List<String> list, String word) { List<String> matchedList = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (String string : list) { if (string != null) { if (string.contains(word)) { // 跟上面的if 合并在一起。 matchedList.add(string); } } } } return matchedList; } //案例二:使用編程語(yǔ)言提供的 continue、break、return 關(guān)鍵字,提前退出嵌套。 //重構(gòu)前代碼。 public List<String> matchStrings_old2(List<String> list, String word) { List<String> matchedList = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (String string : list) { if (string != null && string.contains(word)) { matchedList.add(string); } } } return matchedList; } //重構(gòu)后代碼。 public List<String> matchStrings_new(List<String> list, String word) { List<String> matchedList = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (String string : list) { if (string == null || !(string.contains(word))) { continue; // 使用continue提前退出循環(huán)。 } matchedList.add(string); } } return matchedList; } //案例三:調(diào)整執(zhí)行順序來(lái)減少嵌套 //重構(gòu)前代碼 public List<String> matchStrings_old3(List<String> list, String word) { List<String> matchedList = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (String string : list) { if (string == null || !(string.contains(word))) { continue; // 使用continue提前退出循環(huán)。 } matchedList.add(string); } } return matchedList; } //重構(gòu)后代碼 public List<String> matchStrings_new3(List<String> list, String word) { if (list == null || list.isEmpty()) { // 先判空 return Collections.emptyList(); } List<String> matchedList = new ArrayList<>(); for (String string : list) { if (string == null || !(string.contains(word))) { continue; // 使用continue提前退出循環(huán)。 } matchedList.add(string); } return matchedList; } //案例四:將部分代碼封裝成函數(shù) //重構(gòu)前代碼 public List<String> appendSalts(List<String> passwords){ if(passwords == null && passwords.isEmpty()){ return Collections.emptyList(); } List<String> passwordSalts = new ArrayList(); for (String password : passwords) { if(password == null){ continue; } if(password.length() < 8){ //執(zhí)行長(zhǎng)度 < 8的邏輯 }else{ //執(zhí)行長(zhǎng)度 > 8的邏輯 } } return passwords; } //重構(gòu)后的代碼 public List<String> appendSalts_new(List<String> passwords){ if(passwords == null && passwords.isEmpty()){ return Collections.emptyList(); } List<String> passwordSalts = new ArrayList(); for (String password : passwords) { //將代碼封裝成函數(shù) passwordSalts.add(appendSalt(password)); } return passwords; } private String appendSalt(String password) { if (password.length() < 8) { //執(zhí)行長(zhǎng)度 < 8的邏輯 } else { //執(zhí)行長(zhǎng)度 > 8的邏輯 } return password; } } -
用字面常量取代魔法數(shù)。
//案例一:常量取代魔法數(shù)字 //重構(gòu)前代碼 public double CalculateCircularArea(double radius){ return (3.1415) * radius * radius; } //重構(gòu)后代碼 private static final double PI = 3.1415; public double CalculateCircularArea_new(double radius){ return PI * radius *radius; } -
用解釋性變量來(lái)解釋復(fù)雜表達(dá)式,以此提高代碼可讀性。
//案例二:解釋性變量來(lái)解釋復(fù)雜表達(dá)式。 private static final Date SUMMER_START = null; private static final Date SUMMER_END = null; public boolean validateDate(Date date){ if(date.after(SUMMER_START) && date.before(SUMMER_END)){ return true; } return false; } //重構(gòu)后代碼 public boolean validateDate_new(Date date){ // 引入解釋性變量 if(isSummer(date)){ return true; } return false; } private boolean isSummer(Date date) { return date.after(SUMMER_START) && date.before(SUMMER_END); }
統(tǒng)一編碼規(guī)范
項(xiàng)目、團(tuán)隊(duì),甚至公司,一定要制定統(tǒng)一的編碼規(guī)范,并且通過(guò) Code Review 督促執(zhí)行,這對(duì)提高代碼質(zhì)量有立竿見(jiàn)影的效果。
-- 來(lái)源王爭(zhēng)老師的《設(shè)計(jì)模式之美》