读书笔记 -《重构-改善既有代码的设计》Part2

重构 专栏收录该内容
4 篇文章 0 订阅


上一节 读书笔记 -《重构-改善既有代码的设计》Part1
下一节 读书笔记 -《重构-改善既有代码的设计》Part3

读书笔记 -《重构-改善既有代码的设计》Part2

代码的坏味道

“如果尿布臭了,就换掉它。”

Duplicated Code 重复代码

针对重复代码

  • 如果两个函数中有相同的表达式,采用 “Extract Method” 提取表达式形成新的方法
  • 如果两个统一基类派生的子类中有相同的表达式,采用 “Extract Method” 提取方法,然后采用 “Pull Up Method" 将方法放到基类中
  • 如果两个毫无关系的类出现重复代码,应该考虑 ”Extract Class“,提取重复代码到独立类中,然后在另一个类中调用新建的独立类对象
  • 始终需要考虑重复代码能否 “Form Template Method” 成为一个 ”Template Method“

重构方法:提炼函数、函数上移、提炼类塑造模板函数

Long Method 过长函数

函数短小的好处

不熟悉面向对象技术的人,常常觉得对象程序中只有无穷无尽的委托,没有进行任何计算。但最终你会发现,这些小小函数的价值非常大。”间接层“ 所能代码的全部利益 - 解释能力、共享能力、选择能力,都是由小型函数支持的

  • 积极的分解函数

    1. 函数名能解释函数用途

    2. 函数的长度不是关键,函数是用来做什么,怎么做才是关键

    3. 基本上,Extract Method 能解决函数过长问题。但如果函数内有大量参数,提炼的函数需要传递这些参数,往往降低了可读性。

      Replace Temp with Query – 消除临时变量

      Introduce Parameter Object / Preserve Whole Object – 创造参数对象,将过长的参数列表变得简洁

      Replace Method with Method Object – 如果还是有太多临时变量和参数,就该考虑这么做了

​ Decompose Conditional – 分解条件表达式

重构方法:提炼函数、以查询取代临时变量、引入参数对象、分解条件表达式、保持对象完整、以函数对象取代函数

Large Class 过大的类

不要试图让一个类完成所有事情

  • Extract Class
  • Extract Subclass
  • Extract Interface – 为每一种使用方式提炼接口,有助于类的分解
  • Duplicate Observed Data

重构方法:提炼类、提炼子类、提炼接口

Long Parameter List 过长参数列表

Introduce Parameter Object – 引入对象

Preserve Whole Object – 收集对象数据

Replace parameter with Method – method 返回值替代

重构方法:引入参数对象、保持对象完整、以函数对象取代函数

Divergent Change 发散式变化

一个类通常应为不同原因在不同方向上发生变化,就该考虑提炼各个方向上变化的新类,保证一种变化一种修改。

职责不单一

重构方法:提炼类

Shotgun Surery 霰弹式修改

一种变化需要在不同的类中作出修改,需要采用 Move Method, Move Field 把需要修改的代码放进同一个类。

Inline Class 把一系列相关行为放进同一个类,可能会造成少量 Divergent Change, 但仍可以轻易处理它们。

职责分散

重构方法:搬移函数、搬移值域

Feature Envy 依恋情结

如果一个函数调用了很多类的数据和方法,怎么进行优化呢?

不管使用 Extract Method 还是 Move Method,把握一个原则:

总是把一起变化的东西放在一起

Tips:可以了解几个精巧的设计模式,Strategy、Visitor、Self Delegation…

重构方法:搬移函数、提炼函数

Data Clumps 数据泥团

两个类中相同的字段,许多函数签名中相同的参数等等,总是绑在一起出现的数据你会总想让它们有自己的对象,为什么不去做呢?

Extract Class / Introduce Parameter Object / Preserve Whole Object 减少字段和参数个数

重构方法:提炼类、引入参数对象、保持对象完整

Primitive Obsession 基本类型偏执

不要总喜欢用基本类型作为函数参数,或聚合在一个类中,当有一大堆基本类型的时候,应该考虑它们之间又怎样的关系,是否能运用较小的对象包含它们。

重构方法:以对象取代数据值、以类取代类型码、以子类取代类型码

Switch Statements 令人发麻的 Switch 语句

面向对象程序的一个最明显特征就是:少用 switch - case 语句

可能你也遇到过,在不同函数甚至是同一个函数中运用了相同结构的 switch - case 语句,这样重复的工作让代码看起来糟糕至极。

