【书中自有黄金屋】《重构-改善既有代码的设计》阅读总结

214 阅读13分钟

前言

为何阅读这本书

阅读这本书的初心在于,半年多的时间里一直在重构项目代码。

阅读了不少人移交过来的代码,项目代码整体看下来,就会发现代码的阅读起来非常费劲,并且复用性和拓展性都很差,另外代码逻辑上太绕让人费解。看这样的代码,简直内心在奔腾。

重构时,就会一边骂着一边改着,这种改造简直就是挑战人性,给人非常不好的体验。

在重构完一个项目时,就会发现糟糕代码的很多通病。

就是完全没有任何规则和规范,按照自己的规则来写,而好的代码会有一套规则约束,有很明确的规范。正因为脑中没有概念和理论支撑,才会写的如此【随心所欲】。

让我想起了托尔斯泰的一句话:幸福的人是相似的,不幸的人各有各的不幸。换做写代码就是:优雅的代码都是相似的,糟糕的代码各有各的毛病。

而理论支撑和方法论在哪呢?就要通过学习和阅读来获取。所以就找来这本书来阅读,以深刻深入学习这套理论。

花费5小时时间阅读完成本书,又花费3天下班时间将书中的观点进行汇总。整体而言,这本书的绝大部分观点在这次重构项目中都得到了应用和实践,所以在看到这些观点和方法时,会理解的更加深刻。

心得体会

  • 开发

    • 提炼短函数,保持单一职责原则,函数和数据应该被包装在形式良好的单元内。并将函数以用途命名

    • 函数应保持变化只在一处发生,如果不能保持则应考虑分离出变化的部分

    • 几个类中相同部分,应考虑提炼/分解函数,并继承。而代码太分散,应考虑合并代码

    • 降低类、方法和值域的耦合性,可考虑移动、提炼相关内容

    • 减少临时变量;临时表达式可考虑final。复杂表达式应考虑创建临时变量

    • 调用函数中参数过多,应考虑提炼类或者用类来表示

    • 多余的代码和注释就应被去掉

  • 测试

    • 小步前进、频繁测试

    • 临界值测试

    • 不断丰富测试用例

  • 重构记录

    • 应记录下来重构的过程、动机、案例、图 在这里插入图片描述

