zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

【前端】重构,有品位的代码 05── 搬移特性

2023-03-09 22:02:59 时间

写在前面

本文是《重构,有品位的代码》系列第五篇文章,前面文章主要介绍的重构手法是关于如何新建、移除或重命名程序的元素。当然,不只是只有这些手法,还有类型的重构也是很重要的,主要是在不同上下文间搬移元素。可以通过搬移函数手法在类与其他模块之间搬移函数,同样的也有搬移字段手法,还有其它手法将在本文中将逐一介绍...

前情回顾:

  • 《重构,有品位的代码 01──走上重构之道》
  • 《重构,有品位的代码 02──构建测试体系》
  • 《重构,有品位的代码 03──常用重构手法》
  • 《重构,有品位的代码 04──封装》

常见的搬移特性手法

在平时开发中,经常会在代码中使用到搬移特性,但是并不知道是做了什么搬移特性,现在我们将常用的搬移特性手法进行总结如下:

  • 搬移函数
  • 搬移字段
  • 搬移语句到函数
  • 搬移语句到使用者
  • 以函数调用取代内联代码
  • 移动语句
  • 拆分循环
  • 以管道取代循环
  • 移除死代码

1. 搬移函数

模块化能够确保我们的代码块间的联系易于查找、直观易懂,能够保证相互关联的软件要素集中在一块,便于我们理解和管理。与此同时,随着对代码的理解加深,了解到那些软件要素如何组织最为恰当,此时需要通过不断地搬移元素进行重新模块化。

函数是存活在上下文中的,这个上下文可能是全局的,也有可能是当前所在模块进行提供的。而类即为主要的模块化手段,作为函数的上下文,此外通过函数嵌套的方式,外层函数也可为内层函数提供上下文。简而言之,模块可以为函数提供存活的上下文环境。

由于在某些代码频繁引用其他上下文中的元素,即与其他上下文的元素关系紧密,而对于自身上下文中的元素关心甚少,此时就可以考虑将联系密切的元素进行归纳,取得更好的封装效果。那么有以下情况,你可以进行搬移函数的操作:

  • 某段代码需要频繁调用别处函数
  • 在函数内部定义帮助函数在别处也有调用
  • 在类中定义函数

通常的,首先检查函数在当前上下文中引用的所有程序元素(包括变量和函数),考虑是否需要将它们进行搬移,并对待搬移函数是否具有多态性进行检查。将函数复制一份到目标上下文中,调整函数使得适应新的上下文。执行静态检查,设法从源上下文中正确引用目标函数,修改源函数,使之成为一个纯委托函数。

原始代码:

  1. class Account{ 
  2.   constructor(){ 
  3.     .... 
  4.   } 
  5.   get bankCharge(){ 
  6.     let result = 4.5; 
  7.     if(this._daysOverdrawn> 0) result += this.overdraftCharge; 
  8.   } 
  9.    
  10.   get overdraftCharge(){ 
  11.     if(this.type.isPremium){ 
  12.       const basecharge = 10; 
  13.       if(this.dayOverdrawn <= 7){ 
  14.         return baseCharge; 
  15.       }else
  16.         return baseCharge + (this.daysOverdrawn - 7) * 0.85; 
  17.       } 
  18.     }else
  19.       return this.daysOverdrawn * 1.75; 
  20.     } 
  21.   } 

重构代码:

  1. class Account{ 
  2.   constructor(){ 
  3.     ... 
  4.   } 
  5.   get bankcharge(){ 
  6.     let result = 4.5; 
  7.     if(this._daysOverdrawn> 0) result += this.overdraftCharge; 
  8.   } 
  9.   get overdraftCharge(){ 
  10.     return this.Type.overdraftCharge(this); 
  11.   } 
  12.  
  13. class AccountType{ 
  14.   constructor(){ 
  15.     ... 
  16.   } 
  17.   overdraftCharge(account){ 
  18.     if(this.isPremium){ 
  19.       const basecharge = 10; 
  20.       if(account.dayOverdrawn <= 7){ 
  21.         return baseCharge; 
  22.       }else
  23.         return baseCharge + (account.daysOverdrawn - 7) * 0.85; 
  24.       } 
  25.     }else
  26.       return account.daysOverdrawn * 1.75; 
  27.     } 
  28.   } 

2. 搬移字段

在开发中你是否会遇到一些糟糕的代码,使用了糟糕的数据结构,代码的逻辑并不清晰条理,更多的是各种纠缠不清,代码很多令人费解的无用代码。因此,通常可以做些预先的设计,设法获取最恰当的数据结构,而具备驱动设计方面的经验和知识,将有助于你设计数据结构。

当然,即使经验丰富、技能熟练,也会在设计数据结构的时候犯错,但是随着对问题理解的深入,对业务逻辑的熟悉,便会考虑到更深更全面。在过程中发现数据结构不适应需求,便要及时进行修缮,如果容许瑕疵存在便会导致代码复杂化,问题累积。

在你每次进行调用函数时,在传入一个参数时,总是需要伴随另外的字段作为参数传入,即修改一条记录同时需要修改另一条记录,那么意味着此处的字段位置放置错误。另外的,假设你更新某个字段,同时需要在多个结构中做出改变,那么就意味着你需要将此字段进行正确的搬移。

具体的,确保源字段已经进行良好封装,在目标对象上创建字段(及对应的访问函数)并执行静态检查,确保源对象里能够正常引用目标对象,即调整源对象的访问函数能够使用目标对象的字段。最后,移除源对象上的字段。

