大規(guī)模高層次重構(gòu)
解耦代碼
“解耦”為何如此重要?
過于復(fù)雜的代碼往往在可讀性筐眷、可維護(hù)性上都不友好黎烈。解耦保證代碼松耦合、高內(nèi)聚匀谣,是控制代碼復(fù)雜度的有效手段照棋。代碼高內(nèi)聚、松耦合武翎,也就是意味著烈炭,代碼結(jié)構(gòu)清晰、分層模塊化合理宝恶、依賴關(guān)系簡單符隙、模塊或類之間的耦合小,那代碼整體的質(zhì)量就不會差垫毙。
代碼是否需要“解耦”霹疫?
間接的衡量標(biāo)準(zhǔn)有很多,比如综芥,看修改代碼是否牽一發(fā)而動全身丽蝎。直接的衡量標(biāo)準(zhǔn)是把模塊與模塊、類與類之間的依賴關(guān)系畫出來膀藐,根據(jù)依賴關(guān)系圖的復(fù)雜性來判斷是否需要解耦重構(gòu)屠阻。
如何給代碼“解耦”红省?
給代碼解耦的方法有:封裝與抽象、中間層国觉、模塊化吧恃,以及一些其他的設(shè)計(jì)思想與原則,比如:單一職責(zé)原則麻诀、基于接口而非實(shí)現(xiàn)編程蚜枢、依賴注入、多用組合少用繼承、迪米特法則等。當(dāng)然评架,還有一些設(shè)計(jì)模式埠况,比如觀察者模式。
小規(guī)模低層次重構(gòu)
編碼規(guī)范
命名
命名的關(guān)鍵是能準(zhǔn)確達(dá)意昭殉。對于不同作用域的命名苞七,我們可以適當(dāng)?shù)剡x擇不同的長度。
-
我們可以借助類的信息來簡化屬性挪丢、函數(shù)的命名蹂风,利用函數(shù)的信息來簡化函數(shù)參數(shù)的命名。
public class User { private String userName; private String name;//借助User對象上下文簡化命名 public void uploadUserAvatarImageToAliyun(String userAvatarImageUri); public void uploadUserAvatarImageToAliyun(String imageUri); // 借助函數(shù)上下文簡化命名 }
命名要可讀乾蓬、可搜索惠啄。不要使用生僻的、不好讀的英文單詞來命名任内。
命名要符合項(xiàng)目的統(tǒng)一規(guī)范撵渡,也不要用些反直覺的命名。
接口有兩種命名方式:一種是在接口中帶前綴“I”死嗦;另一種是在接口的實(shí)現(xiàn)類中帶后綴“Impl”趋距。對于抽象類的命名,也有兩種方式越除,一種是帶上前綴“Abstract”节腐,一種是不帶前綴。這兩種命名方式都可以摘盆,關(guān)鍵是要在項(xiàng)目中統(tǒng)一翼雀。
注釋
-
注釋的內(nèi)容主要包含這樣三個方面:做什么、為什么骡澈、怎么做锅纺。對于一些復(fù)雜的類和接口,我們可能還需要寫明“如何用”肋殴。
/** * (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ù)一定要寫注釋囤锉,而且要寫得盡可能全面詳細(xì)坦弟。函數(shù)內(nèi)部的注釋要相對少一些,一般都是靠好的命名官地、提煉函數(shù)酿傍、解釋性變量、總結(jié)性注釋來提高代碼可讀性驱入。
/** * 密碼校驗(yàn) * * 對于邏輯比較復(fù)雜的代碼或者比較長的函數(shù)赤炒,如果不好提煉、不好拆分成小的函數(shù)調(diào)用亏较, * 那我們可以借助總結(jié)性的注釋來讓代碼結(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ù)不要超過一屏幕的大小,比如 50 行巡通。類的大小限制比較難確定尘执。
- 一行代碼多長最合適?最好不要超過 IDE 的顯示寬度宴凉。當(dāng)然誊锭,也不能太小,否則會導(dǎo)致很多稍微長點(diǎn)的語句被折成兩行弥锄,也會影響到代碼的整潔丧靡,不利于閱讀。善用空行分割單元塊籽暇。
- 對于比較長的函數(shù)窘行,為了讓邏輯更加清晰,可以使用空行來分割各個代碼塊图仓。四格縮進(jìn)還是兩格縮進(jìn)罐盔?我個人比較推薦使用兩格縮進(jìn),這樣可以節(jié)省空間救崔,尤其是在代碼嵌套層次比較深的情況下惶看。不管是用兩格縮進(jìn)還是四格縮進(jìn),一定不要用 tab 鍵縮進(jìn)六孵。
- 大括號是否要另起一行纬黎?將大括號放到跟上一條語句同一行,可以節(jié)省代碼行數(shù)劫窒。但是將大括號另起新的一行的方式本今,左右括號可以垂直對齊,哪些代碼屬于哪一個代碼塊,更加一目了然冠息。
- 類中成員怎么排列挪凑?在 Google Java 編程規(guī)范中,依賴類按照字母序從小到大排列逛艰。類中先寫成員變量后寫函數(shù)躏碳。成員變量之間或函數(shù)之間,先寫靜態(tài)成員變量或函數(shù)散怖,后寫普通變量或函數(shù)菇绵,并且按照作用域大小依次排列。
編碼技巧
-
將復(fù)雜的邏輯提煉拆分成函數(shù)和類镇眷。
// 1. 把代碼分割成更小的單元塊 也可理解為 將復(fù)雜的邏輯提煉拆分成函數(shù)和類 public void invest(long userId, long financialProductId){ //判斷當(dāng)前時間是否為本月最后一天 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)前時間是否為本月最后一天 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; }
-
通過拆分成多個函數(shù)或?qū)?shù)封裝為對象的方式咬最,來處理參數(shù)過多的情況。
public class User { public User getUser(String userName, String telephone, String email) { return null; } // 方法一:根據(jù)函數(shù)職責(zé)單一的特性欠动,將函數(shù)拆分成多個 public User getUsetByName(String name) {return null;} public User getUsetByTelephone(String Telephone) {return null;} public User getUsetByEmail(String email) {return null;} // 場景二: 將參數(shù)封裝為對象的方式 public User getUser(UserReqest userReqest) { return null; } } @Data class UserReqest{ private String userName; private String telephone; private String email; }
函數(shù)設(shè)計(jì)要職責(zé)單一丹诀。
-
移除過深的嵌套層次,方法包括:去掉多余的 if 或 else 語句翁垂,使用 continue、break硝桩、return 關(guān)鍵字提前退出嵌套沿猜,調(diào)整執(zhí)行順序來減少嵌套,將部分嵌套邏輯抽象成函數(shù)碗脊。
/** * KeywordDemo類 * 編程規(guī)范之移除過深的嵌套層次 * */ public class KeywordDemo { //案例一:去掉多余的 if 或 else 語句 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; } //案例二:使用編程語言提供的 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í)行順序來減少嵌套 //重構(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í)行長度 < 8的邏輯 }else{ //執(zhí)行長度 > 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í)行長度 < 8的邏輯 } else { //執(zhí)行長度 > 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; }
-
用解釋性變量來解釋復(fù)雜表達(dá)式丸逸,以此提高代碼可讀性蹋艺。
//案例二:解釋性變量來解釋復(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ī)范,并且通過 Code Review 督促執(zhí)行涛救,這對提高代碼質(zhì)量有立竿見影的效果畏邢。
-- 來源王爭老師的《設(shè)計(jì)模式之美》