跟狗屎一樣的代碼,到底該如何重構(gòu)?
閱讀本文大概需要 18 分鐘。
來自:網(wǎng)絡(luò),侵刪
一、重構(gòu)原則
1、何謂重構(gòu)
2、為何重構(gòu)
良好的設(shè)計是快速開發(fā)的根本,改善設(shè)計、提高可讀性,減少錯誤,這些都是提高質(zhì)量。3、何時重構(gòu)
重構(gòu)應(yīng)該隨時隨地的進行。第一次做某件事情是只管去做;第二次做類似的事情會產(chǎn)生反感;第三次再做類似的事,你就應(yīng)該重構(gòu)
計算機科學是這樣一門科學:它相信所有的問題都可以通過增加一個間接層來解決。
二、代碼的壞味道
1、重復代碼
2、過長的類
擁有短函數(shù)的對象活得比較好、比較長。 間接層所能帶來的全部利益——解釋能力、共享能力、選擇能力——都是由小型函數(shù)支持的。3、過大的類
4、過長參數(shù)列
5、發(fā)散式變化
6、散彈式修改
7、依戀情結(jié)
將數(shù)據(jù)和對數(shù)據(jù)的操作行為包裝在一起函數(shù)對某個類的興趣高過對自己所處類的興趣。某個函數(shù)為了計算某個值,從另一個對象那調(diào)用幾乎半打的取值函數(shù)。判斷哪個類擁有最大被此函數(shù)使用的數(shù)據(jù),然后就把這個函數(shù)和那些數(shù)據(jù)放在一起。8、數(shù)據(jù)泥團
9、基本類型偏執(zhí)
它們模糊了橫旦與基本數(shù)據(jù)和體積較大的類之間的界限10、switch驚悚現(xiàn)身
少用switch語句11、平行集成體系
讓一個繼承體系的實例引用另一個繼承體系的實例。12、冗余類
13、夸夸其談未來性
14、令人迷惑的暫時字段
15、過度耦合消息鏈
16、中間人
17、狎昵關(guān)系
18、異曲同工的類
19、不完美的類庫
20、純稚的數(shù)據(jù)類
21、被拒絕的遺贈
22、過多的注釋
當你感覺需要撰寫注釋時,請先嘗試重構(gòu),試著讓所有的注釋都變得多余。
三、重新組織函數(shù)
1、提煉函數(shù)
只要新函數(shù)的名稱能夠以更好的方式昭示代碼意圖,你也應(yīng)該提煉它。但如果想不到一個更有意義的名稱就別動
2、內(nèi)聯(lián)函數(shù)
如果子類繼承了這個函數(shù),就不要將此函數(shù)內(nèi)聯(lián),因為子類無法復寫一個根本不存在的函數(shù)。
3、內(nèi)聯(lián)臨時變量
將所有對該變量的引用動作,替換為對它賦值的那個表達式自身double basePrice = anOrder.basePrice();
return (base > 10000 );
return (anOrder.basePrice > 1000);
4、以查詢?nèi)〈R時變量
將這個表達式提煉到一個獨立的函數(shù)中。將這個臨時變量的所有引用點替換為對新函數(shù)的調(diào)用。此后,新函數(shù)就可被其他函數(shù)使用。double basePrice = quantity * timePrice;
if(basePrice > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
if(basePrice() > 1000){
return basePrice * 09.5;
} else {
return basePrice * 0.98;
}
double basePrice(){
return quantity * timePrice;
}
5、引入注釋性變量
將該復雜表達式(或其中一部分)的結(jié)果放進一個臨時變量,以此變量名稱來解釋表達式用途。if ((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize >0){
//do smothing
}
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize >0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized){
//do smothing
}
6、分解臨時變量
針對每次賦值,創(chuàng)造一個獨立、對應(yīng)的臨時變量。double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);
7、移除對參數(shù)的賦值
以一個臨時變量取代該參數(shù)的位置。int discount (int inputVal, int quantity, int yearToData){
if(inputVal > 50) inputVal -= 2;
}
int discount (int inputVal, int quantity, int yearToData){
int result = inputVal;
if(inputVal > 50) result -= 2;
}
8、替換算法
將函數(shù)本體替換成為另一個算法。String foundPerson(String[] people){
for(int i = 0;i < people.length; i++){
if(people[i].equals("Don")){
return "Don";
}
if(people[i].equals("John")){
return "John";
}
if(people[i].equals("Kent")){
return "Kent";
}
}
return "";
}
String foundPerson(String[] people){
List candidates = Arrays.asList(new String[]{"Don", "John", "Kent"});
for(int i = 0;i < people.length; i++){
if(candidates.contains(people[i])){
return prople[i];
}
}
return "";
}
四、在對象之間搬移特性
決定把責任放在哪兒是即使不是最重要的事,也是最重要的事之一。搬移函數(shù)和搬移字段簡單地移動對象行為,就可以解決這些問題。如果這兩個重構(gòu)手法都需要用到,我會首先使用搬移字段,再使用搬移方法。提煉類將一部分責任分離出去。如果一個類變得太不負責任,使用將類內(nèi)聯(lián)化將它融入到另一個類中。1、搬移函數(shù)
在該函數(shù)最長引用的類中建立一個有著類似行為的新函數(shù)。將舊函數(shù)變成一個單純的委托函數(shù),或者將舊函數(shù)完全移除。2、搬移字段
在目標類新建一個字段,修改原字段的所有用戶,令他們改用新字段3、提煉類
建立一個新類,將相關(guān)字段和函數(shù)從就類搬到新類。4、將類內(nèi)聯(lián)化
將這個類的所有特性搬移到另一個類中,然后移除原類。5、隱藏“委托關(guān)系”
在服務(wù)類上建立客戶所需要的所有函數(shù),用來隱藏委托關(guān)系。封裝意味每個對象都應(yīng)該少了解系統(tǒng)的其他部分。一旦發(fā)生變化,需要了解這一變化的對象就會比較少。6、移除中間人
讓客戶直接調(diào)用委托類。7、引入外加函數(shù)
在客戶類中建立一個函數(shù),并以第一參數(shù)形式傳入一個服務(wù)類實例。Date newStart = new Date(year, month, date + 1);
Date newStart = nextDay(nowDate);
private static Date nextDay(Date arg){
retrun new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
8、引入本地擴展
建立一個新類,使它包含這些額外函數(shù)。讓這個擴展品成為源類的子類或包裝類。五、重新組織數(shù)據(jù)
1、自封裝字段
為這個字段建立取值/設(shè)值函數(shù),并且只以這些函數(shù)來訪問字段。private int low, high;
boolean includes(int arg){
retrun arg >= low && arg <= high;
}
private int low, high;
boolean includes(int arg){
retrun arg >= getLow() && arg <= getHigh();
}
int getLow(){
retrun low;
}
int getHigh(){
return high;
}
2、以對象取代數(shù)據(jù)值
將數(shù)據(jù)項變?yōu)閷ο蟆?/code>3、將值對象改為引用對象
將這個值對象變成引用對象。4、將引用對象改為值對象
將它變成一個值對象。5、以對象取代數(shù)組
以對象替換數(shù)組。對于數(shù)組中的每個元素,以一個字段來表示6、復制“被監(jiān)視數(shù)據(jù)”
將該數(shù)據(jù)復制到一個領(lǐng)域?qū)ο笾小=⒁粋€Observer模式,用以同步領(lǐng)域?qū)ο蠛虶UI對象內(nèi)的重復數(shù)據(jù)。7、將單向關(guān)聯(lián)改為雙向關(guān)聯(lián)
添加一個反向指針,并使修改函數(shù)能夠同時更新兩條連接。8、將雙向關(guān)聯(lián)改為單向關(guān)聯(lián)
去除不必要的關(guān)聯(lián)。9、以字面常量取代魔數(shù)
創(chuàng)造一個常量,根據(jù)其意義為它命名,并將上述的字面數(shù)值替換為常量。10、封裝字段
將它聲明為private,并提供相應(yīng)的訪問函數(shù)。11、封裝集合
讓這個函數(shù)返回該集合的一個只讀副本,并在這個類中提供添加/移除集合元素的函數(shù)。六、簡化條件表達式
1、分解條件表達式
從if、then、else三個段落中分別提煉出獨立函數(shù)。2、合并表達式
將這些測試合并為一個條件表達式,并將這個條件表達式提煉成一個獨立函數(shù)。3、合并重復的條件代碼
將這段重復代碼搬移到條件表達式之外。4、移除控制標記
以break/return語句取代控制標記。5、以多態(tài)取代條件表達式
將這個條件表達式的每個分支放進一個子類內(nèi)的覆寫函數(shù)中,然后將原始函數(shù)聲明為抽象函數(shù)七、簡化函數(shù)調(diào)用
1、函數(shù)改名
修改函數(shù)名稱。2、添加參數(shù)
為此函數(shù)添加一個對象參數(shù),讓該對象帶僅函數(shù)所需信息。3、移除參數(shù)
去除參數(shù)。4、分離查詢函數(shù)和修改函數(shù)
建立兩個不同函數(shù),其中一個負責查詢,另一個負責修改。5、令函數(shù)攜帶參數(shù)
建立單一函數(shù),以參數(shù)表達那些不同的值。6、以明確函數(shù)取代參數(shù)
針對該參數(shù)的每一個可能值,建立一個獨立函數(shù)。7、保持對象完整
改為傳遞整個對象。8、以函數(shù)取代參數(shù)
讓參數(shù)接受者去除該參數(shù),直接調(diào)用前一個函數(shù)。9、引入?yún)?shù)對象
以一個對象取代這些參數(shù)。10、移除設(shè)值函數(shù)
去掉該字段的所有設(shè)值函數(shù)。11、隱藏函數(shù)
將函數(shù)修改為private。12 、以工廠函數(shù)取代構(gòu)造函數(shù)
將構(gòu)造函數(shù)替換為工廠函數(shù)。八、處理概括關(guān)系
1、字段上移
將該字段移至超類。2 、函數(shù)上移
將該函數(shù)移至超類。3 、構(gòu)造函數(shù)本體上移
在超類中新建一個構(gòu)造函數(shù),并在子類構(gòu)造函數(shù)中調(diào)用它。4、函數(shù)下移
將函數(shù)移到相關(guān)的子類中。5、字段下移
將字段移到需要它的子類中。6、提煉子類
新建一個子類,將上述部分的特性移到子類中。7、提煉超類
為這兩個類建立一個超類,將相同特性移至超類。8、提煉接口
將相同的子集提煉到一個獨立接口中。9、折疊繼承體系
將它們合為一體。10、塑造模板函數(shù)
將操作放進獨立函數(shù)(保持簽名相同),然后將它們移至超類。11、以委托取代繼承
子類新建字段保存超類,調(diào)整子類函數(shù)為委托超類,取消繼承關(guān)系。12、以繼承取代委托
推薦閱讀:
20 個實例玩轉(zhuǎn) Java 8 Stream,寫的太好了!
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱 
評論
圖片
表情

