重構改善既有代碼的設計 --方法篇
重新組織數據
-
目的
:解決不易理解的錯綜複雜的長函數,促使代碼更清晰更易維護。 -
解決思路
:長函數變成短函數、算法結構優化 -
常用方法
:抽離函數(Extract Method),通過查詢的方式來獲取值 (Replace Temp With query), 優化算法 (Substitute Algorthm) -
AF老項目中有很多行數過百的函數,導致維護起來很費時費力,很多函數都可以用Extract Method方法,拆成小函數,無論是後期增加邏輯還是要代碼複用都都能幫助理解。另外函數很長的原因的是場景特別多有很多出來了函數,但是這些個處理函數都寫在一個入口中,還是希望用Replace Temp With query方式來處理數據
- 抽離函數(Extract Method)
-
一個函數如果需要寫很多註釋來讓人理解的時候,這個函數應該是可以被拆解成小函數。良好的函數命名可以讓人更容易理解代碼
-
場景說明
let infoArr = Array(10).fill().map((item, index) => {
return {
name: 'VFrank' + index,
age: Math.floor(Math.random() * 10 + 20)
}
});
function printInfo() {
// 輸出一個橫幅
console.log('**********************')
console.log('*****Hello VFrank*****')
console.log('**********************')
// 獲取所有name和age字段數組統計項
let temp = { nameArr: [], ageArr: [] };
infoArr.forEach(item) {
let { name, age } = item;
temp.nameArr.push(name);
temp.ageArr.push(age);
}
// 打印統計數據
console.log(nameArr);
console.log(ageArr);
}
- 上面的printInfo函數,其實每個註釋都可以是獨立的一個函數,這裏說明抽離函數中的幾個問題
- 第一塊註釋,那無對內部變量的引用,可以直接方法抽離爲一個小函數
- 第二塊註釋,屬於數據處理層可以單獨抽離出來,以返回值形式
- 第三快註釋,打印邏輯對內部變量有引用關係,需要以參數形式傳入
修改後如下
let infoArr = Array(10).fill().map((item, index) => {
return {
name: 'VFrank' + index,
age: Math.floor(Math.random() * 10 + 20)
}
});
function printInfo() {
printBanner();
let result = getOutStand(); // 這裏用Replace Temp With query方法修改
printDetail(result);
}
// 無引用內部變量關係直接抽離
function printBanner() {
console.log('**********************')
console.log('*****Hello VFrank*****')
console.log('**********************')
}
// 計算邏輯代碼抽離,
function getOutStand() {
let result = { nameArr: [], ageArr: [] };
infoArr.forEach(item) {
let { name, age } = item;
result.nameArr.push(name);
result.ageArr.push(age);
}
return result;
}
// 對內部變量有引用關係的通過參數傳入
function printDetail(result) {
console.log(result.nameArr);
console.log(result.ageArr);
}
- 通過查詢的方式來獲取值(Replace Temp With query)
-
函數中存在很多臨時變量,這些變量只在這個函數中使用,這個導致一個函數過長而不易理解。換成一個函數可以增加函數的顆粒度,給後續維護帶來極大的幫助
-
場景
// 一個計算價格的函數, 基本價格 * 折扣
getPrice() {
let basePrice = this.quantity * this.itemPrice;
let discountFactor = 0;
if (basePrice > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return basePrice * discountFactor;
}
- 第一次修改把basePrice抽取出來
getPrice() {
let discountFactor = 0;
if (this.getBasePrice() > 1000) {
discountFactor = 0.95;
} else {
discountFactor = 0.98;
}
return this.getBasePric() * discountFactor;
}
getBasePric() {
return this.quantity * this.itemPrice;
}
- 修改後發現discountFactor其實也可以抽出來,再次修改如下
getPrice() {
return this.getBasePric() * this.discountFactor();
}
getBasePric() {
return this.quantity * this.itemPrice;
}
getDisCountFactor() {
if (this.getBasePrice() > 1000) {return 0.95;}
return 0.98;
}
在對象之間搬移特性
-
目的
:解決對象因爲承擔過多的責任而變得臃腫不堪的場景(比如業務代碼Mgr經常作了Panel或者Form應該做的事情) -
解決思路
:梳理對象的責任,移動對應的函數。(決定把責任放在哪) -
常用方法
:搬移方法(Move Method),提煉類(ExTract Class)
搬移方法(Move Method)
-
一個類如果與另外一個類有着高度耦合的關係,就需要搬移函數,使得每個類更簡單
-
小原則:函數與哪個對象交流比較多,就移入這個對象
重新組織數據
-
目的
:輕鬆處理數據的重構 -
解決思路
:魔數改成常量、數組轉換成對象 -
常用方法
:搬移方法(Move Method),提煉類(ExTract Class)
簡化條件表達式
-
目的
:簡化一些條件邏輯複雜的判斷 -
解決思路
:將“分支邏輯”和“操作細節”進行分離 -
常用方法
:分解條件表達式(Decompose Conditioal),合併條件表達式(Consolidate Conditional Expression),以衛語句代替嵌套表達式(Replace Nested Conditional withGuard Clauses)
分解條件表達式(Decompose Conditioal)
-
複雜條件邏輯是導致複雜度上升的原因之一,編寫不同的條件分支,根據不同的分支做不同的事情,但是複雜的條件判斷會讓你弄不清楚爲什麼要這樣做,可讀性大大的降低,所以需要拆解爲多個獨立的判斷函數,根據用途來命名區分,突出條件邏輯。
-
場景
// dealWithData(num) {
// if (num < this.minNum || num > this.maxNum) {
// // doSomeThing
// } else {
// // doSomeThing
// }
// }
function dealWithData(num) {
if (this.isOverFlow(num)) {
// doSomeThing
} else {
// doSomeThing
}
}
isOverFlow(num) {
return num < this.minNum || num > this.maxNum
}
合併條件表達式(Consolidate Conditional Expression)
- 有一系類的判斷條件,得到的結果都是一致的
// function dealWithData(num) {
// if (num < 2) {
// return 0;
// }
// if (num > this.maxNum) {
// return 0;
// }
// if (this.isFlag(num)) {
// return 0;
// }
// }
// 修改後
dealWithData(num) {
if (this.isZero(num)) {
return 0;
}
}
isZero(num) {
return num < 2 || num > this.maxNum || this.isFlag(num);
}
以衛語句代替嵌套表達式(Replace Nested Conditional withGuard Clauses)
- 函數中的條件邏輯使人難以看清正常的執行路徑的情況下,使用衛語句理清
// dealWithData() {
// let result;
// if (this.isManage()) {
// result = this.getManage();
// } else {
// if (this.isEmploy()) {
// result = this.getEmploy();
// } else {
// if (this.isReired()) {
// result = this.getReired();
// } else {
// result = this.getDefault()
// }
// }
// }
// }
// 修改後
dealWithData() {
if (this.isManage()) {
return this.getManage();
}
if (this.isEmploy()) {
return this.getEmploy();
}
if (this.isReired()) {
return this.getReired();
}
result = this.getDefault()
}
簡化函數調用
-
目的
:讓接口變得更容易理解和使用 -
解決思路
:對函數的返回值、函數名、參數進行優化 -
常用方法
:函數改名(Rename Method),添加參數(Add Parameter),分離查詢函數和修改函數(Spaaeate Query form Modifier),保持對象完整(Preserve Whole Object)
函數改名(Rename Method)
- 將複雜的處理過程分解成小函數,小函數命名不好,很難弄清楚函數式做什麼的,沒有達到預期的效果
添加參數(Add Parameter)
- 某個函數需要從調用端的到更多的信息,爲此可以添加一個對象參數,讓該對象帶進函數所需信息。但是過多的參數要注意數據泥團的問題。
分離查詢函數和修改函數(Spaaeate Query form Modifier)
// doSomeCode(nameArr) {
// for (let i = 0; i < nameArr.length; i++) {
// if (name === 'VFrank') {
// showMsg();
// return 'VFrank'
// }
// }
// return ''
// }
// 修改後
doSomeCode(nameArr) {
if (findPerson(nameArr)) {
showMsg();
}
}
findPerson(nameArr) {
for (let i = 0; i < nameArr.length; i++) {
if (name === 'JhoVFrankn') {
return 'VFrank'
}
}
}
處理概括關係
-
目的
:梳理繼承關係,劃分函數應該在父類還是在子類中 -
解決思路
:將函數方法在繼承體系同上下移動 -
常用方法
: 函數上移(Pull Up Method), 函數下移(Push Down Method),塑造模板函數(Form Template Method), 提煉父類,提煉子類, 提煉接口
函數上移(Pull Up Method), 函數下移(Push Down Method)
- 有些函數在子類中實現的都是相同的功能,這些函數應該抽離到父類中,反之,如果父類函數中某個函數只是與某一個子類有關係,這個函數應該移到子類中去
構造函數本體上移
-
在各個子類中擁有一些構造函數, 本體幾乎一樣,可以在超類中構造一個函數,並在子類中調用。
-
這一點Ext裏面用的很多,比如Ext的field組件,在initComponent的時候addEvent(‘focus’, ‘blur’)等事件,把子類共有的操作都提到父類中來執行
塑造模板函數
-
子類中,相應的某些函數以相同的順序來執行類似的操作,但是每個函數的操作細節上有所不同,可以把這些操作放到獨立的函數中,用同樣的函數名錶示。
-
Ext的組件的基本上都是這種模式來實現的, 每個組件都有initComponent,onRender,afterRender等函數,就是 用了這種方式,由此也有了鉤子函數,也就有了生命週期概念。