重构不是对之前代码的全盘否认,而是利用更好的方式,写出更好,更有维护性代码。不断的追求与学习,才有更多的进步。
作前端开发有一段时间了,在这段时间里面,对于本身的要求,不只仅是项目能完成,功能正常使用这一层面上。还尽力的研究怎么写出优雅的代码,性能更好,维护性更强的代码,通俗一点就是重构。这篇文章算是我一个小记录,在此分享一下。该文章主要针对介绍,例子也简单,深刻复杂的例子等之后有适合的实例再进行写做分享。若是你们对怎么写出优雅的代码,可维护的代码,有本身的看法,或者有什么重构的实力,欢迎指点评论。javascript
关于重构,准备写一个系列的文章,不定时更新,主要针对如下方案:逻辑混乱重构,分离职责重构,添加扩展性重构,简化使用重构,代码复用重构。其中会穿插如下原则:单一职责原则,最少知识原则,开放-封闭原则。若是你们对重构有什么好的想法,或者有什么好的实例,欢迎留言评论,留下宝贵的建议。
首先,重构不是重写。重构大概的意思是在不影响项目的功能使用前提下,使用一系列的重构方式,改变项目的内部结构。提升项目内部的可读性,可维护性。html
不管是什么项目,都有一个从简单到复杂的一个迭代过程。在这个过程里面,在不影响项目的使用状况下,须要不断的对代码进行优化,保持或者增长代码的可读性,可维护性。这样一来,就能够避免在团队协做开发上须要大量的沟通,交流。才能加入项目的开发中。前端
衣服脏了就洗,破了就补,不合穿就扔。
随着业务需求的不断增长,变动,舍弃,项目的代码也不免会出现瑕疵,这就会影响代码的可读性,可维护性,甚至影响项目的性能。而重构的目的,就是为了解决这些瑕疵,保证代码质量和性能。可是前提是不能影响项目的使用。vue
至于重构的缘由,本身总结了一下,大概有如下几点java
在合适的时间,在合适的事情
在个人理解中,重构能够说是贯穿整一个项目的开发和维护周期,能够看成重构就是开发的一部分。通俗讲,在开发的任什么时候候,只要看到代码有别扭,激发了强迫症,就能够考虑重构了。只是,重构以前先参考下面几点。算法
基于上面的几点,须要你们去评估是否要进行重构。评估的指标,能够参考下面几点typescript
选定目标,针对性出击
怎么重构,这个就是具体状况,具体分析了。如同“为何重构同样”。发现代码有什么问题就针对什么状况进行改进。django
重构也是写代码,可是不止于写,更在于整理和优化。若是说写代码须要一个‘学习--了解-熟练’的过程,那么重构就须要一个‘学习-感悟-突破-熟练’的过程。
针对重构的状况,下面简单的用几个例子进行说明数组
以下面一个例子,在我一个库的其中一个 APIbash
//检测字符串 //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())}`; }复制代码
可能不少人看到这里,以为重构很简单,这样想是对的,重构就是这么简单。可是重构也难,由于重构一步登天,须要一个逐步的过程,甚至能够说重构就是一次次的小改动,逐步造成一个质变的过程。如何保证每一次的改动都是有意义的改善代码;如何保证每一次的改动都不会影响到项目的正常使用;若是发现某次改动没有意义,或者改动了反而让代码更糟糕的时候,能够随时中止或回滚代码,这些才是重构的难点。
关于重构就说到这里了,该文章主要是介绍重构,例子方面都是很简单的一些例子。目的是为了好些理解重构的一些概念。关于重构,可能很复杂,可能很简单。怎么重构也是具体状况,具体分析,重构也没有标准的答案。之后,若是有好的例子,我会第一时间分享,给你们具体状况,具体分析的讲述:为何重构,怎么重构。
最后,若是你们对文章有什么建议,见解,欢迎交流,相互学习,共同进步。
-------------------------华丽的分割线--------------------
想了解更多,关注关注个人微信公众号:守候书阁