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

【前端】重构,有味道的代码05──搬家功能

时间:2023-03-13 17:42:34 科技观察

早前写过本文是《重构,有品位的代码》系列的第五篇。上一篇主要介绍的重构方法是关于如何创建、删除或重命名程序的元素。当然不止这些方法,类型重构也很重要,主要是在不同上下文之间移动元素。通过移动函数可以在类和其他模块之间移动函数,也可以移动字段,其他方法本文会一一介绍...上一篇回顾:《重构,有品位的代码 01──走上重构之道》《重构,有品位的代码 02──构建测试体系》《重构,有品位的代码 03──常用重构手法》《重构,有品位的代码 04──封装》移动特征的常用方法在平时的开发中,代码中经常会用到移动特征,但是我们并不知道移动特征是做什么用的。现将常用的特征迁移方法总结如下:移动函数移动字段移动语句到函数移动语句到用户使用函数调用而不是内联代码移动语句拆分循环用管道替换循环去除死代码1.移动函数模块化可以确保我们的代码块之间的联系很容易找到,直观易懂,可以保证相互关联的软件元素聚集在一起,方便我们理解和管理。同时,随着对代码理解的加深,学会了如何最恰当地组织那些软件元素。这时候就需要通过不断移动元素来重新模块化。函数存在于上下文中,该上下文可以是全局的或由当前模块提供。类是模块化的主要手段,作为功能的上下文。另外,通过函数嵌套的方式,外层函数也可以为内层函数提供上下文。简而言之,模块为功能提供了一个活生生的环境。因为有些代码经常引用其他上下文中的元素,即与其他上下文中的元素密切相关,而不太关心自己上下文中的元素,那么可以考虑总结密切相关的元素,以达到更好的效果。封装效果。在以下情况下,您可以移动函数:某段代码需要频繁调用其他函数。在函数内部定义一个辅助函数,其他地方也会调用它。在类中定义一个函数。通常,首先检查当前上下文中该函数引用的所有程序元素(包括变量和函数),考虑是否需要移动,并检查要移动的函数是否具有多态性。将函数复制到目标上下文并调整函数以适应新上下文。执行静态检查,尝试从源上下文中正确引用目标函数,并将源函数修改为纯委托函数。原代码:classAccount{constructor(){....}getbankCharge(){letresult=4.5;if(this._daysOverdrawn>0)result+=this.overdraftCharge;}getoverdraftCharge(){if(this.type.isPremium){constbasecharge=10;if(this.dayOverdrawn<=7){returnbaseCharge;}else{returnbaseCharge+(this.daysOverdrawn-7)*0.85;}}else{returnthis.daysOverdrawn*1.75;}}}重构代码:classAccount{constructor(){...}getbankcharge(){letresult=4.5;if(this._daysOverdrawn>0)result+=this.overdraftCharge;}getoverdraftCharge(){returnthis.Type.overdraftCharge(this);}}classAccountType{constructor(){...}overdraftCharge(account){if(this.isPremium){constbasecharge=10;if(account.dayOverdrawn<=7){returnbaseCharge;}else{returnbaseCharge+(account.daysOverdrawn-7)*0.85;}}else{returnaccount.daysOverdrawn*1.75;}}}2.移动字段在开发中,你会遇到有些烂代码,用了烂数据结构,代码逻辑不清晰,条理不清,纠结多了Clear,代码是一大堆莫名其妙的无用代码。因此,你通常可以做一些预先设计来尝试获得最合适的数据结构,而拥有驱动设计的经验和知识将有助于你设计数据结构。当然,即使你经验丰富,技术娴熟,在设计数据结构的时候也会犯错误,但是随着对问题的理解更深入,对业务逻辑的熟悉,你会考虑得更深更全面。过程中发现数据结构不符合要求,必须及时修复。如果允许有缺陷,代码就会变得复杂,问题就会堆积起来。每次调用函数,在传入参数时,总是需要传入另外一个字段作为参数,也就是在修改一条记录的同时,需要修改另一条记录,也就是说这里字段的位置是错误的。另外,如果你更新某个字段,需要同时对多个结构进行修改,那就意味着你需要正确移动这个字段。具体来说,保证源字段已经封装好,在目标对象上创建一个字段(以及对应的访问函数)并进行静态检查,保证源对象中可以正常引用目标对象,即调整源对象的访问函数使用目标对象字段。最后,删除源对象上的字段。原始代码:classUser{constructor(name,age,getName){this._getName=getName;this._age=age;this._name=name;}getgetName(){returnthis._getName;}}classUserType{constructor(firstName){this._firstName=firstName;}}重构代码:classUser{constructor(age,name){this._age=age;this._name=name;}getgetName(){returnthis._name.getName;}}classUserType{constructor(firstName,getName){this._firstName=firstName;this._getName=getName;}getgetName(){returnthis._getName;}}3.将语句移动到函数中重构代码时有几个黄金法则,其中最重要的是To"消除重复”的代码,将重复的语句抽象成函数,通过调用函数实现复杂代码的运行。4.将语句移至调用方。作为一个实体的coder,责怪的是设计一个结构一致、抽象得当的程序,而函数就是抽象的法宝。当然,并非所有手段都适用。随着系统能力的演进,原有设计的抽象边界逐渐向外扩散,变得模糊。从原来的单一整体,关注单一点,分化为多个不同的关注点。观点。函数边界的偏移是指之前在多个地方调用的行为需要在不同的点表现出不同的行为。这样,我们就可以将不同的行为从函数中移出,移到调用处。printHtml(outData,onData.html);functionprintHtml(outData,html){outData.write(`