原始代码:

  1. class User
  2.   constructor(name,age,getName){ 
  3.     this._getName = getName; 
  4.     this._age = age; 
  5.     this._name = name
  6.   } 
  7.   get getName(){ 
  8.     return this._getName; 
  9.   } 
  10.  
  11. class UserType{ 
  12.   constructor(firstName){ 
  13.     this._firstName = firstName; 
  14.   } 

重构代码:

  1. class User
  2.   constructor(age,name){ 
  3.     this._age = age; 
  4.     this._name = name
  5.   } 
  6.   get getName(){ 
  7.     return this._name.getName; 
  8.   } 
  9.  
  10. class UserType{ 
  11.   constructor(firstName,getName){ 
  12.     this._firstName = firstName; 
  13.     this._getName = getName; 
  14.   } 
  15.   get getName(){ 
  16.     return this._getName; 
  17.   } 

3. 搬移语句到函数

在重构代码时有几条黄金准则,其中最重要的就是要“消除重复”代码,对重复语句进行抽象到函数中,通过调用函数来实现复杂代码的运行。

4. 搬移语句到调用者

作为搬砖码农的指责就是设计结构一致、抽象合宜的程序,而函数就是抽象的制胜法宝。当然所有的手段都并非放之四海而皆准的法则,随着系统能力的演变,最初设计的抽象边界逐渐向外扩散变得模糊,从原先单独整体、聚焦唯一点,分化成多个不同关注点。

而函数边界发生偏移,意味着之前多个地方调用的行为,现在需要会在不同点表现出不同的行为。这样,我们可以把不同表现行为从函数中挪出,将其搬移到调用处。

  1. printHtml(outData,onData.html); 
  2.  
  3. function printHtml(outData,html){ 
  4.   outData.write(`<p>title:${html.title}</p>`); 
  5.   outData.write(`<p>content:${html.content}</p>`); 

即:

  1. printHtml(outData,onData.html); 
  2. outData.write(`<p>content:${onData.html.content}</p>`); 
  3.  
  4. function printHtml(outData,html){ 
  5.   outData.write(`<p>title:${html.title}</p>`); 
  6.    

5. 以函数调用取代内联代码

使用函数可以将相关行为进行打包,提升代码的表达能力,清晰的解释代码的用途和作用,有助于消除重复的代码。如果某段内联代码是对已有函数进行重复,那么可以使用一个函数调用来取代内联代码,可以实现业务逻辑的抽象。

  1. let flag = false
  2. for(const color of colors){ 
  3.   if(color === "yellow") flag = true

即:

  1. let flag = colors.includes("yellow"); 

6. 移动语句

如果有几行代码使用了相同的数据结构,那么可以使其关联使用,使得代码更易理解,而不是夹在其他数据结构中间。那么在我们写完代码后,需要进行审读,将关联性强的代码移动语句进行聚集。通常,移动语句作为其他重构代码的先提重构手段。

  1. const pricingPlan = rePricingPlan(); 
  2. const order = reOrder(); 
  3. let charge; 
  4. const chargePerUnit = ricingPlan.uint; 

重构代码:

  1. const pricingPlan = rePricingPlan(); 
  2. const chargePerUnit = ricingPlan.uint; 
  3. const order = reOrder(); 
  4. let charge; 

7. 拆分循环

在常规的开发中,会在一次循环中做多件事情,意图让其避免过高的时间复杂度。有的时候,在一次循环中代码过多、逻辑混乱,反而不便于我们日常理解。因此可以根据情况合理拆分循环,使其每次循环只做一件事情,更便于阅读使用。

  1. let averagePrice = 0; 
  2. let totalCount = 0; 
  3. for(const p in goods){ 
  4.   averagePrice += p.price; 
  5.   totalCount += p.count
  6. averagePrice = averagePrice / totalCount; 

重构代码:

  1. let averagePrice = 0; 
  2. for(const p in goods){ 
  3.   averagePrice += p.price; 
  4. let totalCount = 0; 
  5. for(const p in goods){ 
  6.   totalCount += p.count
  7.  
  8. averagePrice = averagePrice / totalCount; 

是不是看起来有点傻,当你在复杂代码中阅读会发现很清晰。

8. 以管道取代循环

在过去进行数组、对象遍历时,通常做法是使用循环进行迭代,当然也可以使用更好的语言结构———”集合管道“来处理迭代(map和filter等)。集合管道允许使用一组运算来描述集合迭代过程,其中每种运算都是一个集合。

通常做法:创建一个用于存放参与循环过程的集合的新变量,从c循环顶部开始,将循环内的每块行为依次搬移。在创建的集合变量中用管道运算进行替换,直到循环内的全部行为进行搬移完毕,最后将循环进行删除。

  1. const users = []; 
  2. for(const item in arrs){ 
  3.   if(item.age === 20) users.push(item.name); 
  4.  
  5. //重构代码 
  6. const users = arrs 
  7. .filter(item=>item.age === 20) 
  8. .map(item=>item.name); 

9. 移除死代码

在将项目部署在生产环境中,可能会因为代码量太大而造成更大的内存开销,无用代码会拖累系统的运行速度,导致项目进程缓慢。当然,多数的现在编译器会自动将无用代码进行移除,但是在你阅读理解代码逻辑和原理时,会让你花费时间去思索,耗费精力。在代码不再使用时,应当立即删除,当你突然又想使用时可以通过版本控制回滚。

  1. if(false){ 
  2.   ... 

这是一句无用代码,应当立刻删除。

小结

在本文中,主要介绍了搬移字段、搬移函数等搬移手段,也有单独对语句搬移、调整顺序的,也可以调整代码的位置,对循环进行拆分、使用管道替换等方法。