书中汇总

  • 代码的坏味道

    • 重复代码

      • Q:冗余代码

      • R:提取相同的代码到一个方法/类中,分割开来相同和差异部分

    • 过长函数

      • Q:过长难以理解

      • R:提取分解成不同的短函数,并用其用途进程命名

      • R:条件式和循环也可以提取函数

      • R:遵循单一职责原则,不同作用的代码抽取到不同的类中

    • 过大类

      • Q:类中过多的代码,可能会造成代码重复、混乱

      • R:提炼类和子类,为每一种使用方法提炼不同的接口

    • 过长参数列

      • Q:参数太多不易理解,可能会前后不一致,后续调用函数需要很多参数

      • R:只传需要的参数

      • R:用封装的实体类

    • 发散式变化

      • Q:某个类受多种多种变化的影响

      • R:改变只应发生在单一类中,以应对不同外界的变化

    • 散弹式修改

      • Q:每遇到变化,就需要多处不同的类做出小修改以响应之

      • R:移动类和方法,把所需的所有需要修改的代码放进同一个类中

    • 依恋情结

      • Q:函数对某类的兴趣高于对自己所处的host class的兴趣

      • 举例:某函数为了计算某值,从另一个函数对象调用几乎所有半打的取值函数

      • R:把函数移动到该去的地方。移动原则:哪个类拥有最多被此函数使用的数据,就把这个函数和那些数据放一块

    • 数据泥团

      • Q:两个类内相同值域、许多函数签名式中的相同参数,就可以抽取自己的类

      • R;相同的属性抽取到一个类中

    • 基本型别偏执

      • Q:使用大对象会增加性能开销

      • R:编写小对象,比如money类、表示范围的range

      • R:将原本单独存在的数据值替换为对象

    • switch惊悚现身

      • Q:switch表达式带来重复

      • R:运用多态

    • 平行继承体系

      • Q:每为某一个类增加子类,另一个类也须增加一个子类。会发现,继承体系的类名前缀和另一个的完全相同,则需要分离两个体系

      • R:让一个继承体系的实体指涉(参考、引用、refer to)另一个继承体系

    • 冗赘类

      • Q:类没有价值/多余

      • R:直接干掉多余的类

    • 夸夸奇谈未来性

      • Q:以为在某天就会用到,并做一些非必要的处理

      • R:去除掉不必要的参数、测试类等

    • 令人迷惑的暂时值域

      • Q:某个实例变量仅为某种特定情势而设,但并不是所有都会用到

      • R:利用提炼函数将这些变量和其相关的函数提炼到一个独立的类中

    • 过度耦合的消息链

      • Q:一个对象索求另一个对象,形成一个消息链。耦合性太高

      • R:看最终得到的对象是什么用途,然后看是否可以提炼函数

    • 中间转手人

      • Q:过度抽取函数/委托函数

      • R:直接和实责对象打交道,而去除掉不必要的中转

    • 狎昵关系

      • Q:两个类太亲密,需要花费很多的时间去探究类关系

      • R:移动方法或者类,以划清界线

      • R:抽取共同点到一个类中

    • 异曲同工的类

      • Q:两个函数做同一件事,却有不同的签名式

      • R:根据用途重命名类,或者直接抽取共用的部分

    • 不完美的程序库类

      • Q:程序库类不能满足需要

      • R:引用外部方法,或引用本地拓展

    • 幼稚的数据类

      • 没有用处的类
    • 被拒绝的遗赠

      • Q:子类中不需要的函数,就不该继承

      • R:抽取需要的父类,然后继承另一个类

    • 过多的注释

      • Q:注释过多会引起误会

      • R:去除掉不必要的注释

  • 构建测试体系

    • 自我测试

      • 频繁进行测试

      • 每个类都应该有一个用于测试的main(),可能不好实现

    • JUnit测试框架

      • 测试套件

        • 测试代码
      • 测试用例

        • 独立的测试类

        • 测试开始,先让它失败,比如错误期望值、导致失败/异常的值

        • 单元测试与功能测试

      • 测试装备

    • 添加更多测试

      • 观察类所做的一切,然后针对任何一项功能的任何一种可能的失败情况,进行测试

      • 测试应该是风险驱动

      • 寻找边界条件:寻找特殊的、可能导致失败的情况

      • 不断丰富测试案例

      • 一个测试类包含另一个测试类

      • 测试应集中在容易出错的地方

  • 重构名录

    • 重构的记录格式

      • 名称

        • 建造一个重构词汇表
      • 简短概要

        • 重构手法的适用场景,它所做的事情

        • 一个简短文句,介绍这个重构能够帮助的问题

        • 一段简短陈述,介绍该做的事情

        • 一幅速写图,简单展现重构前后示例;可展现代码,也可展现UML图

      • 动机

        • 为何需要这个重构

        • 什么情况下不该重构

      • 作法

        • 简明扼要地介绍如何一步步的进行此重构

        • 可以在未来回忆,快速记得怎么做的

        • 每个步骤都写得简短

        • 要安全的重构方式,应采用非常小的步骤,并在每个步骤后进行测试

      • 范例

        • 以一个十分简单的例子说明此重构如何运作

        • 帮助解释重构的基本要素

    • 寻找引用点

    • 这些重构准则有多成熟

      • 小步前进、频繁测试

      • 引入设计模式

  • 重新组织你的函数

    • 提炼函数

      • 动机:过长函数,或一段需要注释才能理解的代码

      • 函数长度不是问题,关键在于函数名称和函数本体之间的语义距离

      • 作法

        • 创建新函数,名以它做什么来命名

        • 颗粒度要小,单一原则,每个函数做一件事

    • 将函数内联化

      • 可以直接使用函数,不需要间接调用。
    • 将临时变量内联化

      • 将变量引用的动作,替换为它赋值的那个表达式本身

      • 如果临时变量并未被声明为final,那就声明为final,然后编译

    • 以查询代替临时变量

      • 应减少临时变量,将临时变量的表达式放在独立函数中
    • 引入解释性变量

      • 复杂的表达式,可以用临时变量将表达式分级,用变量名称解释表达式用途
    • 剖解临时变量

      • 临时变量应被赋值一次,如果临时变量承担太多责任,则应被拆解变量。

      • final作用在临时变量,只承担一个责任

    • 移除对参数的赋值动作

      • 对对象进行赋值,则相当于改变了引用关系。会降低代码的清晰度,且混淆传值和传址的方式

      • 建立一个临时变量,把待处理的参数值赋予它

      • 可在参数上加关键字final,从而强制它遵循不对参数赋值。不过建议在长函数用final

      • 如果返回的值有很多,可以封装成一个对象

    • 以函数对象取代函数

      • 局部变量太多不易提炼函数,可以用函数对象来替换

      • 将常用的变量、值域抽取为函数对象

    • 替换你的算法

      • 将算法替换为更清晰的算法

      • 测试:调用重构前后的方法,看返回值是否一致

  • 在对象之间搬移特性

    • 搬移函数

      • 某个类使用的次数比较多,则可以抽取函数到一个类中
    • 搬移值域

      • 某个值域被其所驻类之外的另一个类更多的使用,则可以调整位置
    • 提炼类

      • 一个类应该是一个清楚的抽象,处理明确的责任

      • 考虑哪些可以分离出去,并分离成一个单独的类

      • 对于函数中的某些数据经常同时变化甚至相依,应将其分离出去

    • 将类内联化

      • 类没有承担足够的责任,则应该合并函数
    • 隐藏委托关系

      • 某个类建立客户所需的所有函数,用以隐藏委托关系
    • 移除中间人

    • 引入外加函数

      • 建立一个函数做调用
    • 引入本地拓展

      • 添加额外的函数以拓展函数
  • 重新组织数据

    • 自封装值域

      • 为待封装值域建立取值/设置函数
    • 以对象取代数据值

      • 为待封装值域建立一个类
    • 将实值对象改为引用对象

      • 可使用多个对象作为新对象的访问点
    • 以对象取代数组

      • 数组不同元素的位置难以记住且难以理解
    • 复制被监视数据

      • 实现良好分层
    • 以符号常量/字面常量取代魔法数

      • 魔法值无法正常说明数字含义
    • 封装值域

      • public调整为private

      • 为public值域提供取值、设值函数

    • 以数据类取代记录

    • 以策略/状态取代型别码

      • 运用设计模式,每个类是不同的类别
  • 简化条件表达式

    • 分解表达式

      • 如if中有非常复杂的表达式,应提炼成一个独立的函数

      • 形成新分支和新函数,可以突出条件逻辑,更清晰表明每个分支的作用

    • 合并表达式

    • 合并重组的条件片段

      • 鉴别出执行方式不随条件变化而变化的代码

      • 共通的代码不止一条,就应考虑提炼函数

    • 移动控制标记

      • 不用通过标记来判断是否结束循环,可以直接用break或return
    • 以卫语句取代嵌套条件式

      • 某个条件极其罕见,就该单独检查该条件,并在条件为真时立刻从函数中返回

      • 卫语句要么从函数中返回,要么抛错

    • 以多态取代条件式

      • 抽取抽象函数
    • 引入null对象

    • 引入断言

  • 简化函数调用

    • 重新命名函数

      • 描述清楚函数的用途
    • 添加参数

    • 移除参数

    • 将查询函数和修改函数分离

      • 将查询和修改的函数分离
    • 令函数携带参数

      • 若干函数的工作是相似的,但函数却包含了不同的值,可统一函数
    • 以明确函数取代参数

      • 函数内完全取决于参数值而采用不同的反应,则应为每个可能值建立一个独立函数
    • 保持对象完整

      • 若将对象中的某几个数据作为参数传递给函数,则可改为使用传递整个对象
    • 以函数取代参数

      • 获取结果可以用函数
    • 引入参数对象

      • 某些参数同时出现,可能分布在不同类中,那么可以抽取到一个类中
    • 移除设置对象

    • 隐藏某个函数

      • 某个函数没被其他类用到,就改为private
    • 以工厂函数取代构造函数

    • 封装向下转型动作

      • 某个函数返回的对象,需要由函数调用者执行向下转型动作,将向下转型动作移到函数
    • 用异常取代错误码

    • 以测试取代异常

  • 处理概括关系

    • 值域上移

      • 两个值域相同的应移到父类
    • 函数上移

    • 构造函数本体上移

      • 子类中拥有一些函数,它们的本体代码几乎完全一致,那么应在父类中新建一个构造函数,并在子类构造函数中调用它
    • 函数下移

    • 值域下移

    • 提炼子类

      • 类中的某些特性只被某些实体用到,新建一个子类,将其特性移至子类
    • 提炼父类

      • 两个类有相似特性,那就为这两个类建立一个父类,将相同特性移至父类
    • 提炼接口

      • 提炼相同的接口子集
    • 折叠继承关系

      • 父类和子类没有太大区别,就合并为一体
    • 塑造模板函数

      • 继承是避免重复行为的一个强大工具
    • 以委托取代继承

      • 某个子类只使用父类接口的一部分,或根本 不需要继承数据,那么就为子类建立一个值域,或建立一个委托的类,然后去掉两者的继承关系
    • 以继承取代委托

  • 大型重构

    • 梳理并分解继承体系

      • 某个继承体系同时承担两项责任,建立两个继承体系,并通过委托关系让其中一个可以调用另一个
    • 将过程化设计为对象设计

      • 将数据记录变成对象,将行为分开,并将行为移入相关对象之中
    • 将领域和表述、显示分离

    • 提炼继承关系