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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者州叠。
  • 序言:七十年代末棵红,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子咧栗,更是在濱河造成了極大的恐慌逆甜,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件致板,死亡現(xiàn)場離奇詭異交煞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)斟或,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門素征,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萝挤,你說我怎么就攤上這事御毅。” “怎么了怜珍?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵端蛆,是天一觀的道長。 經(jīng)常有香客問我酥泛,道長今豆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任柔袁,我火速辦了婚禮呆躲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捶索。我一直安慰自己插掂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布腥例。 她就那樣靜靜地躺著燥筷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪院崇。 梳的紋絲不亂的頭發(fā)上肆氓,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音底瓣,去河邊找鬼谢揪。 笑死蕉陋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拨扶。 我是一名探鬼主播凳鬓,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼患民!你這毒婦竟也來了缩举?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤匹颤,失蹤者是張志新(化名)和其女友劉穎仅孩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體印蓖,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辽慕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赦肃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溅蛉。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖他宛,靈堂內(nèi)的尸體忽然破棺而出船侧,到底是詐尸還是另有隱情,我是刑警寧澤厅各,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布镜撩,位于F島的核電站,受9級特大地震影響讯检,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卫旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一人灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顾翼,春花似錦投放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拜姿,卻和暖如春烙样,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕊肥。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工谒获, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓批狱,卻偏偏與公主長得像裸准,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赔硫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容