title:${html.title}

`);outData.write(`

content:${html.content}

`);}即:printHtml(outData,onData.html);outData.write(`

content:${onData.html.content}

`);functionprintHtml(outData,html){outData.write(`

title:${html.title}

`);}5.用函数调用替换内联代码使用函数封装相关行为,提高代码表现力,一目了然解释代码的用途和功能,有助于消除重复代码。如果一段内联代码是对已有函数的重复,可以用函数调用来代替内联代码,可以实现业务逻辑的抽象。letflag=false;for(constcolorofcolors){if(color==="yellow")flag=true;}即:letflag=colors.includes("yellow");6.如果几行代码使用同一个数据结构,那么可以联合使用,让代码更容易理解,而不是夹在其他数据结构中间。然后在我们写完代码之后,我们需要审查它并收集高度相关的代码移动语句。通常,移动语句用作其他重构代码的抢先重构。constpricingPlan=rePricingPlan();constorder=reOrder();letcharge;constchargePerUnit=ricingPlan.uint;重构代码:constpricingPlan=rePricingPlan();constchargePerUnit=ricingPlan.uint;constcharge=reOrder();letcharge;常规开发会在一个周期内做多件事,目的是避免时间复杂度过高。有时候,一个循环中的代码太多,逻辑混乱,不方便我们日常的理解。因此,可以根据情况合理拆分循环,让每个循环只做一件事,更易于阅读和使用。letaveragePrice=0;lettotalCount=0;for(constpingoods){averagePrice+=p.price;totalCount+=p.count;}averagePrice=averagePrice/totalCount;重构代码:letaveragePrice=0;for(constpingwoods){averagePrice+=p.price;}lettotalCount=0;for(constpingoods){totalCount+=p.count;}averagePrice=averagePrice/totalCount;是不是看起来有点傻,但是当你用复杂的代码读起来,你会发现它非常清晰。8、用管道代替循环以往遍历数组和对象时,通常的做法是使用循环进行迭代。当然,你也可以使用更好的语言结构——“集合管道”来处理迭代(map和filter等)。集合管道允许使用一组操作来描述集合迭代,其中每个操作都是一个集合。常用方法:新建一个变量,用于存放参与循环过程的集合,从c循环的顶部开始依次移动循环中的每一个行为块。使用管道操作替换创建的集合变量,直到循环中的所有行为都被移动,最后删除循环。constusers=[];for(constiteminarrs){if(item.age===20)users.push(item.name);}//重构代码constusers=arrs.filter(item=>item.age===20).map(item=>item.name);9.删除死代码。将项目部署到生产环境可能会因为代码量大而造成较大的内存开销,无用的代码会拖累系统的运行速度,导致项目进度缓慢。当然,现在的编译器大多会自动去掉无用的代码,但是当你阅读理解代码的逻辑和原理时,会耗费你思考的时间和精力。当代码不再使用时,应立即删除。当你突然想再次使用它时,可以通过版本控制回滚。if(false){...}这是无用的代码,应立即删除。小结本文主要介绍移动字段、移动函数等移动方法。还有一些方法可以单独移动和调整语句的顺序。还可以调整代码的位置,拆分循环,使用流水线替换。