当前位置: 首页 > 科技观察

Refactoring-完善代码的方方面面

时间:2023-03-16 15:49:21 科技观察

Refactoring并不是对之前代码的全盘否定,而是更好的写出更好、更易维护的代码。不断的追求和学习,才会有更大的进步。一、前言本人从事前端开发有一段时间了。这段时间,我的要求不仅仅是项目的完成,还有功能的正常使用。我也努力研究如何编写优雅的代码、更好的性能和更可维护的代码。通俗点说就是重构。这篇文章也算是我的一个小记录,分享到这里。这篇文章主要是介绍,例子比较简单,深入和复杂的例子以后有合适的例子会写出来分享。如果你对如何写出优雅可维护的代码有自己的见解,或者有重构的能力,欢迎评论。关于重构,我准备写一个系列文章,会不定期更新,主要针对以下解决方案:逻辑混乱重构、职责分离、重构增加可扩展性、重构简化使用、代码重用重构.其中会穿插以下原则:单一职责原则、最少知识原则、开闭原则。如果大家对重构有什么好的想法,或者有什么好的例子,欢迎大家留言,留下宝贵的建议。2、什么是重构首先,重构不是重写。重构大致就是在不影响项目功能的情况下,使用一系列的重构方法来改变项目的内部结构。提高项目内部的可读性和可维护性。不管是什么项目,都有一个从简单到复杂的迭代过程。在这个过程中,在不影响项目使用的情况下,需要不断优化代码,以保持或增加代码的可读性和可维护性。这样就可以避免团队协作开发中需要大量的交流和沟通。为了加入项目的开发。3、衣服脏了为什么要重构,洗了,破了就补,不合身就扔。随着业务需求的不断增加、变更、废弃,项目的代码难免会出现缺陷,影响代码的可读性和可维护性,甚至影响项目的性能。重构的目的就是解决这些缺陷,保证代码质量和性能。但前提是不能影响项目的使用。至于重构的原因,我总结了以下几点。函数逻辑结构混乱,或者因为没有注释,即使是原代码编写者也很难理清逻辑。功能完全没有可扩展性,遇到新的变化不能灵活处理。由于对象或业务逻辑的强耦合,导致业务逻辑代码量巨大,维护时难以排查问题。重复代码太多,没有复用性。随着技术的发展,代码可能还需要修改以添加新功能。随着学习的深入,前面的代码有没有更好的解决办法。因为代码的写法,虽然功能正常使用,但是性能消耗很大,需要通过改方案来优化。项目的开发和维护周期可以看作是重构开发的一部分。通俗点说,在开发的任何时候,只要看到代码别扭,引发强迫症,就可以考虑重构。只是,在重构之前,参考以下几点。首先,重构是一件需要时间的事情。它可能比以前的开发时间需要更多的时间。其次,重构就是优化代码,前提是不影响项目的使用。***,重构的难度各不相同,可能只是稍微改动一下,难度可能会比之前的开发难度大一些。基于以上几点,你需要评估是否重构。评价指标可以参考以下几点数量:需要重构的代码是否过多。质量:可读性、可维护性、代码逻辑复杂度等问题,对代码质量的影响是否已经到了无法承受的程度。时间:是否有足够的时间进行重构和测试。效果:如果重构代码,会得到哪些改进,比如代码质量提高,性能提升,对后续功能的支持更好等。5.如何重构选择的目标,如何重构目标攻击,这是具体情况,具体分析。如“为什么重构相同”。如果您发现代码有任何问题,您可以针对该情况进行改进。重构也是写代码,但不限于写,还要整理和优化。如果说写代码需要一个“学习-理解-熟练”的过程,那么重构则需要一个“学习-感悟-突破-熟练”的过程。对于重构的情况,下面用5-1的简单例子来说明。该函数没有可扩展性。比如下面这个例子,我的一个库中的一个API//检测字符串//checkType('165226226326','mobile')//result:falseletcheckType=function(str,type){switch(type){case'email':return/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(海峡);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:returntrue;}}这个API看起来不错,可以检测一些常用的数据。但存在以下两个问题。但是,如果您考虑添加其他规则怎么办?您必须在函数内添加大小写。添加规则,修改一次!这违反了开闭原则(对扩展开放,对修改关闭)。而这也会导致整个API变得臃肿难维护。另一个问题是,比如A页面需要添加金额验证,B页面需要日期验证,但是A页面只需要进行金额验证,B页面只需要进行日期验证。如果一直加case。就是导致A页面添加B页面才需要的校验规则,造成不必要的开销。B页面也是如此。建议的方法是给这个API添加一个扩展接口letcheckType=(function(){letrules={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{//checkcheck(str,type){returnrules[type]?rules[type](str):false;},//添加规则addRule(type,fn){rules[type]=fn;}}})();//调用方法//使用手机验证规则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','金钱'));上面的代码有点多,但是也不是太难理解,而且还具有可扩展性。上面的改进其实是通过使用策略模式(将一系列算法封装起来,让算法代码和逻辑代码可以相互独立,不会影响算法的使用)来改进的。策略模式的概念理解起来有点绕口,但是看代码应该不会绕口。让我们在这里展开。在功能上,通过重构,我们可以给功能增加可扩展性,这里就实现了。但如果上述checkType是开源项目的API,重构前的调用方式为:checkType('165226226326','phone')。重构后的调用方式为:checkType.check('188170239','phone');或checkType.addRule();.如果开源项目的作者按照上面的方法重构,那么之前使用开源项目的checkTypeAPI的开发者可能就悲剧了,因为只要开发者更新项目版本,就会出现问题。因为上面的重构是不向后兼容的。如果要向下兼容,其实也不难。加个判断就行了。letcheckType=(function(){letrules={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);}};//暴露接口returnfunction(str,type){//如果type是函数,展开规则,否则是校验数据if(type.constructor===Function){rules[str]=type;}else{returnrules[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将在适当的时候被丢弃。5-2。函数违反单一原则函数违反单一原则的最后一个后果是它们会导致逻辑混乱。如果一个函数承担太多职责,试试这个:单一函数原则——一个函数只做一件事。下面的例子//输入了一批学生信息,但是数据重复,需要对数据进行去重。然后把空的信息改成机密。letstudents=[{id:1,name:'等待',sex:'男',age:'',},{id:2,name:'流浪世界',sex:'男',age:''},{id:1,name:'waiting',sex:'',age:''},{id:3,name:'红艳',sex:'',age:'20'}];functionhandle(arr){//数组去重let_arr=[],_arrIds=[];for(leti=0;i{for(letkeyinitem){if(item[key]===''){item[key]='Confidentiality';}}});return_arr;}console.log(handle(students))运行结果没有问题,但是大家想想,如果在以后如果需求有变化,比如学生信息不再有重复记录,需要去掉去重功能。这样一来,整个功能就得改了。也会影响后面的操作流程。相当于改变了需求,整个方法完全跪了。城门失火,池中鱼遭殃。Let_arr=[],_arrIds=[];for(leti=0;i{for(letkeyinitem){if(item[key]===''){item[key]='confidential';}}});returnarr;}};students=handle.removeRepeat(students);students=handle.setInfo(students);console.log(students);结果是一样的,只是需求要改一下,比如不需要去重,直接注释或者删除代码即可。这样就相当于把function的职责分离了,以前职责之间是不会互相影响的。去掉中间那一步不会影响下一步。//students=handle.removeRepeat(students);students=handle.setInfo(students);console.log(students);5-3.函数编写优化这种情况下,对于之前的函数,不影响使用现在,有更好的办法来做。只需使用更好的解决方案,取代以前的解决方案。比如下面这个需求,这个需求是群里的一个朋友发的,后来引发了一些讨论。给定字符串20180408000000,formatDate函数将处理并返回2018-04-0800:00:00。之前的解法let_dete='20180408000000'functionformatStr(str){returnstr.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/,"$1-$2-$3$4:$5:$6")}formatStr(_dete);//"2018-04-0800:00:00"后来研究了这样的方案。该方法是根据x的位置替换填充数据。不难理解let_dete='20180408000000'functionformatStr(str,type){let_type=type||"xxxx-xx-xxxx:xx:xx";for(leti=0;istr[i++])}formatStr(_dete);结果:“2018-04-0800:00:00”5-4。代码复用上面的例子都是js,说两个和html有点关系的例子--vuedatarender。下面的代码中,payChannelEn2CnaddZeroformatDateTime函数都在vue的方法中。大家注意。以前写过cashchequedraft支付宝微信支付银行转账预付这样写的问题一是代码太多,二是如果项目有10个地方这样渲染数据,如果渲染需求有变化。比如banktransfer的值从bank_trans改成bank,那么你要在项目中修改10次。时间成本太高了。后来用了下面的写法,就是对{{payChannelEn2Cn(cashType)}}payChannelEn2Cn函数的一个小重构,输出结果payChannelEn2Cn(tag){let_obj={'cash':'cash','check':'支票','draft':'汇票','zfb':'支付宝','wx_pay':'微信支付','bank_trans':'银行转账','pre_pay':'预付款'};return_obj[tag];}又如时间戳记写入时间的方式。原理是一样的,只是代码不同。下面是原始代码。{{newDate(payTime).toLocaleDateString().replace(/\//g,'-')}}{{addZero(newDate(payTime).getHours())}}:{{addZero(newDate(payTime).getMinutes())}}:{{addZero(newDate(payTime).getSeconds())}}addZero时间填充函数示例:3->03addZero(i){if(i<10){i="0"+i;}returni;}问题同上,这里就不多说了,直接写重构代码{{formatDateTime(payTime)}}formatDateTime函数,格式化字符串formatDateTime(dateTime){return`${newDate(payTime).toLocaleDateString().replace(/\//g,'-')}${this.addZero(newDate(payTime).getHours())}:${this.addZero(newDate(payTime).getMinutes())}:${this.addZero(newDate(payTime).getSeconds())}`;}可能很多人看到这里觉得重构很简单,你这么想是对的,重构就是这么简单。但是重构也很难,因为重构需要一个循序渐进的过程。甚至可以说,重构是一个又一个的小改动,逐渐形成一个质变的过程。如何确保每一次改动都对改进代码有意义;如何保证每一次改动都不会影响项目的正常使用;如果发现某个改动没有意义,或者改动让代码变得更糟,你可以随时停止或者回滚代码,这些都是重构的难点。6.总结这都是关于重构的。本文主要介绍重构,例子都是很简单的例子。目的是为了更好地理解重构的一些概念。说到重构,它可以很复杂,也可以很简单。如何重构也是具体情况。具体分析后,重构没有标准答案。以后如果有好的例子,我会第一时间分享出来,具体情况和具体分析给大家:为什么要重构,怎么重构。

猜你喜欢