《重构:改善既有代码的设计》 —— 重构方法目录

前言

这部分主要就是记录了 《重构—改善既有代码的设计》 书中第6章 —— 第12章的一些重构手法的简单记录,如果想有更全面的了解,还是推荐看原书。

下面设计到重构方法的内容,括号中的数字表示对应原书的页数。

另外可以参考:

《重构:改善既有代码的设计》 —— 重构概念及代码坏味道

为本书前半部分的一些概念介绍和代码坏味道摘录。

第一组重构

这部分更多地就是说一些单纯的变量以及函数的简单重构手法,有一些很多就是平时我们已经无意识的在用了,但是一些书中的描述还是会让读者对数据,包括函数有了一些新的认识吧。

  • 提炼函数(106): 提炼函数的依据可以有很多,有从代码长度考虑的,有从复用角度考虑的,作者认为最合理的是将意图与实现分开,如果浏览代码需要花费一些时间就需要将其提炼成函数,并根据要做的事情命名它。无局部变量的可以直接提取,有局部变量的则需要视情况,作为参数或者直接在新函数中返回。

  • 内联函数(115): 这里属于提炼函数的反向操作,主要是对一些本身内容和名称就清晰的函数可以去除,直接使用函数本体。

  • 提炼变量(119):对于复杂表达式难阅读的,可以适当提炼变量来使表达式更加清晰,如果是在类中,并且适用于整个类 ,可以选择直接提成类成员。

  • 内联变量(123):更多地是变量没有给表达式本身更有表现力,可以内联变量,消除它。

  • 改变函数声明(124):主要是改函数声明以更加体现实际作用;改参数列表来改变函数与外面的交互方式。如果要重构一个对外发布的 API,将原函数声明成 “deprecated” 可以给客户时间转移成新函数(迁移式)。

  • 封装变量(132):在作者的角度,对于所有可变的数据,只要它的作用域超出单个函数,就会封装起来,只允许通过函数访问,数据作用域越大,封装就越重要,因为这样可以减耦合面向对象的数据如此强调私有(private)也是这个原因),可以根据是否要修改决定返回变量副本还是其他形式。应该封装什么?该如何封装。取决于数据被使用的方式,以及要修改数据的方式。

  • 变量改名(137):变量的名字改成更有意义的名字,好的名字需要练习

  • 引入参数对象(140)一组数据总是结伴同行,则可以将其代之以一个数据结构,这是一个会催生代码更深层改变,会创建出函数来捕捉围绕这些数据的共生行为。这些数据结构提升为新的抽象概念,可以帮助我们更好地理解问题域。

  • 函数组合成类(144):如果一组函数形影不离地操作同一块数据,通常可以组件一个类了。可以 用类简化函数调用,还可以发现其他的计算逻辑,将它们重构进行新的类中。

  • 函数组合成变换(149):只有将数据与操作放在一起,用起来才方便,写一个变换函数来集中操作相关数据和类。

  • 拆分阶段(154):看见一段代码在同时处理两件事情时,就可以将其拆分成独立的模块,可能需要一个中转数据结构,例如编译器的分段,每一步边界明确,则可以更聚焦思考其中一步,不是理解其他的细节。

封装

