重構你的javascript程式碼
重構,對於每個開發者都至關重要,特別是對於那些需要進階的高階程式設計師。根據二八理論,20%的重構方法,能解決80%的壞程式碼。筆者最近查閱較多js編碼指南以及重新閱讀了《程式碼整潔之道》、《重構:改善既有程式碼的設計》兩本經典書籍(強烈建議每隔一段時間看,每次都有新體會),整理出以下幾個要點,幫助大家以最小的記憶,重構大部分壞程式碼。如果想全面瞭解重構方式,可以看筆者整理的AI Javascript風格指南
壞程式碼判斷
壞程式碼對每個人、每個專案標準都不一樣,但以下幾點大概率會是壞程式碼,需要使用重構方法進行程式碼重構。
- 重複程式碼
- 過長函式
- 過大的類
- 過長引數列表
重構方法
1. 好的命名
好的命名貫穿整個軟體編碼過程,好命名包括合理使用大小寫定義、縮排等。目前前端工程提供很多lint或format工具,能很方便的幫助工程檢測和自動化,不清楚的同學可以看看筆者AI前端工具鏈。不管是變數名、函式名或是類名,一個好的命名會加快自身開發效率以及閱讀程式碼效率,畢竟程式讀的次數會比寫的次數多的多。讀github上優秀原始碼就知道,有時候只要看函式名就知道作者的意圖。
// bad var yyyymmdstr = moment().format('YYYY/MM/DD'); // good var yearMonthDay = moment().format('YYYY/MM/DD'); 複製程式碼
// bad function dateAdd(date, month) { // ... } let date = new Date(); dateAdd(date, 1) // 很難理解dateAdd(date, 1)是什麼意思。筆者注:這裡單拎出來舉例很簡單易懂,但希望在做工程時也時刻謹記這條 // good function dateAddMonth(date, month) { // ... } let date = new Date(); dateAddMonth(date, 1); 複製程式碼
2. 函式單一職責
軟體工程中最重要原則之一。剛畢業不久的開發人員容易出現這個問題,覺得業務邏輯很複雜,沒辦法再細分成單獨函式,寫出很長的業務函式。但根據筆者指導小夥伴經驗,大多數是臨時變數過多,導致看不穿業務邏輯的本質;其實重構過程中一步步分解職責,拆分成細小函式並用恰當的名稱命名函式名,能很快理解業務的本質,說不定還能發現潛藏的bug。
// bad function handle(arr) { //陣列去重 let _arr=[],_arrIds=[]; for(let i=0;i<arr.length;i++){ if(_arrIds.indexOf(arr[i].id)===-1){ _arrIds.push(arr[i].id); _arr.push(arr[i]); } } //遍歷替換 _arr.map(item=>{ for(let key in item){ if(item[key]===''){ item[key]='--'; } } }); return _arr; } // good function handle(arr) { let filterArr = filterRepeatById(arr) return replaceEachItem(filterArr) } 複製程式碼
3. 通過引入解釋性變數或函式,使得表達更清晰
// bad if (platform.toUpperCase().indexOf('MAC') > -1 && browser.toUpperCase().indexOf('IE') > -1 && wasInitialized() && resize > 0) { // do something } // good let isMacOs = platform.toUpperCase().indexOf('MAC') > -1 let isIEBrowser = browser.toUpperCase().indexOf('IE') > -1 let isResize = resize > 0 if (isMacOs && isIEBrowser && wasInitialized() && isResize) { // do something } 複製程式碼
// bad const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; saveCityState(ADDRESS.match(cityStateRegex)[1], ADDRESS.match(cityStateRegex)[2]); // good var cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; var match = ADDRESS.match(cityStateRegex) let [, city, state] = match saveCityState(city, state); 複製程式碼
// bad if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = quantity * _winterRate + _winterServiceCharge } else { charge = quantity * _summerRate } // good if (notSummer(date)) { charge = winterCharge(quantity) } else { charge = summerCharge(quantity) } 複製程式碼
4. 更少的巢狀,儘早 return
// bad let getPayAmount = () => { let result if (_isDead) result = deadAmount() else { if (_isSeparated) result = separatedAmount() else { if (_isRetired) result = retiredAmount() else result = normalPayAmount() } } return result } // good let payAmount = () => { if (_isDead) return deadAmount() if (_isSeparated) return separatedAmount() if (_isRetired) return retiredAmount() return normalPayAmount() } 複製程式碼
5. 以HashMap取代條件表示式
// bad let getSpeed = type => { switch (type) { case SPEED_TYPE.AIR: return getAirSpeed() case SPEED_TYPE.WATER: return getWaterSpeed() ... } } // good let speedMap = { [SPEED_TYPE.AIR]: getAirSpeed, [SPEED_TYPE.WATER]: getWaterSpeed } let getSpeed = type => speedMap[type] && speedMap[type]() 複製程式碼
其他
實踐以上列舉的重構方法,能解決專案中大部分的壞程式碼,但還有許多重構方法,能讓你的程式碼變得乾淨整潔易於閱讀。
- 清晰的專案目錄結構
-
ES6+語法糖
- arrow function
- rest
- 函式預設引數
- async/await
- let/const 代替var
- Array Methods
- 常用全部使用const,並字母全部為大寫
- 使用合適的函式名或變數名代替註釋
- 善於利用js中的&& 與 ||
- 避免‘否定情況’的判斷
- 儘量不寫全域性函式與變數
- 採用函數語言程式設計,ES6 Array支援的很好
- 移除重複的程式碼
- 移除註釋的程式碼