重構不是對之前代碼的全盤否認,而是利用更好的方式,寫出更好,更有維護性代碼。不斷的追求與學習,纔有更多的進步。
作前端開發有一段時間了,在這段時間裏面,對於本身的要求,不只僅是項目能完成,功能正常使用這一層面上。還盡力的研究怎麼寫出優雅的代碼,性能更好,維護性更強的代碼,通俗一點就是重構。這篇文章算是我一個小記錄,在此分享一下。該文章主要針對介紹,例子也簡單,深刻複雜的例子等之後有適合的實例再進行寫做分享。若是你們對怎麼寫出優雅的代碼,可維護的代碼,有本身的看法,或者有什麼重構的實力,歡迎指點評論。html
關於重構,準備寫一個系列的文章,不定時更新,主要針對如下方案:邏輯混亂重構,分離職責重構,添加擴展性重構,簡化使用重構,代碼複用重構。其中會穿插如下原則:單一職責原則,最少知識原則,開放-封閉原則。若是你們對重構有什麼好的想法,或者有什麼好的實例,歡迎留言評論,留下寶貴的建議。
首先,重構不是重寫。重構大概的意思是在不影響項目的功能使用前提下,使用一系列的重構方式,改變項目的內部結構。提升項目內部的可讀性,可維護性。前端
不管是什麼項目,都有一個從簡單到複雜的一個迭代過程。在這個過程裏面,在不影響項目的使用狀況下,須要不斷的對代碼進行優化,保持或者增長代碼的可讀性,可維護性。這樣一來,就能夠避免在團隊協做開發上須要大量的溝通,交流。才能加入項目的開發中。vue
衣服髒了就洗,破了就補,不合穿就扔。
隨着業務需求的不斷增長,變動,捨棄,項目的代碼也不免會出現瑕疵,這就會影響代碼的可讀性,可維護性,甚至影響項目的性能。而重構的目的,就是爲了解決這些瑕疵,保證代碼質量和性能。可是前提是不能影響項目的使用。算法
至於重構的緣由,本身總結了一下,大概有如下幾點數組
在合適的時間,在合適的事情
在個人理解中,重構能夠說是貫穿整一個項目的開發和維護週期,能夠看成重構就是開發的一部分。通俗講,在開發的任什麼時候候,只要看到代碼有別扭,激發了強迫症,就能夠考慮重構了。只是,重構以前先參考下面幾點。微信
基於上面的幾點,須要你們去評估是否要進行重構。評估的指標,能夠參考下面幾點函數
選定目標,針對性出擊
怎麼重構,這個就是具體狀況,具體分析了。如同「爲何重構同樣」。發現代碼有什麼問題就針對什麼狀況進行改進。post
重構也是寫代碼,可是不止於寫,更在於整理和優化。若是說寫代碼須要一個‘學習--瞭解-熟練’的過程,那麼重構就須要一個‘學習-感悟-突破-熟練’的過程。
針對重構的狀況,下面簡單的用幾個例子進行說明性能
以下面一個例子,在我一個庫的其中一個 API學習
//檢測字符串 //checkType('165226226326','mobile') //result:false let checkType=function(str, type) { switch (type) { case 'email': return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); case 'mobile': return /^1[3|4|5|7|8][0-9]{9}$/.test(str); case 'tel': return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str); case 'number': return /^[0-9]$/.test(str); case 'english': return /^[a-zA-Z]+$/.test(str); case 'text': return /^\w+$/.test(str); case 'chinese': return /^[\u4E00-\u9FA5]+$/.test(str); case 'lower': return /^[a-z]+$/.test(str); case 'upper': return /^[A-Z]+$/.test(str); default: return true; } }
這個 API 看着沒什麼毛病,能檢測經常使用的一些數據。可是有如下兩個問題。
1.可是若是想到添加其餘規則的呢?就得在函數裏面增長 case 。添加一個規則就修改一次!這樣違反了開放-封閉原則(對擴展開放,對修改關閉)。並且這樣也會致使整個 API 變得臃腫,難維護。
2.還有一個問題就是,好比A頁面須要添加一個金額的校驗,B頁面須要一個日期的校驗,可是金額的校驗只在A頁面須要,日期的校驗只在B頁面須要。若是一直添加 case 。就是致使A頁面把只在B頁面須要的校驗規則也添加進去,形成沒必要要的開銷。B頁面也同理。
建議的方式是給這個 API 增長一個擴展的接口
let checkType=(function(){ let rules={ email(str){ return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); }, mobile(str){ return /^1[3|4|5|7|8][0-9]{9}$/.test(str); }, tel(str){ return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str); }, number(str){ return /^[0-9]$/.test(str); }, english(str){ return /^[a-zA-Z]+$/.test(str); }, text(str){ return /^\w+$/.test(str); }, chinese(str){ return /^[\u4E00-\u9FA5]+$/.test(str); }, lower(str){ return /^[a-z]+$/.test(str); }, upper(str){ return /^[A-Z]+$/.test(str); } }; //暴露接口 return { //校驗 check(str, type){ return rules[type]?rules[type](str):false; }, //添加規則 addRule(type,fn){ rules[type]=fn; } } })(); //調用方式 //使用mobile校驗規則 console.log(checkType.check('188170239','mobile')); //添加金額校驗規則 checkType.addRule('money',function (str) { return /^[0-9]+(.[0-9]{2})?$/.test(str) }); //使用金額校驗規則 console.log(checkType.check('18.36','money'));
上面的代碼,是多了一些,可是理解起來也沒怎麼費勁,並且拓展性也有了。
上面這個改進實際上是使用了策略模式(把一系列的算法進行封裝,使算法代碼和邏輯代碼能夠相互獨立,而且不會影響算法的使用)進行改進的。策略模式的概念理解起來有點繞,可是你們看着代碼,應該不繞。
這裏展開講一點,在功能上來講,經過重構,給函數增長擴展性,這裏實現了。可是若是上面的 checkType
是一個開源項目的 API
,重構以前調用方式是:checkType('165226226326','phone')
。重構以後調用方式是: checkType.check('188170239','phone')
;或者 checkType.addRule()
;。若是開源項目的做者按照上面的方式重構,那麼以前使用了開源項目的 checkType
這個 API
的開發者,就可能悲劇了,由於只要開發者一更新這個項目版本,就有問題。由於上面的重構沒有作向下兼容。
若是要向下兼容,其實也不難。加一個判斷而已。
let checkType=(function(){ let rules={ email(str){ return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); }, mobile(str){ return /^1[3|4|5|7|8][0-9]{9}$/.test(str); }, tel(str){ return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str); }, number(str){ return /^[0-9]$/.test(str); }, english(str){ return /^[a-zA-Z]+$/.test(str); }, text(str){ return /^\w+$/.test(str); }, chinese(str){ return /^[\u4E00-\u9FA5]+$/.test(str); }, lower(str){ return /^[a-z]+$/.test(str); }, upper(str){ return /^[A-Z]+$/.test(str); } }; //暴露接口 return function (str,type){ //若是type是函數,就擴展rules,不然就是驗證數據 if(type.constructor===Function){ rules[str]=type; } else{ return rules[type]?rules[type](str):false; } } })(); console.log(checkType('188170239','mobile')); checkType('money',function (str) { return /^[0-9]+(.[0-9]{2})?$/.test(str) }); //使用金額校驗規則 console.log(checkType('18.36','money'));
這樣運行能正常,也有擴展性性,可是對於代碼潔癖的來講,這樣寫法不優雅。由於 checkType
違反了函數單一原則。一個函數負責過多的職責可能會致使之後不可估量的問題,使用方面也很讓人疑惑。
面對這樣的狀況,就我的而言,瞭解的作法是:保留 checkType
,不作任何修改,在項目裏面增長一個新的 API
,好比 checkTypOfString
,把重構的代碼寫到 checkTypOfString
裏面。經過各類方式引導開發者少舊 checkType
,多用 checkTypOfString
。以後的項目迭代裏面,合適的時候廢棄 checkType
。
函數違反單一原則最大一個後果就是會致使邏輯混亂。若是一個函數承擔了太多的職責,不妨試下:函數單一原則 -- 一個函數只作一件事。
以下例子
//現有一批的錄入學生信息,可是數據有重複,須要把數據進行去重。而後把爲空的信息,改爲保密。 let students=[ { id:1, name:'守候', sex:'男', age:'', }, { id:2, name:'浪跡天涯', sex:'男', age:'' }, { id:1, name:'守候', sex:'', age:'' }, { id:3, name:'鴻雁', sex:'', age:'20' } ]; 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; } console.log(handle(students))
運行結果沒有問題,可是你們想一下,若是之後,若是改了需求,好比,學生信息不會再有重複的記錄,要求把去重的函數去掉。這樣一來,就是整個函數都要改了。還影響到下面的操做流程。至關於了改了需求,整個方法全跪。城門失火殃及池魚。
下面使用單一原則構造一下
let handle={ removeRepeat(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]); } } return _arr; }, setInfo(arr){ arr.map(item=>{ for(let key in item){ if(item[key]===''){ item[key]='保密'; } } }); return arr; } }; students=handle.removeRepeat(students); students=handle.setInfo(students); console.log(students);
結果同樣,可是需求改下,好比不須要去重,把代碼註釋或者直接刪除就好。這樣至關於把函數的職責分離了,並且職責以前互不影響。中間去除那個步驟不會影響下一步。
//students=handle.removeRepeat(students); students=handle.setInfo(students); console.log(students);
這種狀況就是,對於之前的函數,在不影響使用的狀況下,如今有着更好的實現方式。就使用更好的解決方案,替換之前的解決方案。
好比下面的需求,需求是羣裏一個朋友發出來的,後來引起的一些討論。給出一個20180408000000
字符串,formatDate函數要處理並返回2018-04-08 00:00:00
。
之前的解法
let _dete='20180408000000' function formatStr(str){ return str.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, "$1-$2-$3 $4:$5:$6") } formatStr(_dete); //"2018-04-08 00:00:00"
後來研究了這樣的解法。這個方式就是根據x的位置進行替換填充數據,不難理解
let _dete='20180408000000' function formatStr(str,type){ let _type=type||"xxxx-xx-xx xx:xx:xx"; for(let i = 0; i < str.length; i++){ _type = _type.replace('x', str[i]); } return _type; } formatStr(_dete); result:"2018-04-08 00:00:00"
在以後的幾天,在掘金一篇文章(那些優雅靈性的JS代碼片斷,感謝提供的寶貴方式)的評論裏面發現更好的實現方式,下面根據上面的需求本身進行改造。
let _dete='20180408000000' function formatStr(str,type){ let i = 0,_type = type||"xxxx-xx-xx xx:xx:xx"; return _type .replace(/x/g, () => str[i++]) } formatStr(_dete); result:"2018-04-08 00:00:00"
上面幾個例子都是js的,說下與html沾邊一點的兩個例子--vue數據渲染。
下面代碼中,payChannelEn2Cn
addZero
formatDateTime
函數都是在vue的methods
裏面。你們注意。
之前寫法
<span v-if="cashType==='cash'">現金</span> <span v-else-if="cashType==='check'">支票</span> <span v-else-if="cashType==='draft'">匯票</span> <span v-else-if="cashType==='zfb'">支付寶</span> <span v-else-if="cashType==='wx_pay'">微信支付</span> <span v-else-if="cashType==='bank_trans'">銀行轉帳</span> <span v-else-if="cashType==='pre_pay'">預付款</span>
這樣寫的問題在於,首先是代碼多,第二是若是項目有10個地方這樣渲染數據,若是渲染的需求變了。好比銀行轉帳的值從 bank_trans
改爲 bank
,那麼就得在項目裏面修改10次。時間成本太大。
後來就使用了下面的寫法,算是一個小重構吧
<span>{{payChannelEn2Cn(cashType)}}</span>
payChannelEn2Cn
函數,輸出結果
payChannelEn2Cn(tag){ let _obj = { 'cash': '現金', 'check': '支票', 'draft': '匯票', 'zfb': '支付寶', 'wx_pay': '微信支付', 'bank_trans': '銀行轉帳', 'pre_pay': '預付款' }; return _obj[tag]; }
還有一個例子就是時間戳轉時間的寫法。原理同樣,只是代碼不一樣。下面是原來的代碼。
<span>{{new Date(payTime).toLocaleDateString().replace(/\//g, '-')}} {{addZero(new Date(payTime).getHours())}}: {{addZero(new Date(payTime).getMinutes())}}: {{addZero(new Date(payTime).getSeconds())}}</span>
addZero
時間補零函數
Example:3->03 addZero(i){ if (i < 10) { i = "0" + i; } return i; }
問題也和上面的同樣,這裏就很少說了,就寫重構後的代碼
<span>{{formatDateTime(payTime)}} </span>
formatDateTime
函數,格式化字符串
formatDateTime(dateTime){ return `${new Date(payTime).toLocaleDateString().replace(/\//g, '-')} ${this.addZero(new Date(payTime).getHours())}:${this.addZero(new Date(payTime).getMinutes())}:${this.addZero(new Date(payTime).getSeconds())}`; }
可能不少人看到這裏,以爲重構很簡單,這樣想是對的,重構就是這麼簡單。可是重構也難,由於重構一步登天,須要一個逐步的過程,甚至能夠說重構就是一次次的小改動,逐步造成一個質變的過程。如何保證每一次的改動都是有意義的改善代碼;如何保證每一次的改動都不會影響到項目的正常使用;若是發現某次改動沒有意義,或者改動了反而讓代碼更糟糕的時候,能夠隨時中止或回滾代碼,這些纔是重構的難點。
關於重構就說到這裏了,該文章主要是介紹重構,例子方面都是很簡單的一些例子。目的是爲了好些理解重構的一些概念。關於重構,可能很複雜,可能很簡單。怎麼重構也是具體狀況,具體分析,重構也沒有標準的答案。之後,若是有好的例子,我會第一時間分享,給你們具體狀況,具體分析的講述:爲何重構,怎麼重構。
最後,若是你們對文章有什麼建議,見解,歡迎交流,相互學習,共同進步。
-------------------------華麗的分割線--------------------
想了解更多,關注關注個人微信公衆號:守候書閣