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

重构,有味道的代码重构API

时间:2023-03-20 15:20:36 科技观察

本文转载自微信公众号《前端引力》,作者一川。转载本文请联系前端Gravity公众号。写在前面了,小伙伴们,最近事情比较多,空闲的时间都花在了学习新知识、新技术和夯实基础上。在实践发展中,我越来越感到自己的技能和能力是有限的,也认识到自己的不足和不足。.后面会回顾所学,总结项目实践中的方法,分享给各位小伙伴一起学习,批评指正。今天继续给大家分享?系列的第八篇。大家知道,模块和功能是软件的钢筋混凝土,API是整个软件大厦的栋梁。显然,随着对软件开发的深入了解,我们会发现如何改进api以将更新数据的功能与读取数据的功能分开。让每个函数各司其职,把它们之间的东西连接到模块去调用。重构API的常用方法是:将查询功能与修改功能分开。参数化函数。删除标记参数以确保完整性。用查询替换参数。用参数替换查询。删除设置器功能。用工厂函数替换构造函数。使用命令Replacefunctions用函数替换命令1.将查询函数和修改函数分开。如果函数只是作为一个值函数,没有其他多余的函数,那么这个函数就很简单,也很有价值。因为这个函数可以任意调用,所以可以用在整个项目的任何一个角落,而不用担心其他多余的负担。请记住:任何返回值的函数都不应有其他冗余函数,即单独的命令和查询。通常的做法是:复制整个函数并命名为查询,在新建的查询函数中去掉所有带有附加函数的语句,检查原函数的所有调用。如果在调用处使用了函数的返回值,则改为调用新创建的查询函数,并在下面立即进行调用,并从原始函数中删除返回值。举个栗子//原文写法constsetOk=()=>{...}constselectPeopleFun=(people)=>{for(letpinpeople){if(p==="yichuan"){setOk();return"good";}if(p==="onechuan"){setOk();return"ok";}return"";}}//重构constsetOk=()=>{...}constfindNull=(people)=>{for(constpofpeople){if(p==="yichuan"){setOk();return;}if(p==="onechuan"){setOk();return;}}return;}constselectPeopleFun=(人)=>{if(findNull(people)!=="")setOk();}2.函数参数化当我们发现两个函数的逻辑很相似,只有一些字面值不同时,我们可以使用它将提取合并成一个函数,以参数的形式传入不同的值,从而消除了重复的逻辑。这种重构方式可以让逻辑更加简洁,可重用,因为每个函数都可以多次使用。举个栗子//原逻辑functionuseFun(param){...}functionbaseFunction(param){if(param<0)returnuseFun(param);constamount=bottomFun(param)*0.1+middleFun(param)*0.2+topFun(参数)*0.3;returnuseFun(数量);}functionbottomFun(param){returnMath.min(param,100)}functionmiddleFun(param){returnparam>100?Math.min(param,200)-100:0;}functiontopFun(param){returnparam>200?param-200:0;}//重构代码functioncommonFun(param,bottom,top){returnparam>bottom?Math.min(param,top)-bottom:0;}functionbaseFun(param){if(param<0)returnuseFun(0);constamount=commonFun(param,0,100)*0.1+commonFun(param,100,200)*0.2+commonFun(param,200,Infinity)*0.3;returnuseFun(amount);}3。去掉tag参数tag参数直接理解为tag参数,即通常调用者用它来表示被调用函数应该执行哪部分逻辑。但是事与愿违,marker参数在实际使用过程中并没有实现标记的作用,让人很难理解函数的哪一部分可以调用,应该怎么调用。通常我们通过API查看哪部分是可调用函数,但是编辑参数会隐藏函数调用的差异。在使用这些函数的时候,我们要读取上下文中标记参数的可用值。你一定知道用布尔值作为标签是多么的荒谬,因为它们不能清晰自觉地传达信息,调用函数时也很难弄清楚true的含义,但是用函数来完成个别就清楚多了任务。当然,并不是所有类似的参数都是标注参数。如果调用者传入的是程序中不断传入的数据,那么这样的参数不叫标记参数。只有当调用者第一次输入字面量值时,或者只有函数内部的参数影响函数内部的控制流,此时,参数才被标记为参数。去掉flag参数不仅让代码更简洁,也有助于开发工具更好地发挥作用。去除标记参数后,代码分析工具可以更清晰地体现“高级”和“普通”逻辑在使用上的区别。如果一个函数有多个标志参数,要去掉它们会很费力,保留它们总比得到好。但是也证明了这个功能做的太多了,需要简化其逻辑。举个栗子//原代码函数setFun(name,value){if(name==="height"){this._height=value;return;}if(name==="width"){this._width=value;return;}}//重构代码functionssetHeight(value){this._height=value;}functionsetWidth(value){this._width=value;}4.确保完整性值,然后将这些值传递给一个函数,然后就可以将整条记录传递给这个函数,并在函数内部导出需要的值。//原代码constlow=aRoom.dayRange.low;consthigh=aRoom.dayRange.high;if(plan.goodRange(low,high)){...}//重构代码if(plan.goodRange(aRoom.dayRange)){...}5。查询的参数列表而不是参数函数应该总结函数的可变性,并确定函数可能表现出行为差异的主要方式,但参数列表应尽量避免冗余,因为它简短且易于使用。理解。什么是冗余就是如果给调用函数传入了一个值,而这个值是函数自己获取的,这个不必要的参数会增加调用者的难度,因为调用者要找出这个参数是在哪里定义的。如果要移除的参数值可以通过查询另一个参数值得到,可以用query代替parameters;如果被处理的函数具有引用透明性,即任何时候你只需要传入相同的参数值,该函数的行为总是相同的,允许它访问一个全局变量。6.用参数替换query在浏览函数实现时,你经常会发现一些不好的引用关系,比如引用了一些全局变量或者另一个你想移除的元素。其实可以通过将它们替换为函数参数来解决,将处理引用关系的责任传递给函数调用者。其实这次重构的思路是:改变代码的依赖关系,让目标函数不再依赖某个元素,将元素的值作为参数传递给函数。当然,如果所有的依赖都变成参数,那么参数列表会很长而且重复。其次,作用域之间共享过多,会导致函数之间过度依赖。具体做法:对执行查询操作的代码进行变量抽取,与函数体分离,不再对已有的函数体代码进行查询操作,而是使用上一步抽取的变量,将函数抽取用于这部分代码。使用内联变量就是将提取出来的变量放到一个函数中,对原函数使用内联函数。targetFun(plan)constotherFun={...}functiontargetFun(plan){curPlan=otherFun.curPlan...}//重构targetFun(plan)functiontargetFun(plan,curPlan){...}7.去除设定值功能当一个字段提供了设置功能时,意味着该字段被改变了。如果不想在对象创建后更改该字段,则不提供设置函数,并声明该字段不可更改。但是有些开发者喜欢通过access函数来读取字段值,也是在构造函数中,这样会导致构造函数成为setter函数的唯一使用者,所以你还不如直接去掉setter函数,毫无意义。当然,也可以由客户端通过脚本(通过调用构造器,即一系列设置函数)构造对象,而不仅仅是简单的构造器调用。执行创建脚本后,新对象的部分字段不可修改,只能在初始对象创建过程中调用设置函数。其实这个时候设置功能也应该去掉,这样可以更明确的表达意图。classUser{get(){...}set(){...}}//重构classUser{get(){...}}8.用工厂函数代替构造函数很多面向对象语言都有构造函数使用对于对象的初始化,通常客户端会通过调用构造函数来创建一个新的对象。但是对于普通函数来说,构造函数有一定的局限性。通常,它只能返回当前调用类的实例,即不能根据环境或参数信息返回子类实例或代理对象。并且构造函数的名称是固定的,所以不能使用比默认名称更清晰的函数名称。此外,创建实例调用需要一个特殊的运算符(关键字new)。但是工厂函数不限于此,可以实现为内部调用构造函数,也可以通过其他方式调用。9.用命令代替函数函数可以作为一个独立的函数,也可以作为类对象中的一个方法,或者作为程序设计的基本构建块。将一个函数封装到它自己的对象中,成为一个命令对象。当然,这些对象中的大多数只提供单一功能。获取函数的请求并执行函数,就是这个对象存在的意义。与普通函数相比,命令对象提供了更强大的控制灵活性和更强的表达能力。除了函数调用本身,命令对象也可以用来支持额外的操作,比如撤销。您可以使用命令对象提供的方法进行设置和取值操作,从而提高丰富的生命周期管理能力。具体方法:为要包装的函数创建一个空类,并根据函数名给类命名,将函数移动到空类中,为每个参数创建一个字段,在构造函数中添加对应的参数.举个栗子//原代码functionuser(name,work,address){letresult="";letaddressLevel="";...longcode}//重构代码classUser{constructor(name,work,address){this._name=name;this._work=work;this._addrsss=address;}clac(){this._result="";this._addressLevel="";...longcode}}10.complex函数替换命令对象计算提供了一种强大的机制,可以轻松地将原本复杂的功能拆分为多个方法,并通过字段相互共享状态。拆分方法可以单独调用,也可以逐步构建调用前的数据状态,但这种强大的功能是有代价的。通常我们调用一个函数,让它完成自己的任务。当功能不是很复杂时,命令对象得不偿失。最好使用普通函数。通常,创建和执行命令对象的代码被单独抽取成一个独立的函数,命令对象在执行阶段使用的函数一个一个地使用内联函数。使用变异函数声明,构造函数的参数被传递给执行函数。对于所有的字段,在执行函数中找到它被引用的地方,改成使用参数,将对构造函数的调用和执行函数调用内联到调用函数中,分两步进行。举个栗子//原代码classChargeClass{constructor(custom,param){this._custom=custom;this._param=param;}clac(){returnthis._custom.rate*this._param}}//重构代码functioncharge(自定义,参数){返回自定义。速率*参数;}