您现在的位置是:亿华云 > 应用开发

重构,有品位的代码之重构API

亿华云2025-10-04 03:55:13【应用开发】0人已围观

简介本文转载自微信公众号「前端万有引力」,作者 一川 。转载本文请联系前端万有引力公众号。写在前面伙伴们,最近事情有点多、空余时间都花在学习新知识、新技术以及巩固基础上了,在实践开发越来越觉得自己的技术和

本文转载自微信公众号「前端万有引力」,重构作者 一川 。有品转载本文请联系前端万有引力公众号。代码

写在前面

伙伴们,重构最近事情有点多、有品空余时间都花在学习新知识、代码新技术以及巩固基础上了,重构在实践开发越来越觉得自己的有品技术和能力有限,认识到了自己的代码短板和不足。后面我会把自己所学所看,重构以及在项目实践中对方法进行总结,有品分享给各位伙伴们共同学习、代码批评指正。重构

今天就继续分享《重构,有品有品味的代码代码》系列第八篇文章,As you know,模块和函数组成了软件的钢筋水泥,而api就是整个软件建筑的栋和梁。显而易见,在对软件开发有了深层次的理解,我们会发现如何改进api将更新数据的函数和读取数据的函数进行分割。让每个函数都做自己的本分,衔接它们之间的事情交给模块去调用。云南idc服务商

重构API

常见的重构API的方法有:

将查询函数和修改函数分离 函数参数化 移除标记参数 保证完整性 以查询取代参数 以参数取代查询 移除设值函数 以工厂函数取代构造函数 以命令取代函数 以函数取代命令

1. 将查询函数和修改函数分离

如果函数只是作为取值函数,没有其他多余的实现功能,那么这个函数是很单纯的、很有价值的东西。因为可以任意调用此函数,可以在整个项目的任意角落使用,无需担心有其它多余的累赘。记住:任何有返回值的函数,不应该有其它多余的功能,即命令和查询分开。

通常做法是:拷贝整个函数将其作为一个查询来命名,在新建的此查询函数中移除所有有附加功能的语句,并对其进行检查原函数的所有调用处。如果调用处使用了该函数的返回值,就将其改为调用新建的查询函数,并在下面立刻进行一次调用,香港云服务器且从原函数中移除返回值。

举个栗子

//原始写法 const setOk = ()=>{ ...} const selectPeopleFun = (people)=>{    for(let p in people){      if(p === "yichuan"){        setOk();       return "good";     }     if(p === "onechuan"){        setOk();       return "ok";     }     return "";   } } //重构写法 const setOk = ()=>{ ...} const findNull = (people)=>{    for(const p of people){      if(p === "yichuan"){        setOk();       return;     }     if(p === "onechuan"){        setOk();       return;     }   }   return; } const selectPeopleFun = (people)=>{    if(findNull(people) !== "") setOk(); } 

2. 函数参数化

当我们发现两个函数的逻辑非常相似,只有某些字面量值不同时,可以将其进行抽取合并成一个函数,以参数的形式传入不同的值,从而消除重复的逻辑。此重构方法能够使得逻辑更加简洁、复用性强,因为每个函数都可以进行多次使用。

举个栗子

//原始逻辑 function useFun(param){ ...} function baseFunction(param){    if(param < 0) return useFun(param);   const amount = bottomFun(param) * 0.1 + middleFun(param) * 0.2 + topFun(param) *0.3;   return useFun(amount); } function bottomFun(param){    return Math.min(param,100) } function middleFun(param){    return param > 100 ? Math.min(param,200) - 100 :0; } function topFun(param){    return param > 200 ? param - 200 : 0; } //重构代码 function commonFun(param,bottom,top){    return param > bottom ? Math.min(param,top) - bottom:0; } function baseFun(param){    if(param<0) return useFun(0);   const amount = commonFun(param,0,100) * 0.1 + commonFun(param,100,200) * 0.2 + commonFun(param,200,Infinity) *0.3;   return useFun(amount); } 

3. 移除标记参数

标记参数直接理解就是作为标记的参数,即通常调用者用其来只是被调用函数应该执行哪部分逻辑。但事与愿违,标记参数在实际使用过程中并没达到作为标记的作用,令人难以理解到底哪部分函数可以调用、应该如何调用。通常我们通过API查看哪部分是可调用函数,但是编辑参数却会进行隐藏函数调用中存在的差异性,在使用这些函数我们还得阅读上下文中标记参数有哪些可用的值。

要知道布尔值作为标记是多么荒唐的使用方法,网站模板因为其不能见名知意的传递信息,在函数调用时很难厘清true代表的含义,但是明确使用函数完成单独的任务,就显得清晰的多。

当然并非所有的类似参数都是标记参数,如果调用者传入的程序中不断传递的数据,那么这样的参数就不叫做标记参数。只有当调用者初入字面量值时,或者在函数内部只有参数影响了函数内部的控制流,此时作为参数就是标记参数。

移除标记参数不仅使得代码更加整洁,并且能够帮助开发工具更好的发挥作用。去掉标记参数后,代码分析工具能够更清晰体现“高级”和“普通”逻辑在使用时的区别。如果某个函数有多个标记参数,此时想要移除得花费功夫,得不偿失还不如将其保留,但是也侧面证明此函数做的太多,需要将其逻辑进行简化。

举个栗子

//原始代码 function setFun(name,value){    if(name === "height"){      this._height = value;     return;   }   if(name === "width"){      this._width = value;     return;   } } //重构代码 function setHeight(value){    this._height = value; } function setWidth(value){    this._width = value; } 

