2022-05-01代碼重構(gòu) -- 大小規(guī)模重構(gòu)

大規(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ì)模式之美》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容