重构方法:以子类取代型别码、以State/strategy 取代型别码、以多态取代条件式

新版重构这个坏味道被去掉了,但依然值得考虑

Parallel Inheritance Hierarchies 平行继承体系

它是 Shotgun Surgery 的特殊情况,在这种情况下,每当你为某个类添加一个类增加一个子类,必须也为另一个类响应的增加一个子类。

当发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,你就得考虑是不是这个问题。

重构方法:让一个继承体系的实例引用另一个继承体系的实例。如果再接再厉运用 Move Method (搬移函数)
和Move Field (搬移字段),就可以将引用端的继承体系取消

Lazy Class 冗余类

你所创建的每一个类,都得有人去理解它、维护它,这些工作都是要花钱的。

如果一个类的所得不值其价,它就应该消失。

如果某些子类没有做足够的工作,试试 Collapse Hierarchy;

对于几乎没用的组件,你应该以 Inline Class 应对

某些类的职责已经不值得单独提取成一个类

解决方法: 将其职责转移或声明成内部类

重构方法:折叠继承关系、将类内联化

Speculative Generality 夸夸其谈未来性

如果有人说 ”我想我们总有一天需要做这事“,那么应该注意这种”坏味道“。对于没用的类,没用的参数,没用的代码行,删除是个最好的选择。

Temporary Field 令人迷惑的临时字段

临时字段仅为某种特定情况设置,这种代码会让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。如果一个变量未被使用,常常会耗费你的时间去理解当初为什么它会存在,它是用来干什么的。

比如一个复杂的算法,需要好几个变量,往往会导致坏味道 Temporary Field 出现,有些字段只在使用该算法时才出现

Message Chains 过度耦合的消息链

如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后…这就是消息链。采取这种方式,意味用户的代码将会与查找过程中的导航结构紧密耦合,一旦对象关系发生变化,客户端不得不做出相应的修改。

采取什么方式? => Hide Delegate

你可以在消息链的不同位置进行这种重构,但也会出现一个问题,中间的一系列对象都变成了中间体”middle man“。

如果我们确认消息链的最终的对象时用来做什么的,然后 Extract Method 提炼除独立函数,在 Move Method 将函数导入消息链。

重构方法:隐藏委托关系

Middle Man 中间人

对象的基本特征之一就是封装 - 对外部世界隐藏其内部细节。封装往往伴随委托。所以也有可能过度使用委托。

应该使用 Remove Middle Man,直接和真正负责的对象打交道。

  • 如果 Middle Man 的函数只有少数几个,可以运用 InlineMethod 把它们放进调用端
  • 如果 Middle Man 还有其它行为,可以运用 Replace Delegation with Inheratance 把它编程直接负责的对象的子类。这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。

重构方法:移除中间人、将函数内联化、以继承取代委托

Inappropriate Intimacy 亲密关系

两个类过多的关注彼此的private 成分,可以通过Move Method和 Move Field方式解决,或则在两个类中间加一个中间人

重构方法:搬移函数、搬移值域、将双向关联改为单向

Alternative Classes with Difference Interfaces 接口不同功能一致的类

如果两个函数签名不同,但用途一致,请直接重命名它们;

如果两个类也是这种情况,考虑删除一个,或者合并它们。

Incomplete Library Class 不完美的类库

如果类库构造的存在缺陷,我们常常无法修改其中的类使它完成我们希望完成的工作。可以运用 Introduce Foreign Method 或 Introduce Local Extension 去添加新的行为。

工具库不够完美的情况下可以考虑外加函数 (Introduce Foreign Method) 或 引入本地扩展(Introduce Local Extension)

Data Class 数据类

Data Class 只是拥有一对数据字段和字段访问函数的类,作为数据容器,如果存在 public 的字段,请封装它们。

Refused Bequest 拒绝的馈赠

子类应该继承超类的函数和数据,但如果它们不想或不需要被继承,又该怎么办?

传统的做法,就是为这个子类新建一个兄弟类,把不需要被继承的放到这个兄弟类中,让超类只存在共享的东西。所以你常常会被建议”超类应该是 abstract 的“

不建议传统的做法,不要胡乱修改继承体系,应该运用 Replace Inheritance with Delegation 来达到目的。

重构方法:下移方法、下移字段、用委托替代继承

Comments 过多的注释

注释应该解释代码是干什么用的,过于形式的注释和无实际解释的注释没有必要。往往我们在命名字段、函数、类的时候就能够解决部分释义。

重构方法:提炼函数、重命名

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页

打赏作者

谁家的书

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值