您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“javascript中怎么使用重構(gòu)改善代碼的各方面問題”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
1.前言
做前端開發(fā)有一段時間了,在這段時間里面,對于自己的要求,不僅僅是項目能完成,功能正常使用這一層面上。還盡力的研究怎么寫出優(yōu)雅的代碼,性能更好,維護(hù)性更強(qiáng)的代碼,通俗一點就是重構(gòu)。這篇文章算是我一個小記錄,在此分享一下。該文章主要針對介紹,例子也簡單,深入復(fù)雜的例子等以后有適合的實例再進(jìn)行寫作分享。如果大家對怎么寫出優(yōu)雅的代碼,可維護(hù)的代碼,有自己的見解,或者有什么重構(gòu)的實力,歡迎指點評論。
關(guān)于重構(gòu),準(zhǔn)備寫一個系列的文章,不定時更新,主要針對以下方案:邏輯混亂重構(gòu),分離職責(zé)重構(gòu),添加擴(kuò)展性重構(gòu),簡化使用重構(gòu),代碼復(fù)用重構(gòu)。其中會穿插以下原則:單一職責(zé)原則,最少知識原則,開放-封閉原則。如果大家對重構(gòu)有什么好的想法,或者有什么好的實例,歡迎留言評論,留下寶貴的建議。
2.什么是重構(gòu)
首先,重構(gòu)不是重寫。重構(gòu)大概的意思是在不影響項目的功能使用前提下,使用一系列的重構(gòu)方式,改變項目的內(nèi)部結(jié)構(gòu)。提高項目內(nèi)部的可讀性,可維護(hù)性。
無論是什么項目,都有一個從簡單到復(fù)雜的一個迭代過程。在這個過程里面,在不影響項目的使用情況下,需要不斷的對代碼進(jìn)行優(yōu)化,保持或者增加代碼的可讀性,可維護(hù)性。這樣一來,就可以避免在團(tuán)隊協(xié)作開發(fā)上需要大量的溝通,交流。才能加入項目的開發(fā)中。
3.為什么重構(gòu)
衣服臟了就洗,破了就補(bǔ),不合穿就扔。
隨著業(yè)務(wù)需求的不斷增加,變更,舍棄,項目的代碼也難免會出現(xiàn)瑕疵,這就會影響代碼的可讀性,可維護(hù)性,甚至影響項目的性能。而重構(gòu)的目的,就是為了解決這些瑕疵,保證代碼質(zhì)量和性能。但是前提是不能影響項目的使用。
至于重構(gòu)的原因,自己總結(jié)了一下,大概有以下幾點
函數(shù)邏輯結(jié)構(gòu)混亂,或因為沒注釋原因,連原代碼寫作者都很難理清當(dāng)中的邏輯。
函數(shù)無擴(kuò)展性可言,遇到新的變化,不能靈活的處理。
因為對象強(qiáng)耦合或者業(yè)務(wù)邏輯的原因,導(dǎo)致業(yè)務(wù)邏輯的代碼巨大,維護(hù)的時候排查困難。
重復(fù)代碼太多,沒有復(fù)用性。
隨著技術(shù)的發(fā)展,代碼可能也需要使用新特性進(jìn)行修改。
隨著學(xué)習(xí)的深入,對于以前的代碼,是否有著更好的一個解決方案。
因為代碼的寫法,雖然功能正常使用,但是性能消耗較多,需要換方案進(jìn)行優(yōu)化
4.何時重構(gòu)
在合適的時間,在合適的事情
在我的理解中,重構(gòu)可以說是貫穿整一個項目的開發(fā)和維護(hù)周期,可以當(dāng)作重構(gòu)就是開發(fā)的一部分。通俗講,在開發(fā)的任何時候,只要看到代碼有別扭,激發(fā)了強(qiáng)迫癥,就可以考慮重構(gòu)了。只是,重構(gòu)之前先參考下面幾點。
首先,重構(gòu)是需要花時間去做的一件事?;ǖ臅r間可能比之前的開發(fā)時間還要多。
其次,重構(gòu)是為了把代碼優(yōu)化,前提是不能影響項目的使用。
***,重構(gòu)的難度大小不一,可能只是稍微改動,可能難度比之前開發(fā)還要難。
基于上面的幾點,需要大家去評估是否要進(jìn)行重構(gòu)。評估的指標(biāo),可以參考下面幾點
數(shù)量: 需要重構(gòu)的代碼是否過多。
質(zhì)量: 可讀性,可維護(hù)性,代碼邏輯復(fù)雜度,等問題,對代碼的質(zhì)量影響是否到了一個難以忍受的地步。
時間: 是否有充裕的時間進(jìn)行重構(gòu)和測試。
效果: 如果重構(gòu)了代碼,得到哪些改善,比如代碼質(zhì)量提高了,性能提升了,更好的支持后續(xù)功能等。
5.怎么重構(gòu)
選定目標(biāo),針對性出擊
怎么重構(gòu),這個就是具體情況,具體分析了。如同“為什么重構(gòu)一樣”。發(fā)現(xiàn)代碼有什么問題就針對什么情況進(jìn)行改進(jìn)。
重構(gòu)也是寫代碼,但是不止于寫,更在于整理和優(yōu)化。如果說寫代碼需要一個‘學(xué)習(xí)--了解-熟練’的過程,那么重構(gòu)就需要一個‘學(xué)習(xí)-感悟-突破-熟練’的過程。
針對重構(gòu)的情況,下面簡單的用幾個例子進(jìn)行說明
5-1.函數(shù)無擴(kuò)展性
如下面一個例子,在我一個庫的其中一個 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 看著沒什么毛病,能檢測常用的一些數(shù)據(jù)。但是有以下兩個問題。
但是如果想到添加其他規(guī)則的呢?就得在函數(shù)里面增加 case 。添加一個規(guī)則就修改一次!這樣違反了開放-封閉原則(對擴(kuò)展開放,對修改關(guān)閉)。而且這樣也會導(dǎo)致整個 API 變得臃腫,難維護(hù)。
還有一個問題就是,比如A頁面需要添加一個金額的校驗,B頁面需要一個日期的校驗,但是金額的校驗只在A頁面需要,日期的校驗只在B頁面需要。如果一直添加 case 。就是導(dǎo)致A頁面把只在B頁面需要的校驗規(guī)則也添加進(jìn)去,造成不必要的開銷。B頁面也同理。
建議的方式是給這個 API 增加一個擴(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 { //校驗 check(str, type){ return rules[type]?rules[type](str):false; }, //添加規(guī)則 addRule(type,fn){ rules[type]=fn; } } })(); //調(diào)用方式 //使用mobile校驗規(guī)則 console.log(checkType.check('188170239','mobile')); //添加金額校驗規(guī)則 checkType.addRule('money',function (str) { return /^[0-9]+(.[0-9]{2})?$/.test(str) }); //使用金額校驗規(guī)則 console.log(checkType.check('18.36','money'));
上面的代碼,是多了一些,但是理解起來也沒怎么費(fèi)勁,而且拓展性也有了。
上面這個改進(jìn)其實是使用了策略模式(把一系列的算法進(jìn)行封裝,使算法代碼和邏輯代碼可以相互獨(dú)立,并且不會影響算法的使用)進(jìn)行改進(jìn)的。策略模式的概念理解起來有點繞,但是大家看著代碼,應(yīng)該不繞。
這里展開講一點,在功能上來說,通過重構(gòu),給函數(shù)增加擴(kuò)展性,這里實現(xiàn)了。但是如果上面的 checkType是一個開源項目的 API ,重構(gòu)之前調(diào)用方式是:checkType('165226226326','phone') 。重構(gòu)之后調(diào)用方式是: checkType.check('188170239','phone') ;或者 checkType.addRule() ;。如果開源項目的作者按照上面的方式重構(gòu),那么之前使用了開源項目的 checkType 這個 API 的開發(fā)者,就可能悲劇了,因為只要開發(fā)者一更新這個項目版本,就有問題。因為上面的重構(gòu)沒有做向下兼容。
如果要向下兼容,其實也不難。加一個判斷而已。
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,否則就是驗證數(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) }); //使用金額校驗規(guī)則 console.log(checkType('18.36','money'));
這樣運(yùn)行能正常,也有擴(kuò)展性性,但是對于代碼潔癖的來說,這樣寫法不優(yōu)雅。因為 checkType 違反了函數(shù)單一原則。一個函數(shù)負(fù)責(zé)過多的職責(zé)可能會導(dǎo)致以后不可估量的問題,使用方面也很讓人疑惑。
面對這樣的情況,就個人而言,了解的做法是:保留 checkType ,不做任何修改,在項目里面增加一個新的 API ,比如 checkTypOfString ,把重構(gòu)的代碼寫到 checkTypOfString 里面。通過各種方式引導(dǎo)開發(fā)者少舊 checkType ,多用 checkTypOfString 。之后的項目迭代里面,合適的時候廢棄 checkType 。
5-2.函數(shù)違反單一原則
函數(shù)違反單一原則***一個后果就是會導(dǎo)致邏輯混亂。如果一個函數(shù)承擔(dān)了太多的職責(zé),不妨試下:函數(shù)單一原則 -- 一個函數(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<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))
運(yùn)行結(jié)果沒有問題,但是大家想一下,如果以后,如果改了需求,比如,學(xué)生信息不會再有重復(fù)的記錄,要求把去重的函數(shù)去掉。這樣一來,就是整個函數(shù)都要改了。還影響到下面的操作流程。相當(dāng)于了改了需求,整個方法全跪。城門失火殃及池魚。
下面使用單一原則構(gòu)造一下
let handle={ removeRepeat(arr){ //數(shù)組去重 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);
結(jié)果一樣,但是需求改下,比如不需要去重,把代碼注釋或者直接刪除就好。這樣相當(dāng)于把函數(shù)的職責(zé)分離了,而且職責(zé)之前互不影響。中間去除那個步驟不會影響下一步。
//students=handle.removeRepeat(students); students=handle.setInfo(students); console.log(students);
5-3.函數(shù)寫法優(yōu)化
這種情況就是,對于以前的函數(shù),在不影響使用的情況下,現(xiàn)在有著更好的實現(xiàn)方式。就使用更好的解決方案,替換以前的解決方案。
比如下面的需求,需求是群里一個朋友發(fā)出來的,后來引發(fā)的一些討論。給出一個20180408000000字符串,formatDate函數(shù)要處理并返回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"
后來研究了這樣的解法。這個方式就是根據(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代碼片段,感謝提供的寶貴方式)的評論里面發(fā)現(xiàn)更好的實現(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ù)用
上面幾個例子都是js的,說下與html沾邊一點的兩個例子--vue數(shù)據(jù)渲染。
下面代碼中,payChannelEn2Cn addZero formatDateTime函數(shù)都是在vue的methods里面。大家注意。
以前寫法
<span v-if="cashType==='cash'">現(xiàn)金</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'">銀行轉(zhuǎn)賬</span> <span v-else-if="cashType==='pre_pay'">預(yù)付款</span>
這樣寫的問題在于,首先是代碼多,第二是如果項目有10個地方這樣渲染數(shù)據(jù),如果渲染的需求變了。比如銀行轉(zhuǎn)賬的值從 bank_trans 改成 bank ,那么就得在項目里面修改10次。時間成本太大。
后來就使用了下面的寫法,算是一個小重構(gòu)吧
<span>{{payChannelEn2Cn(cashType)}}</span>
payChannelEn2Cn 函數(shù),輸出結(jié)果
payChannelEn2Cn(tag){ let _obj = { 'cash': '現(xiàn)金', 'check': '支票', 'draft': '匯票', 'zfb': '支付寶', 'wx_pay': '微信支付', 'bank_trans': '銀行轉(zhuǎn)賬', 'pre_pay': '預(yù)付款' }; return _obj[tag]; }
還有一個例子就是時間戳轉(zhuǎn)時間的寫法。原理一樣,只是代碼不同。下面是原來的代碼。
<span>{{new Date(payTime).toLocaleDateString().replace(/\//g, '-')}} {{addZero(new Date(payTime).getHours())}}: {{addZero(new Date(payTime).getMinutes())}}: {{addZero(new Date(payTime).getSeconds())}}</span>
addZero時間補(bǔ)零函數(shù)
Example:3->03 addZero(i){ if (i < 10) { i = "0" + i; } return i; }
問題也和上面的一樣,這里就不多說了,就寫重構(gòu)后的代碼
<span>{{formatDateTime(payTime)}} </span>
formatDateTime函數(shù),格式化字符串
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())}`; }
“javascript中怎么使用重構(gòu)改善代碼的各方面問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。