类是为了隐藏信息而生的,但是过度隐藏就会冗余。这部分进一步体现在新的抽象上,主要还是通过封装的手法来进行新的抽象,不管是数据上的还是类上的,带来问题域的简化。

  • 封装记录(162):用类来替换一些记录性结构(map,hashmap,dict等),类的数据结构更直观,更多地隐藏结构细节,只阅读类代码,就可以知道数据的所有用法。
  • 封装集合(170):要更改集合数据也封装起来,并且为其他使用者提供操作的方式来修改,并且是返回数据的一个副本。封装所有的可变数据,很容易看清数据被修改的地点和修改方式。
  • 以对象取代基本类型(174):当发现某个数据的操作不仅仅局限于打印时,会为它创建一个新类,为日后的业务逻辑添加有用。
  • 以查询取代临时变量(178):某些临时变量可以直接提成函数,就不需要将变量作为参数提炼出来给小函数了,在类中很好用。
  • 提炼类(182):决定如何分解类所负的责任,创建一个新的类,用以表现从旧类中分离出来的责任。
  • 内联类(186):将一个不需要的类塞进另一个类中,一般是可以先内联,然后联系上下文,再使用提炼手法。
  • 隐藏委托关系(189):封装意味着每个模块尽可能少了解系统的其他部分,因此,一旦发生变化,需要了解这一变化的模块就会比较少,使变化容易进行。(责任链设计模式)
  • 移除中间人(192):如果有过多地转发函数,也不是很合适,所以可以适当地移除中间的转发。
  • 替换算法(195):用比较简单的方式取代复杂的方式。

搬移特性

这部分主要就是类间关系,以及函数间关系的重构手法,尽量保证高内聚,并且关联大的代码放在一起。

  • 搬移函数(198):模块化是优秀软件的核心所在,任何函数都要具备上下文环境才能存活,类是模块化的重要手段。使用搬移函数的一大原因是它频繁引用其他上下文的元素,而对自身上下文元素关心甚少。可以是函数从内搬到外,或者类之间搬。
  • 搬移字段(207):往往数据结构才是一个健壮程序的根基,一个适应于问题域的良好数据结构可以让代码变得很简答明了。一个糟糕的数据结构则会招致许多无用的代码。这些代码更多地是在差劲的数据结构中纠缠不清,而非实现系统有用的行为。坏的数据结构就会隐藏程序的真实意图。
  • 搬移语句到函数(213):如果某个语句与一个函数放在一起更像一个整体,则应该将该语句搬到函数里去。
  • 搬移语句到调用者(217):作为程序员,职责是设计出结构一致,抽象合适的程序。函数边界发生偏移的一个征兆就是,以往在多个地方共用的行为,如今需要在某些调用点上表现出不同的行为,则将表现不同的语句搬移到调用点,函数只保留共用的部分。
  • 以函数调用取代内联代码(222)
  • 移动语句(223):让存在关联的东西一起出现,可以使代码更容易理解。
  • 拆分循环(227):使循环只干一个事,也是为其他手法做铺垫(提炼函数等)
  • 以管道取代循环(231):Lines.slice.filter.map 等这种连续的调用来代替之前的各种 for 循环。
  • 移除死代码(237):防止对理解代码有其他干扰。

重新组织数据

  • 拆分变量(240):一个中间变量值负责一个责任,多个则应该用多个变量来表示,对输入参数赋值传出,可以 copy 一份操作再返回。
  • 字符改名(244)在一个软件中上做的工作越多,对数据的理解就越深,有必要将加深的理解融入程序中。
  • 以查询取代派生变量(248):可变数据是软件最大的错误源头,在类成员使用中,尽量直接调用类成员(即查询)来避免生成很多中间变量(派生变量)。
  • 将引用对象改为值对象(252):引用对象可以理解为有改变接口对象,值对象则是一种只读对象,有需要的是有值对象一般是需要修改属性时直接创建一个新的对象。
  • 将值对象改为引用对象(256):与上面的方式相反。

简化条件逻辑

程序的大部分威力来自条件逻辑,而复杂度同样来自条件逻辑。对于条件表达式尽量就是减少嵌套,或者就是以多态来替换。

  • 分解条件表达式(260):if 的条件和分支语句都可分解成函数,然后再调用。
  • 合并条件表达式(263):多个条件检查为一致的行为服务,则可以合并不用分开写。
  • 以卫语句取代嵌套条件表达式(266):即 if(condition) return result; 此类提前条件返回的语句叫做卫语句,以此来减少嵌套(else 语句)。还可以为了提前返回直接反转要判断的条件来进一步较少嵌套。
  • 以多态取代条件表达式(272)
  • 引入特例(289)
  • 引入断言(302):断言主要是体现程序运行到这一点时,对当前状态做了何种假设,只用来检查"必须为真"的条件。

