本文是对 重构(第2版) 的整理,会包含以下内容
- 概述,包含什么是重构、重构目的
- 哪些需要重构
- 重构方法
每种需要重构的场景和重构方法并不是一一对应的,每种待重构场景后面会包含建议的重构方法。
和重构紧密相关的是单元测试,单元测试会保证重构的稳定性,相关内容会在后续文章中具体展开。
1. 概述
1.1 定义
重构(Refactoring )可以用来表示修改软件内部结构以使更容易理解,而不影响对外行为。可以用作动词或名词。
重构是结构调整(restructuring)的一种特定形式,指的是运用大量小步骤一步步达成大规模的修改,即使重构没有完成也可以停下来不影响使用。
我们平时使用重构这个词时可能经常泛指restructuring,一个大功能的结构修改可能需要几天,然后才能整体分支合并。
1.2 重构目的
- 维护软件设计
当人们为了短期目标多次修改软件内部逻辑时,比如需求变更,代码就会偏离以前的结构,比如出现重复代码。因此我们应该经常重构维护以前的状态。
如果因为新的需求,造成原来的设计不适合而要大面积改设计,在这里不被称为重构。
- 增加可读性
方便后续维护
- 排查bug
2 哪些需要重构
书中将需要重构的标准称为Bad Smells,具体可细分为以下,其中的模块可以理解为class或者函数等带有独立作用域的单位,其中解决方案具体讲解在下一章
- Mysterious Name 不恰当的命名
- Change Function Declaration
- Rename Variable
- Rename Field
- Duplicated Code 相同功能的重复代码
- 当同一个类中两个函数表达式相同,则使用Extract Function提取函数
- 如果重复代码只是相似,则使用Slide Statements将同类语句放在一起
- 如果不同子类有重复代码,可以使用Pull Up Method将其提升到基类
- Long Function 太长的函数不易理解
- 一般只需要Extract Function将适合在一起的组成一个函数提出来
- 如果含有大量参数和临时变量,不方便直接提取,则应运用Replace Temp with Query消除临时变量,使用Introduce Parameter Object和Preserve Whole Object 将过长的参数变得简洁
- 如果还有很多临时变量和参数,则应使用Replace Function with Command
- 对于其中的条件表达式,用Decompose Conditional处理,对于庞大的switch使用Extract Function将每种情况编程独立的函数调用,对于多个函数基于同一条件的分支选择,可以使用Replace Conditional with Polymorphism进行多态调用
- 对于循环和循环内的代码提炼到独立函数中,可以使用Split Loop
- Long Parameter List 过长的参数列表
- 如果其中一个参数可以通过另一个参数获得,则使用Replace Parameter with Query去掉相关参数
- 如果参数是从某个对象抽出来的,则使用Preserve Whole Object 传递整个对象
- 如果几项参数总是同时出现,应使用Introduce Parameter Object 将他们放在同一个对象中
- 如果参数中含有区分函数的flag,则使用Remove Flag Argument将其移除
- 如果多个函数有同样的参数,则可以使用Combine Functions into Class将函数组合成类,将参数变为类的参数
- Global Data 全局数据难以维护
- 首先应该Encapsulate Variable封装变量,将全局变量使用一个函数包起来,控制访问
- Mutable Data 可变数据对不同使用场景的影响,函数式编程是建立在不可变数据基础上的
- 使用Encapsulate Variable来确保数据更新时只能通过特定方法修改
- 如果一个变量用于存储不同东西,则应使用Split Variable拆分成多个变量
- 使用Slide Statements和Extract Function把逻辑从处理更新的操作中搬离,将没有副作用的代码和执行数据操作的代码分开
- 设计api时应使用Separate Query from Modifier 确保不会调用到有副作用的代码,除非真的需要
- 使用Remove Setting Method 设为只读
- 如果可变数据在变的地方会修改,应使用Replace Derived Variable with Query
- 如果可变函数的作用域大,则使用Combine Functions into Class 和Combine Functions into Transform限制对变量的修改途径
- 如果一个变量内部包含数据,应使用Change Reference to Value将引用替换为值
- Divergent Change 如果一个模块因为不同原因修改
- 如果不同原因存在次序关系,则应使用Split Phase按步骤拆开
- 如果两者之间存在来回调用,则应使用Move Function 将其分开
- 如果存在两类逻辑,则使用Extract Function按逻辑分开
- 如果模块以类形式定义,则使用Extract Class 拆分成不同的类
- Shotgun Surgery 某种变化需要修改多个模块,容易漏掉某个修改
- 使用Move Function和Move Field将所有修改代码放进同一个模块
- 如果多个函数操作类似的数据,可以使用Combine Functions into Class将其组合成类
- 如果可通过转换可使用函数组合成变换Combine Functions into Transform转化为对象属性
- 如果多个模块具有先后关系,则使用Split Phase按阶段拆分
- 最常用的时使用内联相关的策略,比如Inline Function、Inline Class将不该拆分的逻辑放在一起
- Feature Envy 多个模块之间过度耦合,通信频繁
- 当某个函数为了计算某个值需要从另一个模块频繁取值,应使用Move Function 将函数移过去,如果只有函数的一部分依赖这些数据,则可以先使用Extract Function抽离函数再Move Function
- 当一个函数用到多个模块的数据,可以将函数放在最多使用的类中,或者先使用Extract Function再Move Function到各自位置
- Data Clumps 不同对象中含有相同的字段,其中两个或多个可以被一个新对象代替
- 找出这些字段出现的地方,运用Extract Class将他们提炼到统一的对象中,然后使用Introduce Parameter Object和Preserve Whole Object处理参数
- Primitive Obsession 不准确的原始类型,比如用数字表示货币、物理量等,而忽略单位
- 可以使用Replace Primitive with Object 将原始数据替换为对象
- 如果被替换的数据表示类型码,则使用Replace Type Code with Subclasses和Replace Conditional with Polymorphism的组合将其替换
- 如果有一组总是同时出现的基本类型,应使用Extract Class和Introduce Parameter Object来处理
- Repeated Switches 重复的switch,会造成想增加一个选择分支时需要更新所有的switch
- 使用多态Replace Conditional with Polymorphism
- Loops 循环语句
- Replace Loop with Pipeline
- Lazy Element 多余的元素,比如因为后来代码造成的多余函数
- 使用Inline Function 或Inline Class内联处理
- 如果属于一个继承链,则使用Collapse Hierarchy
- Speculative Generality 不必要的通用性
- 如果某个抽象类没啥用,则使用Collapse Hierarchy
- 不必要的委托使用Inline Function 或Inline Class
- 函数的某些参数用不到,使用Change Function Declaration
- 如果是死代码,则使用Remove Dead Code
- Temporary Field 类中某种情况才用到的临时字段
- 使用Extract Class 收容临时字段,然后使用Move Function 和将相关搬进该对象
- 还可以使用Introduce Special Case 引入特例
- Message Chains 过长的消息链,与整个链条耦合
- 使用Hide Delegate 在不同对象之间委托
- 将所有中间对象变成中间人,即使用Extract Function提炼函数,然后使用Move Function将其放入消息链
- Middle Man 中间人使用存在问题
- 如果过度使用,使用Remove Middle Man 移除中间人
- 如果只有少数无效中间人,则使用Inline Function将其放在调用端
- 如果中间人还有其他用处,则使用Replace Superclass with Delegate或Replace Subclass with Delegate将其变成真正对象,从而扩展原对象的行为,同时解决中间人和其他用处的问题
- Insider Trading 隐晦的模块间通信,比如继承
- 如果两个模块经常通信,则使用Move Function和Move Field 减少通信
- 新建模块存放这些数据和方法,使用Hide Delegate将新模块作为委托
- 对于继承关系产生的问题使用Replace Superclass with Delegate或Replace Subclass with Delegate
- Large Class 过大的类,一个类负责多项任务
- 可以使用Extract Class将相同功能的类提炼到新类,具体可以使用Extract Superclass 提炼超类或使用Replace Type Code with Subclasses 提炼子类
- Alternative Classes with Different Interfaces 接口不统一的待替换类
- 使用Change Function Declaration改变签名,这往往不够,还需要使用Move Function搬移函数,如果因此造成重复代码可以使用Extract Superclass提取超类
- Data Class 纯数据类,只包含数据和访问的方法,作为数据容器,对于不可修改数据无需处理
- 为了避免被随意操纵,应使用Encapsulate Record 进行封装,使用Remove Setting Method 设为只读;然后找出修改这些数据的地点,将其行为使用Move Function搬到当前数据类中,如果无法搬移整个函数,则使用Extract Function 提炼后再搬
- Refused Bequest 不需要的继承
- 为子类创建一个兄弟类,使用Push Down Method 和Push Down Field将当前子类用不到的字段从基类下移到新创建的类中
- 如果子类实现了父类的行为,但是不支持其接口,应使用Replace Superclass with Delegate或Replace Subclass with Delegate解除继承关系
- Comments 用注释来解释功能。注释一般用来注释原因和打算。
- 如果需要注释解释一行代码做了什么,应使用Extract Function 提炼出来
- 如果还不行,则使用Change Function Declaration修改函数声明
- 如果需要注释某些系统的要求,可以使用Introduce Assertion 引入断言
3 重构方法
3.1 第一组重构
- Extract Function 将命令式代码超过一定长度或者将某类功能的语句提取到函数中,增强可读性,且“函数名的长度不重要”
- Inline Function 当代码本身可读或者函数提取不合理时可将它们内联到一个大函数中处理
- Extract Variable 将一个表达式赋值给一个变量增强可读性
- Inline Variable 当命名并不比表达式本身更有意义时,取消变量
- Change Function Declaration 函数作为程序的组成单位,要给函数名和参数一个可读性好的命名
- Encapsulate Variable 将一组数据封装成函数或者使用不可变数据,方便复用
- Rename Variable 变量命名的可读性
- Introduce Parameter Object 将多次使用的重复参数转为可维护性好的对象
- Combine Functions into Class 将多个函数组合成一个class,作为属性对外暴露
10. Combine Functions into Transform 将数据做转换然后统一对外暴露,和上一个的区别是如果需要对元数据做更新则上一个更好,因为本种要透传数据。 11. Split Phase 当一个函数做多件事时,可以按步骤等拆分成多个阶段
3.2 Encapsulation
封装的数据一般指的是可变数据(mutable state)。
- Encapsulate Record 记录指的是kv数据结构,应将其封装成类,以封装细节,容易改名
13. Encapsulate Collection 集合是指的是引用类型的对象属性,当我们对记录封装了以后,当我们获取了引用属性后,可以在封装的类无感知的情况下直接修改对应属性。为了解决这个问题需要进一步封装,比如获取的是源数据的副本,只能使用class提供的对应方法才能更源数据。
- Replace Primitive with Object 对于需要进一步处理的数据项可以使用对象而不是原始类型,可以用来减少重复代码
- Replace Temp with Query 用query代替变量可以复用计算逻辑,且不会被重复赋值
- Extract Class 一个类应有明确的单一责任,如果违反应提取新的类,或者当需要子类化时创建新的子类
- Inline Class 当多个类功能一致等时可以合并
- Hide Delegate 不同类接口之间可以增加代理,减少耦合
- Remove Middle Man 当没有必要使用代理时就去掉
- Substitute Algorithm 将实现某功能的算法直接替换为另一个
3.3 Moving Features
在不同的上下文中搬移元素
- Move Function 将函数放在合适的类上
- Move Field 搬移字段
- Move Statements into Function 将一些语句放到函数中,比如一些和当前函数一体但是在函数外的语句
- Move Statements to Callers 修改调用者
- Replace Inline Code with Function Call 将一些行内代码封装到一个函数
- Slide Statements 将存在关联的语句放在一起,比如同是赋值语句
- Split Loop 将多个任务的循环语句拆分到多个循环语句,执行多次循环不会对性能产生明显影响
- Replace Loop with Pipeline 以链式调用代替循环语句
- Remove Dead Code 移除死代码,eslint可以自动移除
3.4 Organizing Data
重新组织数据
30. Split Variable 表示不同含义的临时变量不要使用同一个变量名
31. Rename Field 字段重命名
32. Replace Derived Variable with Query 在get中计算,在set中不要修改另外的变量
- Change Reference to Value 值对象是不可变的
- Change Value to Reference 当共享一个数据集时,使用引用对象聚合在一起
3.5 Simplifying Conditional Logic
简化条件逻辑
- Decompose Conditional 将复杂的条件和具体执行步骤封装为函数
- Consolidate Conditional Expression 将条件不同但结果相同的条件语句合并
- Replace Nested Conditional with Guard Clauses 这里Guard Clauses指的是在符合某种条件时直接return从而不会继续执行后面的条件,单一出口原则其实没那么有用。
- Replace Conditional with Polymorphism 使用多态代替条件语句。需要创建一组类
- Introduce Special Case 对于检查某种特殊值的重复代码可以引入特例函数统一处理
- Introduce Assertion 引入断言,即如果某个条件为真时才继续否则直接return
3.6 Refactoring APIs
api将模块之间连接起来
- Separate Query from Modifier 任何有返回值的函数都不应该有副作用,如果遇到两者都负责的函数,应该将其分离
- Parameterize Function 可以使用函数做参数,合并类似的函数
- Remove Flag Argument 移除flag参数,将不同场景拆开
- Preserve Whole Object 当将一个对象的某些字段传递给另一个函数时,可直接将整个对象传递过去
- Replace Parameter with Query 参数之间应保证不重复,比如不需要把对象和对象上的某个字段同时传到某个函数
- Replace Query with Parameter 如果想获得对象上某个时间点的快照属性,应使用查询,而不是将对象传进去
- Remove Setting Method 移除setter设置为只读
- Replace Function with Command 将函数本身封装成对象,可以执行另外的操作(用作命令)
- Replace Command with Function 如果对象不需要命令,还是用函数吧
3.7 Dealing with Inheritance
处理继承关系
- Pull Up Method 如果某个方法是子类共有的应直接上移到基类
- Pull Up Field 字段也一样
- Pull Up Constructor Body 构造函数也一样
- Push Down Method 相反操作
- Push Down Field
- Replace Type Code with Subclasses 以子类代替类型
- Remove Subclass 移除没用的子类
- Extract Superclass 将类似的类修改为继承同一个类的子类
- Collapse Hierarchy 将超类子类合并
- Replace Subclass with Delegate 以委托代替子类
- Replace Superclass with Delegate 以委托代替超类
完结撒花
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!