重構(gòu)不是對(duì)以前代碼的全盤(pán)否定,而是利用更好的方式,寫(xiě)出更好,更有維護(hù)性代碼。不斷的追求與學(xué)習(xí),才有更多的進(jìn)步。 1.前言做前端開(kāi)發(fā)有一段時(shí)間了,在這段時(shí)間里面,對(duì)于自己的要求,不僅僅是項(xiàng)目能完成,功能正常使用這一層面上。還盡力的研究怎么寫(xiě)出優(yōu)雅的代碼,性能更好,維護(hù)性更強(qiáng)的代碼,通俗一點(diǎn)就是重構(gòu)。這篇文章算是我一個(gè)小記錄,在此分享一下。該文章主要針對(duì)介紹,例子也簡(jiǎn)單,深入復(fù)雜的例子等以后有適合的實(shí)例再進(jìn)行寫(xiě)作分享。如果大家對(duì)怎么寫(xiě)出優(yōu)雅的代碼,可維護(hù)的代碼,有自己的見(jiàn)解,或者有什么重構(gòu)的實(shí)力,歡迎指點(diǎn)評(píng)論。 關(guān)于重構(gòu),準(zhǔn)備寫(xiě)一個(gè)系列的文章,不定時(shí)更新,主要針對(duì)以下方案:邏輯混亂重構(gòu),分離職責(zé)重構(gòu),添加擴(kuò)展性重構(gòu),簡(jiǎn)化使用重構(gòu),代碼復(fù)用重構(gòu)。其中會(huì)穿插以下原則:?jiǎn)我宦氊?zé)原則,最少知識(shí)原則,開(kāi)放-封閉原則。如果大家對(duì)重構(gòu)有什么好的想法,或者有什么好的實(shí)例,歡迎留言評(píng)論,留下寶貴的建議。 2.什么是重構(gòu)首先,重構(gòu)不是重寫(xiě)。重構(gòu)大概的意思是在不影響項(xiàng)目的功能使用前提下,使用一系列的重構(gòu)方式,改變項(xiàng)目的內(nèi)部結(jié)構(gòu)。提高項(xiàng)目?jī)?nèi)部的可讀性,可維護(hù)性。 無(wú)論是什么項(xiàng)目,都有一個(gè)從簡(jiǎn)單到復(fù)雜的一個(gè)迭代過(guò)程。在這個(gè)過(guò)程里面,在不影響項(xiàng)目的使用情況下,需要不斷的對(duì)代碼進(jìn)行優(yōu)化,保持或者增加代碼的可讀性,可維護(hù)性。這樣一來(lái),就可以避免在團(tuán)隊(duì)協(xié)作開(kāi)發(fā)上需要大量的溝通,交流。才能加入項(xiàng)目的開(kāi)發(fā)中。 3.為什么重構(gòu)衣服臟了就洗,破了就補(bǔ),不合穿就扔。 隨著業(yè)務(wù)需求的不斷增加,變更,舍棄,項(xiàng)目的代碼也難免會(huì)出現(xiàn)瑕疵,這就會(huì)影響代碼的可讀性,可維護(hù)性,甚至影響項(xiàng)目的性能。而重構(gòu)的目的,就是為了解決這些瑕疵,保證代碼質(zhì)量和性能。但是前提是不能影響項(xiàng)目的使用。 至于重構(gòu)的原因,自己總結(jié)了一下,大概有以下幾點(diǎn)
4.何時(shí)重構(gòu)在合適的時(shí)間,在合適的事情 在我的理解中,重構(gòu)可以說(shuō)是貫穿整一個(gè)項(xiàng)目的開(kāi)發(fā)和維護(hù)周期,可以當(dāng)作重構(gòu)就是開(kāi)發(fā)的一部分。通俗講,在開(kāi)發(fā)的任何時(shí)候,只要看到代碼有別扭,激發(fā)了強(qiáng)迫癥,就可以考慮重構(gòu)了。只是,重構(gòu)之前先參考下面幾點(diǎn)。
基于上面的幾點(diǎn),需要大家去評(píng)估是否要進(jìn)行重構(gòu)。評(píng)估的指標(biāo),可以參考下面幾點(diǎn)
5.怎么重構(gòu)選定目標(biāo),針對(duì)性出擊 怎么重構(gòu),這個(gè)就是具體情況,具體分析了。如同“為什么重構(gòu)一樣”。發(fā)現(xiàn)代碼有什么問(wèn)題就針對(duì)什么情況進(jìn)行改進(jìn)。 重構(gòu)也是寫(xiě)代碼,但是不止于寫(xiě),更在于整理和優(yōu)化。如果說(shuō)寫(xiě)代碼需要一個(gè)‘學(xué)習(xí)--了解-熟練’的過(guò)程,那么重構(gòu)就需要一個(gè)‘學(xué)習(xí)-感悟-突破-熟練’的過(guò)程。 針對(duì)重構(gòu)的情況,下面簡(jiǎn)單的用幾個(gè)例子進(jìn)行說(shuō)明 5-1.函數(shù)無(wú)擴(kuò)展性如下面一個(gè)例子,在我一個(gè)庫(kù)的其中一個(gè) API //檢測(cè)字符串//checkType('165226226326','mobile')//result:falselet 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; }} 這個(gè) API 看著沒(méi)什么毛病,能檢測(cè)常用的一些數(shù)據(jù)。但是有以下兩個(gè)問(wèn)題。 1.但是如果想到添加其他規(guī)則的呢?就得在函數(shù)里面增加 case 。添加一個(gè)規(guī)則就修改一次!這樣違反了開(kāi)放-封閉原則(對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉)。而且這樣也會(huì)導(dǎo)致整個(gè) API 變得臃腫,難維護(hù)。 2.還有一個(gè)問(wèn)題就是,比如A頁(yè)面需要添加一個(gè)金額的校驗(yàn),B頁(yè)面需要一個(gè)日期的校驗(yàn),但是金額的校驗(yàn)只在A(yíng)頁(yè)面需要,日期的校驗(yàn)只在B頁(yè)面需要。如果一直添加 case 。就是導(dǎo)致A頁(yè)面把只在B頁(yè)面需要的校驗(yàn)規(guī)則也添加進(jìn)去,造成不必要的開(kāi)銷(xiāo)。B頁(yè)面也同理。 建議的方式是給這個(gè) API 增加一個(gè)擴(kuò)展的接口 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 { //校驗(yàn) check(str, type){ return rules[type]?rules[type](str):false; }, //添加規(guī)則 addRule(type,fn){ rules[type]=fn; } }})();//調(diào)用方式//使用mobile校驗(yàn)規(guī)則console.log(checkType.check('188170239','mobile'));//添加金額校驗(yàn)規(guī)則checkType.addRule('money',function (str) { return /^[0-9]+(.[0-9]{2})?$/.test(str)});//使用金額校驗(yàn)規(guī)則console.log(checkType.check('18.36','money')); 上面的代碼,是多了一些,但是理解起來(lái)也沒(méi)怎么費(fèi)勁,而且拓展性也有了。 上面這個(gè)改進(jìn)其實(shí)是使用了策略模式(把一系列的算法進(jìn)行封裝,使算法代碼和邏輯代碼可以相互獨(dú)立,并且不會(huì)影響算法的使用)進(jìn)行改進(jìn)的。策略模式的概念理解起來(lái)有點(diǎn)繞,但是大家看著代碼,應(yīng)該不繞。 這里展開(kāi)講一點(diǎn),在功能上來(lái)說(shuō),通過(guò)重構(gòu),給函數(shù)增加擴(kuò)展性,這里實(shí)現(xiàn)了。但是如果上面的 如果要向下兼容,其實(shí)也不難。加一個(gè)判斷而已。 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是函數(shù),就擴(kuò)展rules,否則就是驗(yàn)證數(shù)據(jù) 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)});//使用金額校驗(yàn)規(guī)則console.log(checkType('18.36','money')); 這樣運(yùn)行能正常,也有擴(kuò)展性性,但是對(duì)于代碼潔癖的來(lái)說(shuō),這樣寫(xiě)法不優(yōu)雅。因?yàn)? 面對(duì)這樣的情況,就個(gè)人而言,了解的做法是:保留 5-2.函數(shù)違反單一原則函數(shù)違反單一原則最大一個(gè)后果就是會(huì)導(dǎo)致邏輯混亂。如果一個(gè)函數(shù)承擔(dān)了太多的職責(zé),不妨試下:函數(shù)單一原則 -- 一個(gè)函數(shù)只做一件事。 如下例子 //現(xiàn)有一批的錄入學(xué)生信息,但是數(shù)據(jù)有重復(fù),需要把數(shù)據(jù)進(jìn)行去重。然后把為空的信息,改成保密。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) { //數(shù)組去重 let _arr=[],_arrIds=[]; for(let i=0;i
下面使用單一原則構(gòu)造一下 let handle={ removeRepeat(arr){ //數(shù)組去重 let _arr=[],_arrIds=[]; for(let i=0;i<>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); 結(jié)果一樣,但是需求改下,比如不需要去重,把代碼注釋或者直接刪除就好。這樣相當(dāng)于把函數(shù)的職責(zé)分離了,而且職責(zé)之前互不影響。中間去除那個(gè)步驟不會(huì)影響下一步。 //students=handle.removeRepeat(students);students=handle.setInfo(students);console.log(students); 5-3.函數(shù)寫(xiě)法優(yōu)化這種情況就是,對(duì)于以前的函數(shù),在不影響使用的情況下,現(xiàn)在有著更好的實(shí)現(xiàn)方式。就使用更好的解決方案,替換以前的解決方案。 比如下面的需求,需求是群里一個(gè)朋友發(fā)出來(lái)的,后來(lái)引發(fā)的一些討論。給出一個(gè) 以前的解法 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' 后來(lái)研究了這樣的解法。這個(gè)方式就是根據(jù)x的位置進(jìn)行替換填充數(shù)據(jù),不難理解 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' 在之后的幾天,在掘金一篇文章(那些優(yōu)雅靈性的JS代碼片段,感謝提供的寶貴方式)的評(píng)論里面發(fā)現(xiàn)更好的實(shí)現(xiàn)方式,下面根據(jù)上面的需求自己進(jìn)行改造。 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' 5-4.代碼復(fù)用上面幾個(gè)例子都是js的,說(shuō)下與html沾邊一點(diǎn)的兩個(gè)例子--vue數(shù)據(jù)渲染。 下面代碼中, 以前寫(xiě)法 span v-if='cashType==='cash''>現(xiàn)金span v-else-if='cashType==='check''>支票span v-else-if='cashType==='draft''>匯票span v-else-if='cashType==='zfb''>支付寶span v-else-if='cashType==='wx_pay''>微信支付span v-else-if='cashType==='bank_trans''>銀行轉(zhuǎn)賬span v-else-if='cashType==='pre_pay''>預(yù)付款 這樣寫(xiě)的問(wèn)題在于,首先是代碼多,第二是如果項(xiàng)目有10個(gè)地方這樣渲染數(shù)據(jù),如果渲染的需求變了。比如銀行轉(zhuǎn)賬的值從 span>{{payChannelEn2Cn(cashType)}}span>
payChannelEn2Cn(tag){ let _obj = { 'cash': '現(xiàn)金', 'check': '支票', 'draft': '匯票', 'zfb': '支付寶', 'wx_pay': '微信支付', 'bank_trans': '銀行轉(zhuǎn)賬', 'pre_pay': '預(yù)付款' }; return _obj[tag];} 還有一個(gè)例子就是時(shí)間戳轉(zhuǎn)時(shí)間的寫(xiě)法。原理一樣,只是代碼不同。下面是原來(lái)的代碼。 span>{{new Date(payTime).toLocaleDateString().replace(/\//g, '-')}} {{addZero(new Date(payTime).getHours())}}:{{addZero(new Date(payTime).getMinutes())}}:{{addZero(new Date(payTime).getSeconds())}}span>
Example:3->03addZero(i){ if (i <>10) { i = '0' + i; } return i;} 問(wèn)題也和上面的一樣,這里就不多說(shuō)了,就寫(xiě)重構(gòu)后的代碼 span>{{formatDateTime(payTime)}} span>
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())}`;} 可能很多人看到這里,覺(jué)得重構(gòu)很簡(jiǎn)單,這樣想是對(duì)的,重構(gòu)就是這么簡(jiǎn)單。但是重構(gòu)也難,因?yàn)橹貥?gòu)一步登天,需要一個(gè)逐步的過(guò)程,甚至可以說(shuō)重構(gòu)就是一次次的小改動(dòng),逐步形成一個(gè)質(zhì)變的過(guò)程。如何保證每一次的改動(dòng)都是有意義的改善代碼;如何保證每一次的改動(dòng)都不會(huì)影響到項(xiàng)目的正常使用;如果發(fā)現(xiàn)某次改動(dòng)沒(méi)有意義,或者改動(dòng)了反而讓代碼更糟糕的時(shí)候,可以隨時(shí)停止或回滾代碼,這些才是重構(gòu)的難點(diǎn)。 6.小結(jié)關(guān)于重構(gòu)就說(shuō)到這里了,該文章主要是介紹重構(gòu),例子方面都是很簡(jiǎn)單的一些例子。目的是為了好些理解重構(gòu)的一些概念。關(guān)于重構(gòu),可能很復(fù)雜,可能很簡(jiǎn)單。怎么重構(gòu)也是具體情況,具體分析,重構(gòu)也沒(méi)有標(biāo)準(zhǔn)的答案。以后,如果有好的例子,我會(huì)第一時(shí)間分享,給大家具體情況,具體分析的講述:為什么重構(gòu),怎么重構(gòu)。 最后,如果大家對(duì)文章有什么建議,看法,歡迎交流,相互學(xué)習(xí),共同進(jìn)步。 |
|