4. 保证完整性

当看到代码从一个记录结构中导出几个值,然后又把这几个值传递给一个函数,那么可以把整个记录传递给这个函数,在函数内部导出所需要的值。

//原始代码 const low = aRoom.dayRange.low; const high = aRoom.dayRange.high; if(plan.goodRange(low,high)){ ...} //重构代码 if(plan.goodRange(aRoom.dayRange)){ ...} 

5. 以查询取代参数

函数的参数列表应该总结该函数的可变性,标识出函数可能体现出行为差异的主要方式,但是参数列表又应该尽量避免冗余,因为短小精悍易理解。什么是冗余,就是倘若调用函数中传入一个值,而这个值由函数自己获取,这个本不必要的参数会增加调用者的难度,因为调用者不得不去找出此参数定义的位置。

如果想要移除得参数值只需要向另一个参数值查询即可得到,这就可以使用以查询代替参数;如果在处理的函数具有引用透明性,即在任何时候只要传入相同的参数值,该函数的行为永远一致,可以让它访问一个全局变量。

6. 以参数取代查询

在浏览函数实现时,会经常发现一些糟糕的引用关系,比如引用一些全局变量或者另一个想要移除得元素,其实可以通过将其替换成函数参数来解决,将处理引用关系的责任推卸给函数调用者。其实此重构思想是:改变代码的依赖关系,让目标函数不再依赖某个元素,将元素的值以参数形式进行传递给函数。当然,如果把所有依赖关系都变成参数,会导致参数列表冗长重复,其次倘若作用域间的共享太多,会导致函数间过度依赖。

具体做法:将执行查询操作的代码进行变量提炼,将其从函数体中分离,对现有函数体代码不再执行查询操作,而是使用上一步提炼的变量,对此部分代码使用函数提炼。使用内联变量就是把提炼出来的变量放到一个函数中,且对原先的函数使用内联函数。

targetFun(plan) const otherFun = { ...} function targetFun(plan){    curPlan =  otherFun.curPlan   ... } //重构 targetFun(plan) function targetFun(plan,curPlan){    ... } 

7. 移除设值函数

当为某个字段提供了设置函数,表示此字段被改变,如果不希望在对象创建之后字段被改变,就不要提供设值函数,同时声明此字段不可改变。但是呢,有些开发者喜欢通过访问函数来读取字段值,在构造函数内也是,这就会导致构造函数成为设值函数的唯一使用者,就这样你还不如直接移除设值函数呢,没有意义。

当然,对象有可能是由客户端通过脚本(通过调用构造函数,即一系列的设置函数)进行构造出来的,而不是只有一次简单的构造函数调用。在执行完创建脚本后,此新生对象的部分字段不应该再被修改,设值函数只能被允许在起初对象创建过程中被调用。其实此时也应该移除设值函数,能够更加清晰的表达意图。

class User{    get(){ ...}   set(){ ...} } //重构 class User{    get(){ ...} } 

8. 以工厂函数取代构造函数

很多面向对象语言都有构造函数用于对象的初始化,通常客户端会通过调用构造函数来新建对象。但对于普通函数而言,构造函数具有一定的局限性,通常只能返回当前所调用类的实例,就是无法根据环境或参数信息返回子类实例或代理对象。且构造函数名字是固定的,因此无法使用比默认名字更清晰的函数名,此外还需要通过特殊的操作符(关键字new)来创建实例调用。然而,工厂函数就不受限制,可以实现内部调用构造函数,也可以使用其他方式调用。

9. 以命令取代函数

函数可以是作为独立函数,也可以作为类对象中的方法,还是作为程序设计的基本构造模块。将函数封装成自己的对象成为命令对象,当然这种对象大多只服务于单一函数,获得该函数的请求并进行执行函数,就是这种对象存在的意义。

与普通函数相比,命令对象提供了更加强大的控制灵活性和更强的表达能力,除了函数调用本身,命令对象还可以作为支持附加的操作,比如撤销。可以通过命令对象提供的方法进行设置和取值操作,从而提升丰富的生命周期管理能力。

具体方法:为想要包装的函数创建一个空类,并根据该函数的名字命名类,将函数搬移到空类中,并对每个参数创建一个字段,在构造函数中添加对应的参数。

举个栗子

//原始代码 function user(name,work,address){    let result = "";   let addressLevel ="";   ...long code } //重构代码 class User{    constructor(name,work,address){      this._name = name;     this._work = work;     this._addrsss = address;   }   clac(){      this._result="";     this._addressLevel ="";     ...long code   } } 

10. 以函数取代命令

命令对象为处理复杂计算提供了强大的机制,可以轻松将原本复杂的函数拆分成多个方法,彼此之间通过字段进行状态共享。拆分后的方法可以分别进行调用,开始调用之前的数据状态也可以逐步构建,但是这种强大功能是有代价的。通常我们调用函数让其完成自身的任务,当此函数不是很复杂时,命令对象显得得不偿失,还不如使用普通函数呢。

通常的,将创建并执行命令对象的代码单独提炼到独立函数中,对命令对象在执行阶段用到的函数逐一使用内联函数。使用改变函数声明,将构造函数的参数转移到执行函数。对于所有的字段在执行函数中找到引用它的地方,并将其改为使用参数,将调用构造函数和调用执行函数两步进行内联到调用函数中。

举个栗子

//原始代码 class ChargeClass{    constructor(custom,param){      this._custom = custom;     this._param = param;   }   clac(){      return this._custom.rate * this._param   } } //重构代码 function charge(custom,param){    return custom.rate * param; } 

很赞哦!(274)