重构,体现一个工程师的基本素养和底蕴(细节篇)
重構(gòu)小記(重構(gòu),改善既有代碼的設(shè)計(jì)讀后總結(jié))
方法級別
提煉函數(shù):
將一個大方法,拆成多個小方法,難點(diǎn)在于小方法的命名。
假如有早上上學(xué)的一個大方法,
那么就應(yīng)該在里面有起床,穿衣服,吃早點(diǎn)等小方法,
而起床這個方法又可以分為穿衣服,穿鞋,疊被子等方法,
而穿衣服又有穿內(nèi)衣,穿外衣等步驟。
內(nèi)聯(lián)函數(shù)
一個太小的方法,里面只做了一個動作,就可以考慮去掉了,去掉會使代碼更清晰。
感覺內(nèi)聯(lián)函數(shù)說的就是不要過度的提煉函數(shù)。
內(nèi)聯(lián)臨時變量
就是說要盡量去掉只用一次的臨時變量
注意:如何判斷臨時變量只被賦值了一次,就是在前面加flnal,能編譯過就是只有一次
比如有代碼:int age = user.getAge();return age>18;
那么這兩句代碼,就完全可以合成一句:return user.getAge()>18;
以查詢?nèi)〈R時變量
如果臨時變量被用了多次的話,也盡量用方法去代替臨時變量。
比如有代碼:
int sum = a + b;
if(sum>10){
return sum2;
}
else{
return sum3;
}
這里的sum變量,就要用方法封裝后去掉。變成以下:
int getSum(){
return a + b;
}
if(getSum()>10){
return getSum()*2;
}
else{
return getSum()*3;
}
這種不一定就是最正確的,甚至有時候會反過來改,所以把握整體很關(guān)鍵。
這樣可能會對性能產(chǎn)生一點(diǎn)影響,但幾乎也可以忽略不計(jì)。
引入解釋型變量
用變量名來簡化復(fù)雜表達(dá)式
比如有代碼:if(a > 0 && aa > 0 && aaa> 0 && b < 0 && bb < 0 && bbb < 0){return true;}
這個if中的判斷表達(dá)式就太復(fù)雜,可改成下面這樣:
boolean aClassMoreThanZero = a > 0 && aa > 0 && aaa> 0;//a類變量大于0
boolean bClassLessThanZero = b < 0 && bb < 0 && bbb < 0;//b類變量小于0
if(aClassMoreThanZero && bClassLessThanZero){return true;}
分解臨時變量
臨時變量被賦值多次,并且是不同的意思,就要將臨時變量分解成多個變量,保證一個臨時變量只代表一個意思。
移除對參數(shù)的賦值
一個方法中,盡量不要對入?yún)⑦M(jìn)行賦值,可以嘗試將入?yún)⒂胒inal修飾
以函數(shù)對象取代函數(shù)
如果方法中局部變量太多,就創(chuàng)建一個函數(shù)對象,把所有的局部變量變成這個對象的屬性,然后把這個方法的實(shí)現(xiàn)全部移到對象中
說實(shí)話,我個人覺得這種方式?jīng)]什么用,因?yàn)槿绻阌袝r間這樣搞,還不如換個思路梳理下這個方法,不過也可能是我沒遇到這樣情況。
替換算法
替換一個方法的實(shí)現(xiàn),重點(diǎn)在于替換前后要保證執(zhí)行結(jié)果一致。
比如有個方法,是冒泡算法實(shí)現(xiàn)排序,那么就可以用插入排序來替代冒泡排序。
對象級別
搬移函數(shù)
一個類中有個函數(shù),卻與另一個類進(jìn)行更多的交流,那么就嘗試將這個函數(shù)整體遷移過去。
搬移字段
一個類中有某個字段,被另一個類更多的用到,那么就嘗試將這個字段移到另一個類中。
提煉類
如果一個類越來越大,就可以考慮將其中的一部分與類名關(guān)系不大的屬性抽出來,做成這個類的子類。
將類內(nèi)聯(lián)化
與提煉類相反,如果一個類沒有做太多事情,就可以嘗試將他與另一個類合并。
隱藏委托關(guān)系
隱藏真實(shí)的調(diào)用關(guān)系,將真實(shí)的調(diào)用關(guān)系封裝在最外層的方法中。
比如有個A類,A中有個B類屬性,B中又有個C類。
如果想通過A獲得C的值,普通的做法就是a.getB().getC();但是這樣就把真實(shí)的C暴露出來了,其實(shí)調(diào)用方也根本沒必要知道這個,這時候就可以這樣做:
在A類中添加一個方法,叫g(shù)etC(),方法的實(shí)現(xiàn)就是return b.getC();這樣就相關(guān)與把B給隱藏了;
這時如果再想通過A獲得C,那么就可以直接a.getC();這就是隱藏委托關(guān)系。
移除中間人
這個是隱藏委托關(guān)系的反向重構(gòu),也就是說當(dāng)委托的太多的時候,就應(yīng)該考慮是不是應(yīng)該直接點(diǎn),物極必反。
相當(dāng)于把上個例子的a.getC()又改成a.getB().getC();
引入外加函數(shù)
對于有些引入的源碼類,如果你想增強(qiáng)它,肯定不能直接修改源碼,那么就需要引入外加函數(shù)。
感覺這個就是對源碼的增強(qiáng),通過新建一個函數(shù),返回增強(qiáng)后的內(nèi)容,以后使用的時候就直接用這個函數(shù)就好了。
引入本地?cái)U(kuò)展
這個是引入外加函數(shù)的增強(qiáng)版
如果需要對源碼進(jìn)行很多增強(qiáng),那么就可以這樣做
方案一:新建一個類(子類),繼承源碼類,新增強(qiáng)的功能就可以放在子類中
方案二:新建一個類(包裝類),將源碼類當(dāng)成這個新類的屬性,那么也可以完成和方案一一樣的效果
重新組織數(shù)據(jù)
自封裝字段
屬性私有化,通過get/set方法取值/設(shè)置值,而不是直接對值進(jìn)行操作
以對象取代數(shù)據(jù)值
屬性使用對象,這是一個過程,比如剛開始是String,后面慢慢的感覺還要一些字段,而這些字段又是一類數(shù)據(jù),那么就將這一類數(shù)據(jù)抽成一個類來代替原來分散的數(shù)據(jù)。
將值對象改為引用對象
將一個類衍生出的彼此相同的許多實(shí)例,使用工廠替換成同一個實(shí)例,并將原類的構(gòu)造方法私有化,禁止直接構(gòu)造。
比如員工是個類,類中有個屬性是老板這個類,那么多個員工的老板,其實(shí)就是同一個,這時候就要用這種方式,先將老板類的構(gòu)造方法私有化。
然后可以先在靜態(tài)塊中將老板類創(chuàng)建好,然后每new一個新員工的時候,老板類的賦值就用這個創(chuàng)建好的老板實(shí)例就行。
將引用對象改為值對象
如果引用對象太小,且不可變,難以管理,就可以考慮將上面的方式反過來了
以對象取代數(shù)組
如果數(shù)組中有多個不同的類型,復(fù)雜到不能讓人很快的明白哪個是什么意思,就該用對象來代替了
比如數(shù)組=[“小明”,20,175,70];不是很容易就看出來這個數(shù)組中每個的意思,這時就要用對象代替,那么屬性就分別是名字,年齡,身高,體重。
以字面常量取代魔法值
任何一個數(shù)字,都不應(yīng)該憑空出現(xiàn),需要在類前將它設(shè)置成常量,然后再去引用,這樣才能使代碼變的清晰易懂。
以類取代類型碼
1、2、3、4等類型碼,不能很清楚的表達(dá)意思,這時候就應(yīng)該用類來代替這些類型碼。
Lei.A.getCode(),代替原來的1;
Lei.B.getCode(),代替原來的2;…
以子類取代類型碼
類中多個類型碼,可以使用子類,每個子類對應(yīng)一個類型碼。
使用的時候,根據(jù)不同的條件,原來是返回不同的類型碼,現(xiàn)在改為返回不同的子類。
以state/Strategy取代類型碼
如果類由于其他原因,不能被繼承,那么就需要新建一個狀態(tài)類,然后不同的子類去繼承這個狀態(tài)類,還是一個狀態(tài)對應(yīng)一個子類。
然后這個狀態(tài)類中建立一個查詢方法,返回狀態(tài)碼,各個子類重寫這個方法,返回具體的狀態(tài)碼。
以字段代替子類
如果子類差別不大,甚至只是在返回常量數(shù)據(jù)不一樣,那么就要考慮刪掉子類了,原子類的功能,完全可以通過不同的方法來代替。
簡化條件表達(dá)式
分解條件表達(dá)式
表達(dá)式太多,就會分不清每個的意思,這時候就應(yīng)該把表達(dá)式按照意思區(qū)分成小塊,每個小塊通過命名來區(qū)分具體的意思。
合并條件表達(dá)式
如果多個表達(dá)式,返回同一個結(jié)果,就要考慮合并成一個表達(dá)式。
合并重復(fù)的條件片段
如果每個表達(dá)式的分支上都有著相同的代碼塊,那么這就是重復(fù)代碼,我們就應(yīng)該把這塊代碼提煉出來。
移除控制標(biāo)記
就是類似于if(怎么樣)就停止 這樣類似的控制標(biāo)記,可以用break,continue,return等替代,多用在循環(huán)體中。
以衛(wèi)語句取代嵌套的條件表達(dá)式
盡量避免嵌套的條件表達(dá)式
比如if(){}else{if(){}else{if(){}}}
盡量改成if(){};if(){};if(){};…
以多態(tài)取代條件表達(dá)式
將條件表達(dá)式的每個分支放在每個子類重寫的方法中,原始方法變抽象
多態(tài)就是根據(jù)對象的不同類型采取不同的行為。
引入斷言
斷言是一個條件表達(dá)式,應(yīng)該總是為真,如果失敗,則說明程序員犯了錯誤。
Assert類有很多方法,常用的是Assert.isTrue(XXX);
斷言不會改變程序任何行為,但能幫助程序員理解代碼正確運(yùn)行的必要條件。
簡化函數(shù)的調(diào)用
函數(shù)改名
函數(shù)的名字至關(guān)重要,代碼首先是為人寫的,其次才是為機(jī)器寫的
添加參數(shù)
添加參數(shù)要謹(jǐn)慎,實(shí)在沒辦法的情況下,才會考慮添加參數(shù)。
去除參數(shù)
如果某個參數(shù)確實(shí)沒用了,記得趕緊去掉,不要抱著不去也不會出問題的心態(tài),否則這樣只會讓程序越來越復(fù)雜。
去除參數(shù)的時候,還要注意多態(tài)的影響。
將查詢函數(shù)和修改函數(shù)分離
一個函數(shù)只完成一個職責(zé),要么查詢,要么修改。
另函數(shù)攜帶參數(shù)
統(tǒng)一合并類似功能的函數(shù)
如果多個函數(shù)只是部分值不一樣,做的確實(shí)一類事情,那么就要考慮合并成一個函數(shù),并把不同的值當(dāng)成參數(shù)傳進(jìn)來。
以明確函數(shù)去掉參數(shù)
與上面的相反,如果一個函數(shù)中根據(jù)入?yún)⒉煌?#xff0c;執(zhí)行的邏輯已經(jīng)完全不同了,那么就將他們分開成各自的函數(shù)。
保持對象完整性
如果入?yún)⑹悄硞€對象的多個屬性時,考慮用這個對象整體作為入?yún)?#xff0c;來代替多個參數(shù)。
以函數(shù)取代參數(shù)
如果函數(shù)的參數(shù)可以通過函數(shù)內(nèi)部通過其他方法得到,就不要使用參數(shù)來傳遞。
盡量的簡化函數(shù)的參數(shù),因?yàn)樘嗟膮?shù)會讓函數(shù)變的復(fù)雜。
引入?yún)?shù)對象
如果一些參數(shù)總是很自然的同時出現(xiàn),那么就要考慮是不是用一個對象來代替這些參數(shù)了
移除設(shè)值函數(shù)
如果一個字段在函數(shù)過程中需要被修改,就為它提供一個修改的函數(shù)
如果這個字段在函數(shù)過程中不會被修改,就刪掉修改它的函數(shù),同時用final修飾。
隱藏函數(shù)
一個函數(shù)從沒有被其他類用到,就用private修飾它
以工廠函數(shù)取代構(gòu)造函數(shù)
如果創(chuàng)建對象時,需要根據(jù)不同的條件創(chuàng)建不同的對象,可以考慮寫個工廠方法來代替構(gòu)造方法
封裝向下轉(zhuǎn)型
函數(shù)的返回值,不要設(shè)置成Object等原始類型,最好指定類型,免得調(diào)用方又要強(qiáng)制轉(zhuǎn)型。
以異常取代錯誤碼
就是自定義異常,并合理的利用它
以測試取代異常
就是方法一開始,要對入?yún)⑦M(jìn)行校驗(yàn),看是否是后面程序可以接受的參數(shù)
比如如果為空,要怎么樣,如果為0,要怎么樣,這些能想到的要提前抓到并做合理的處理。最常見的就是判空。
處理概括關(guān)系(繼承關(guān)系)
字段上移
子類有相同作用的字段,應(yīng)該上移到他們的父類中。
字段下移
與上面相反,如果父類的某些字段,只與部分某些子類有關(guān),那么就應(yīng)該將這些字段下移到對應(yīng)的子類中。
函數(shù)上移
子類中有相同作用的函數(shù),應(yīng)該將這個函數(shù)上移到他們的父類中。
函數(shù)下移
與上面相反,如果父類的某些函數(shù),只與部分某些子類有關(guān),那么就應(yīng)該將這些函數(shù)下移到對應(yīng)的子類中。
####構(gòu)造函數(shù)本體上移
各個子類中的一些構(gòu)造函數(shù),如果他們的本體幾乎完全一致,那么就在父類中建一個構(gòu)造函數(shù),然后在子類中調(diào)父類,類似于super(id,name)
提煉子類
如果類中的某些屬性,只被某些實(shí)例用到,那么就新建個子類,將這些屬性移到子類中。
這樣保證不用這些屬性的實(shí)例,用父類new;用到這些屬性的實(shí)例,用子類new。
提煉超類
如果兩個類有相似的特性,就新建一個他們的父類,將相同特性移到父類上去,避免代碼的重復(fù)。
提煉接口
若干類中有相同的子集,可以考慮新建接口讓這些類繼承,然后統(tǒng)一相同的部分,調(diào)用也改成接口調(diào)用
折疊繼承體系
如果子類和父類沒有太大區(qū)別,就將他們合為一體
塑造模板函數(shù)
詳見設(shè)計(jì)模式的模板模式,就是在抽象父類中定義好抽象函數(shù),再寫個方法A,寫明各個抽象函數(shù)的調(diào)用順序。
那么子類中只需要重寫父類的抽象方法,用的時候,子類調(diào)用方法A,就能按照父類方法A中的調(diào)用順序來調(diào)用各自的實(shí)現(xiàn)了。
以委托取代繼承
如果父類中的很多屬性和方法,子類都用不到,那么就可以放棄繼承,在子類中引入一個原父類的屬性引用即可。
以繼承取代委托
與上面的相反,如果兩個類之間的委托調(diào)用的太多,甚至幾乎是全部了,那么就該考慮讓委托的這個類作為父類了,之間繼承會更好。
大型重構(gòu)
梳理并分解繼承體系
某個繼承體系同時承擔(dān)兩項(xiàng)責(zé)任或多項(xiàng)責(zé)任時,考慮拆開。
比如原來有父類A,A有兩個子類B和C,B還有個子類D,C還有個子類E。B和C都承擔(dān)同類責(zé)任,而D和E卻承擔(dān)另外一個不同的責(zé)任。
此時就應(yīng)該把BC和DE分開了。我們建立兩個繼承體系A(chǔ)和F,A擁有子類BC,F擁有子類DE,通過委托關(guān)系(A作為F的屬性)讓其中一個F調(diào)用另外一個A。
將過程化設(shè)計(jì)轉(zhuǎn)為對象設(shè)計(jì)
其實(shí)就是盡量的面向?qū)ο?#xff0c;將面向過程變成轉(zhuǎn)成面向?qū)ο缶幊獭?/p>
將領(lǐng)域和表述/顯示分離
與界面展示相關(guān)的邏輯的類,和純業(yè)務(wù)邏輯的類,要分離。
提煉繼承體系
一個類在演變過程中,所承擔(dān)的責(zé)任越來越多時,就應(yīng)該考慮分離它了,可以采取抽出一部分當(dāng)成子類,使整體變成繼承的體系。
總結(jié)
以上是生活随笔為你收集整理的重构,体现一个工程师的基本素养和底蕴(细节篇)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 抽样_beta分布的采样或抽样
- 下一篇: [Cordova]JS和Native交互