咱們在修改他人代碼的時候,閱讀他人代碼所花的時間常常比實現功能的時間還要更多javascript
若是程序結構不清晰,代碼混亂 。牽一髮而動全身。那維護起來就更難維護了java
在咱們的程序中,能夠聞到不少的壞味道。主要有如下這些點git
命名存在使用縮寫、不規範、無心義github
例子:var a = xxx,b = xxx
算法
相同(或類似)的代碼在項目中出現了屢次,若是需求發生更改,則須要同時修改多個地方函數
程序越長越難理解,一個函數應該只完成一個功能優化
一個類的職責過多,一個類應該是一個獨立的總體。設計
太長的參數列表難以理解,不易使用。當須要修改的時候,會更加容易出錯調試
有些數據項老是三五成羣的待在一塊兒。例如兩個類中相同的字段、許多函數簽名相同的參數。code
這些都應該提煉到一個對象中,將不少參數列縮短,簡化函數調用
總體上實現的功能差很少,可是因爲有一點點區別。因此寫成了多個函數
針對一個比較長的函數,提煉成一個個完成特定功能的函數。
// 提煉前 function test11() { var day = $('day'); var yearVal = '2016'; var monthVal = '10'; var dayVal = '10'; day.val(dayVal); switch (monthVal) { case 4: case 6: case 9: case 11: if (dayVal > 30) { day.val(30); } break; case 2: if ( yearVal % 4 == 0 && (yearVal % 100 != 0 || yearVal % 400 == 0) && monthVal == 2 ) { if (dayVal > 29) { day.val(29); } } else { if (dayVal > 28) { day.val(28); } } break; default: if (dayVal > 31) { day.val(31); } } }
// 提煉後 function test12() { var day = $('day'); var yearVal = '2016'; var monthVal = '10'; var dayVal = '10'; var maxDay = getMaxDay(yearVal, monthVal); if (dayVal > maxDay) { day.val(maxDay); } else { day.val(dayVal); } } function getMaxDay(year, month) { var maxDay = 0; switch (month) { case 4: case 6: case 9: case 11: maxDay = 30; break; case 2: if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { maxDay = 29; } else { maxDay = 28; } break; default: maxDay = 31; } return maxDay; }
例子中,提煉前的代碼,須要很費勁的看完整個函數,纔會明白作了什麼處理,提煉後的代碼。只須要稍微看一下,就知道 getMaxDay 是獲取當前月份的最大天數
有時候,一個函數的本體與函數名同樣簡單易懂,就要用到這種手法。
這種手法用於處理優化過分的問題
舉個例子:
function biggerThanZero(num) { return num > 0; } function test() { var num = 10; if (biggerThanZero(num)) { //do something } } //內聯後 function test() { var num = 10; if (num > 0) { //do something } }
當表達式比較複雜難以閱讀的時候,就能夠經過臨時變量來幫助你將表達式分解爲容易管理的形式
有些時候,運用提煉函數會更好一點
舉兩個簡單的例子:
// 例子 1 // before function test2() { if ( platform.toUpperCase().indexOf('MAC') > -1 && browser.toUpperCase().indexOf('IE') > -1 && wasInitialized() && resize > 0 ) { // do something } } // after function test2() { var isMacOs = platform.toUpperCase().indexOf('MAC') > -1; var isIEBrowser = browser.toUpperCase().indexOf('IE') > -1; var wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { // do something } } // -------------------------------------------------- // 例子2 // before function caluPrice(quantity, itemPrice) { return ( quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100) ); } // after function caluPrice(quantity, itemPrice) { var basePrice = quantity * itemPrice; var discount = Math.max(0, quantity - 500) * itemPrice * 0.05; var shiping = Math.min(basePrice * 0.1, 100); return basePrice - discount + shiping; }
在兩個例子中,引入解釋性的變量以後,可讀性大大增長。函數的意圖就比較明顯,單看變量命名就已經能大概知道具體的實現
舉個例子:
// 分解臨時變量 // before function test3() { var temp = 2 * (width + height); console.log(temp); // do something temp = height * width; // do something console.log(temp); } // after function test4() { var perimeter = 2 * (width + height); console.log(perimeter); // do something var area = height * width; // do something console.log(area); }
在這個例子中,temp 分別被賦予了兩次,若是代碼塊較長的狀況,會增長風險,由於你不知道他在哪裏被改掉了
當你重構的時候,發現實現一樣的功能有一個更清晰的方式,就應該將原有的算法替換成你的算法。
舉個例子:
// 替換算法 // before function getWeekDay() { var weekStr = ''; switch (date.format('d')) { case 0: weekStr = '日'; break; case 1: weekStr = '一'; break; case 2: weekStr = '二'; break; case 3: weekStr = '三'; break; case 4: weekStr = '四'; break; case 5: weekStr = '五'; break; case 6: weekStr = '六'; break; } return weekStr; } // after function getWeekDay() { var weekDays = ['日', '一', '二', '三', '四', '五', '六']; return weekDays[date.format('d')]; }
在計算機科學中,魔法數是歷史最悠久的不良現象之一。
魔法數是指程序中莫名其妙的數字。擁有特殊意義,卻又不能明確表現出這種意義的數字
舉個例子:
// before function test5(x) { if (x == 1) { console.log('完成'); } else if (x == 2) { console.log('上傳中'); } else if (x == 3) { console.log('上傳失敗'); } else { console.log('未知的錯誤'); } } function test6(x) { if (x == 3) { // do something } } // after var UploadStatus = { START: 0, UPLOADING: 1, SUCCESS: 2, ERROR: 3, UNKNOWN: 4 }; function test7(x) { if (x == UploadStatus.START) { console.log('未開始'); } else if (x == UploadStatus.UPLOADING) { console.log('上傳中'); } else if (x == UploadStatus.SUCCESS) { console.log('上傳成功'); } else if (x == UploadStatus.ERROR) { console.log('上傳失敗'); } else { console.log('未知的錯誤'); } } function test8(x) { if (x == UploadStatus.ERROR) { // do something } }
對於魔法數,應該用一個枚舉對象或一個常量來賦予其可見的意義。這樣,你在用到的時候,就可以明確的知道它表明的是什麼意思
並且,當需求變化的時候,只須要改變一個地方便可
複雜的條件邏輯是致使複雜度上升的地點之一。由於必須編寫代碼來處理不一樣的分支,很容易就寫出一個至關長的函數
將每一個分支條件分解成新函數能夠突出條件邏輯,更清楚代表每一個分支的做用以及緣由
舉個例子:
// 分解條件表達式 // 商品在冬季和夏季單價不同 // before var SUMMER_START = '06-01'; var SUMMER_END = '09-01'; function test9() { var quantity = 2; var winterRate = 0.5; var winterServiceCharge = 9; var summerRate = 0.6; var charge = 0; if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = quantity * winterRate + winterServiceCharge; } else { charge = quantity * summerRate; } return charge; } // after function test9() { var quantity = 2; return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity); } function notSummer(date) { return date.before(SUMMER_START) || date.after(SUMMER_END); } function summerCharge(quantity) { var summerRate = 0.6; return quantity * summerRate; } function winterCharge(quantity) { var winterRate = 0.5; var winterServiceCharge = 9; return quantity * winterRate + winterServiceCharge; }
當發現一系列的條件檢查,檢查條件不同,可是行爲卻一致。就能夠將它們合併爲一個條件表達式
舉個例子:
// 合併條件表達式 // before function test10(x) { var isFireFox = 'xxxx'; var isIE = 'xxxx'; var isChrome = 'xxxx'; if (isFireFox) { return true; } if (isIE) { return true; } if (isChrome) { return true; } return false; } // after function test10(x) { var isFireFox = 'xxxx'; var isIE = 'xxxx'; var isChrome = 'xxxx'; if (isFireFox || isIE || isChrome) { return true; } return false; }
合併後的代碼會告訴你,實際上只有一個條件檢查,只是有多個並列條件須要檢查而已
條件表達式上有着相同的一段代碼,就應該將它搬離出來
// 合併重複片斷 // before function test11(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; // 這裏處理一些業務 } else { total = price * 0.8; // 這裏處理一些業務 } } // after function test12(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; } else { total = price * 0.8; } // 這裏處理一些業務 }
在不一樣的條件裏面作了一樣的事情,應該將其抽離出條件判斷。這樣代碼量少並且邏輯更加清晰
若是某個條件較爲罕見,應該單獨檢查該條件,並在該條件爲真時當即從函數中返回。這樣的檢查就叫衛語句
舉個例子:
// 以衛語句取代嵌套條件表達式 // before function getPayMent() { var result = 0; if (isDead) { result = deadAmount(); } else { if (isSepartated) { result = separtedAmount(); } else { if (isRetired) { result = retiredAmount(); } else { result = normalPayAmount(); } } } return result; } // after function getPayMent() { if (isDead) { return deadAmount(); } if (isSepartated) { return separtedAmount(); } if (isRetired) { return retiredAmount(); } return normalPayAmount(); }
當函數名稱不能表達函數的用途,就應該更名
變量和函數應使用合乎邏輯的名字。
eg:獲取產品列表 -> getProductList()
變量名應爲名詞,由於變量名描述的大部分是一個事物。
eg: 產品 -> product
函數名應爲動詞開始,由於函數描述的是一個動做
eg:獲取產品列表 -> getProductList()
若是某個函數只向你提供一個值,沒有任何反作用。這個函數就能夠任意的調用。
這樣的函數稱爲純函數
若是遇到一個既有返回值,又有反作用的函數。就應該將查詢與修改動做分離出來
舉個例子:
// before function test13(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == 'andy') { // do something 例如進行DOM 操做之類的 return 'andy'; } if (people[i].name == 'ChunYang') { // do something 例如進行DOM 操做之類的 return 'ChunYang'; } } } // after function test14(people) { var p = find(people); // do something 例如進行DOM 操做之類的 // doSomeThing(p); } function find(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == 'andy') { return 'andy'; } if (people[i].name == 'ChunYang') { return 'ChunYang'; } } }
若是發現兩個函數,作着相似的工做。區別只在於其中幾個變量的不一樣。就能夠經過參數來處理。
這樣能夠去除重複的代碼,提升靈活性
關鍵點: 找出不一樣的地方和重複的地方。
《重構 改善既有代碼的設計 》 基於 java 的
《代碼大全》