作者:
江南白衣
像工匠一樣進(jìn)行重構(gòu), 讓重構(gòu)成為一門手藝.
Martin Fowler的《Refactoring》其實(shí)更適合做一本關(guān)于重構(gòu)的洗腦,宣言式的書,就像Kent Beck的《XP Explain》一樣薄薄的就可以了。只可惜他卻非常的厚,后面的重構(gòu)名錄都是寫給小白看的。所以我更喜歡《Refacoring WorkBook》,以一個工匠的語氣(沉默寡言而實(shí)要)傳授重構(gòu)的手藝。
1.重構(gòu) Between Classes
〈Design pattern〉有半數(shù)篇幅教育大家不能只靠繼承,要善用組合/委托。重構(gòu)里面其實(shí)也有很多事情靠把繼承變成委托來解決。
1.1繼承
1.1.1 并行繼承體系,組合爆炸
這在以前是個頭痛的問題,現(xiàn)在都已習(xí)慣用委托。
另外java還有個不是很讓人滿意的接口機(jī)制解決并行繼承。
1.1.2 父子類的關(guān)系
比如過于親密,子類會亂修改父類的方法,訪問父類的變量,這時候可以定義final的Template方法。
還有拒絕的饋贈,我暫時還沒有在這上面遇到問題,作者也建議如果沒事就由他,如果有事,就要費(fèi)勁的move method ;或者子類不繼承父類,而只是組合父類。
1.2職責(zé)
經(jīng)過很多次重構(gòu)之后,我發(fā)現(xiàn),其實(shí)哪個方法應(yīng)該放在哪個類其實(shí)很主觀的,你每天醒來都能想到一個理由讓一個方法搬一下家,所以我現(xiàn)在已經(jīng)放棄追求一種“對”的職責(zé)分配了,看著順眼就行。
1.3散彈式修改
作一個修改就要改N個類時,也沒什么特別好方法,就是找找看,有沒有能為這個修改負(fù)責(zé)的統(tǒng)管全局的類。
但現(xiàn)在的很多散彈式修改是分層做成的。
1.4庫類
OpenSource的類庫,總有些時候會想要擴(kuò)展
1.如果只是一兩個方法,直接在客戶代碼里擴(kuò)展,
2.否則自己多一個類庫的子類
3.最費(fèi)勁就是引入一個新的層
題外話,重構(gòu)其實(shí)很依賴工具,和對全部代碼的擁有度,嘩一下就來個全項(xiàng)目的rename。當(dāng)你設(shè)計庫類時,你并不一定擁有使用這些庫類的客戶代碼了,因此一開始就要認(rèn)真設(shè)計,不能依賴重構(gòu),改接口會讓人K死的。
2.重構(gòu) Within Classes
2.1 大是罪
Long Method、Large Class、Long Parameter List, 一般通過度量工具找出來,還可以自己設(shè)定一個觸發(fā)器,當(dāng)度量值超過某個限度時就報警。
PMD可以實(shí)現(xiàn)這個功能,但度量工具我更喜歡Metrics Reload,一個IDEA的插件,給出的度量信息很全面。
但是也切忌為了度量的數(shù)值而重構(gòu)。
Long Method當(dāng)然是嘗試Extract Method。
Large Class就要把類的職能分開幾個域,嘗試拆出另一個Class或者SubClass。
Long Parameter List 可以通過在方法內(nèi)的變量用查詢獲得而不用由參數(shù)傳入;
或者把某些參數(shù)組成一個對象。
1.2 重復(fù)也是罪
重復(fù)在30年前就被認(rèn)為是不好的一樣?xùn)|西,表現(xiàn)在1.代碼類似,2.代碼、接口不同而作用相近。
去除重復(fù)的方法也沒什么特別,無非就是
Extract Method(同一個類)。有差異時,通過參數(shù)化達(dá)到共用。
Pull Up Method到父類(同一個父類)。有差異時,通過模板機(jī)制達(dá)到共用。
Class A調(diào)用Class B 或者 Extract Class C (兩個完全無相干的類)
1.3 命名
中國程序員的英文本來就差,要多參考Ofbiz、Comperie的命名, 盡快建立團(tuán)隊(duì)的項(xiàng)目字典、領(lǐng)域術(shù)語字典。
也幸虧,現(xiàn)在在工具輔助下,代碼的rename是最容易的重構(gòu)。
1.4復(fù)雜條件表達(dá)式 作者認(rèn)為,即使現(xiàn)在Program最關(guān)注的是對象,以及對象間的關(guān)系了,但優(yōu)質(zhì)的內(nèi)部代碼依然重要,推薦《編程珠璣》和《Elements of Programing style》。
化簡復(fù)雜條件的基本有三個方法
1.通過!(A&B)==(!A)||(!B)化簡
2.通過有意義的變量,函數(shù)代替條件表達(dá)式,比如
boolean isMidScore = (X>1000)&&(X<3000);
3.通過把一個if拆分開來執(zhí)行,把guard clause放在前面
比如if(A||B)
do();
->if(A)
do();
if(B)
do();
又可以把2、3靈活組合,比如根據(jù)2,Extract出一個isRight()函數(shù),根據(jù)3
isRight()
{
if(A)
return true;
if(B)
return true;
return false;
}
1.5 其他 沒用的死代碼,通過IDE工具探知并移除。小心有些框架代碼是不能刪除的。
Magic Number,當(dāng)然是改為常量。如果Magic Number很多,可以用Map、枚舉類來存放。
除臭劑式的注釋,為方法,變量改一個更適合的名字。如果注釋是針對一個代碼段的,可以Extract Method。當(dāng)然,代碼只能說明how, 不能說明why,更不能說明why not,這才是注釋存在的地方。