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

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

让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名申请雅安服务器托管、营销软件、网站建设、岢岚网站维护、网站推广。

写在前面

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

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

重构API

常见的重构API的方法有:

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

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

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

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

举个栗子

 
 
 
  1. //原始写法
  2. const setOk = ()=>{...}
  3. const selectPeopleFun = (people)=>{
  4.   for(let p in people){
  5.     if(p === "yichuan"){
  6.       setOk();
  7.       return "good";
  8.     }
  9.     if(p === "onechuan"){
  10.       setOk();
  11.       return "ok";
  12.     }
  13.     return "";
  14.   }
  15. }
  16. //重构写法
  17. const setOk = ()=>{...}
  18. const findNull = (people)=>{
  19.   for(const p of people){
  20.     if(p === "yichuan"){
  21.       setOk();
  22.       return;
  23.     }
  24.     if(p === "onechuan"){
  25.       setOk();
  26.       return;
  27.     }
  28.   }
  29.   return;
  30. }
  31. const selectPeopleFun = (people)=>{
  32.   if(findNull(people) !== "") setOk();
  33. }

2. 函数参数化

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

举个栗子

 
 
 
  1. //原始逻辑
  2. function useFun(param){...}
  3. function baseFunction(param){
  4.   if(param < 0) return useFun(param);
  5.   const amount = bottomFun(param) * 0.1 + middleFun(param) * 0.2 + topFun(param) *0.3;
  6.   return useFun(amount);
  7. }
  8. function bottomFun(param){
  9.   return Math.min(param,100)
  10. }
  11. function middleFun(param){
  12.   return param > 100 ? Math.min(param,200) - 100 :0;
  13. }
  14. function topFun(param){
  15.   return param > 200 ? param - 200 : 0;
  16. }
  17. //重构代码
  18. function commonFun(param,bottom,top){
  19.   return param > bottom ? Math.min(param,top) - bottom:0;
  20. }
  21. function baseFun(param){
  22.   if(param<0) return useFun(0);
  23.   const amount = commonFun(param,0,100) * 0.1 + commonFun(param,100,200) * 0.2 + commonFun(param,200,Infinity) *0.3;
  24.   return useFun(amount);
  25. }

3. 移除标记参数

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

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

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

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

举个栗子

 
 
 
  1. //原始代码
  2. function setFun(name,value){
  3.   if(name === "height"){
  4.     this._height = value;
  5.     return;
  6.   }
  7.   if(name === "width"){
  8.     this._width = value;
  9.     return;
  10.   }
  11. }
  12. //重构代码
  13. function setHeight(value){
  14.   this._height = value;
  15. }
  16. function setWidth(value){
  17.   this._width = value;
  18. }

4. 保证完整性

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

 
 
 
  1. //原始代码
  2. const low = aRoom.dayRange.low;
  3. const high = aRoom.dayRange.high;
  4. if(plan.goodRange(low,high)){...}
  5. //重构代码
  6. if(plan.goodRange(aRoom.dayRange)){...}

5. 以查询取代参数

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

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

6. 以参数取代查询

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

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

 
 
 
  1. targetFun(plan)
  2. const otherFun = {...}
  3. function targetFun(plan){
  4.   curPlan =  otherFun.curPlan
  5.   ...
  6. }
  7. //重构
  8. targetFun(plan)
  9. function targetFun(plan,curPlan){
  10.   ...
  11. }

7. 移除设值函数

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

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

 
 
 
  1. class User{
  2.   get(){...}
  3.   set(){...}
  4. }
  5. //重构
  6. class User{
  7.   get(){...}
  8. }

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

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

9. 以命令取代函数

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

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

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

举个栗子

 
 
 
  1. //原始代码
  2. function user(name,work,address){
  3.   let result = "";
  4.   let addressLevel ="";
  5.   ...long code
  6. }
  7. //重构代码
  8. class User{
  9.   constructor(name,work,address){
  10.     this._name = name;
  11.     this._work = work;
  12.     this._addrsss = address;
  13.   }
  14.   clac(){
  15.     this._result="";
  16.     this._addressLevel ="";
  17.     ...long code
  18.   }
  19. }

10. 以函数取代命令

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

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

举个栗子

 
 
 
  1. //原始代码
  2. class ChargeClass{
  3.   constructor(custom,param){
  4.     this._custom = custom;
  5.     this._param = param;
  6.   }
  7.   clac(){
  8.     return this._custom.rate * this._param
  9.   }
  10. }
  11. //重构代码
  12. function charge(custom,param){
  13.   return custom.rate * param;
  14. }

分享标题:重构,有品位的代码之重构API
分享地址:http://www.shufengxianlan.com/qtweb/news23/265323.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联