重构 API

好的 API 会把更新数据的函数与只是读取数据的函数清晰分开。这部分其实就会涉及到一些设计模式中的内容了,例如命令对象,对于就是 command 模式。

  • 将查询函数和修改函数分离(306)
  • 函数参数化(310):两个函数逻辑非常相似,只有一些字面量不同,则可以合并为一个函数,以参数的形式传入不同的值来消除重复。
  • 移除标记参数(314):标记参数是指调用者用它来指示被调用函数应该执行哪部分。
  • 保持对象完整(319):代码从一个结构中导出几个值,又把这几个值一起传递给一个函数,更应该直接传入整个对象在函数内部带出要的值。即都是属于 A类的参数, A.name,A.value 作为函数的两个参数一起传进函数,然后给到另一个函数使用。更应该直接传入 A 完整对象。如果好几处代码都在使用对象的一部分功能,可能意味着应该用提炼类(182)把这一部分功能单独提出去。
  • 以查询取代参数(324):将部分参数的获得总结成一个函数,然后供使用者调用而不再是以参数形式传递。
  • 以参数取代查询(327):主要是想让函数不再依赖于某个元素,则将其以参数形式传递出去。将查询变成参数后,就迫使调用者来弄清楚如何提供正确的参数值,会增加调用者的复杂度,而查询则将责任转移给了函数本身,如何获得正确参数值的责任。
  • 移除设值函数(331):如果不希望某个字段创建之后还被修改则应该删除设置函数,只在初始化构造时可设值。(需要修改属性时,就创建新的对象,即上面提到的值对象
  • 以工厂函数取代构造函数(334)
  • 以命令取代函数(337):复杂函数转成类对象。这里的命令就是指命令对象,用类来封装一个函数的调用请求。
  • 以函数取代命令(344):命令对象为处理复杂计算提供了强大的机制,借助命令对象将原本复杂的函数拆分出多个方法,彼此间通过字段共享状态,拆解后方法可以分别调用,但是强大之外是复杂度的代码,若只是用其中给一个函数则可以变回普通函数。

处理继承关系

这部分也是很多设计模式里会用到的一些手法。

  • 函数上移(350):某个函数在各个子类中的函数体相同,则使用函数上移,从子类提到父类中来消除重复。
  • 字段上移(353):变量子类共同,上移到父类。
  • 构造函数本体上移(355):子类构造中有共同的初始化过程可以上移到父类,子类直接调用。
  • 函数下移(359):如果父类中某个函数只与一个(少数几个)子类有关,则最好将其从父类中挪走,放到真正关系它的子类中。
  • 字段下移(361):同上
  • 以子类取代类型码(362):类似前面的工厂函数,引入子类。
  • 移除子类(369):有时添加子类是为了应对未来的功能,结果构想中的功能压根没被构造出来,或用了另一种方式构造,使子类不再被需要,则可以删除子类,替换成父类中的一个字段。
  • 提炼超类(375):看到两个类在做相似的事,则可利用基本的继承机制把它们的相似之处提炼到超类。
  • 折叠继承体系(380):经过一段时间后子类与父类已经乜有多大差别,则可以将子类与超类合并。
  • 以委托取代子类(381):如果对象的行为有明显的类别之分,继承是很自然的表达方式,但是继承也有短板,导致行为不同的原因可能有很多种,但是继承只能用在处理一个方向上的变化,“人"根据年龄可以是"老人"和"年轻人”,根据收入可以是"穷人"和"富人",但是不能同时采用两种继承方式。更大地问题是,继承个类之间引入非常紧密的关系,将责任委托给另一个继承体系,对象组合优于继承
  • 以委托取代超类(399):使用委托关系能更清晰地表达"这是另一个东西,我只需要用到其中携带的一些功能"。如果超类的一些函数对子类并不适用,就说明我不应该通过继承来获得超类的功能。没有继承关系,直接就是委托。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值