任何一个傻瓜都能写出计算机能够理解的程序,唯有写出人类容易理解的程序,才是优秀的程序员。
定义:
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
目的:
使软件更容易被理解和修改。 与之形成对比的是性能优化,但是两者出发点不同,性能优化往往使代码较难理解。
性能优化:
不改变程序的外在行为(除了执行速度),只改变内部结构。
重构过程:
在不改变代码外在行为的前提下,对代码做出修改,以改进程序内部的结构。
重构基本技巧
小步前进,频繁测试
重构基本技巧说明
- 如果发现需要为程序添加一个特性,而代码结构使你无法很方便的达成目的,
那就先重构代码,再添加特性。- 重构之前,首先检查自己是否有一套可靠的测试机制,这些测试必须有自我检验能力。
任何不会被修改的变量我会把它当做参数传入新的方法,- 而会被修改的变量,如果只有一个,我会把它当做返回值。
- 重构步骤的本质,由于重构每次修改的幅度都很小,所以任何错误都很容易被发现。
而会被修改的变量,如果只有一个,我会把它当做返回值。=
重构时机
添加功能时重构
修补错误时重构
复审代码时重构
程序有两面价值:
今天能为你做什么
明天能为你做什么
————Kent Beck
程序为什么难以相与?
难以阅读的程序,难以修改
逻辑重复的程序,难以修改
添加新行为时需要修改已有的程序,难以修改
带复杂条件逻辑的程序,难以修改
因此,我们希望程序:
容易阅读
所有逻辑都只能在唯一地点指定
新的改动不会危及现有行为
尽可能简单表达条件逻辑
间接层价值
允许逻辑共享
分开解释意图和实现
隔离变化
封装条件逻辑
坏味道:
- 重复代码 Duplicated Code
a. 将相似部分和差异部分分开,构成单独一个函数。 - 过长函数 Long Method
a. 间接层带来的利益——解释能力、共享能力、选择能力——都是由小型函数支持的
b. 每当感觉需要注释来说明点什么的时候,我们就把需要说明的东西写进一个独立的函数中,并以其用途命名。
c. 条件表达式和循环也常常是提炼的信号,循环和其内的代码可以被提炼到一个独立的函数中。 - 过大的类 Large Class
a. 单个类做太多事情 - 过长的参数列表 Long Parameter List
a. 如果向已有对象发出一条请求就可以取代一个参数,那你应该计划重构手法。 - 发散式变化 Divergent Change
a. 某个类经常因为不同原因在不同方向上发生改变,应将类拆分 - 霰弹式修改 Shotgun Surgery
a. 与发散式变化类似也恰恰相反
b. 遇到某种变化需要在许多不同的类中做出小修改,需要将一系列相关行为放入一个类
c. 使“外界变化”和“需要修改的类”趋于一一对应。 - 依恋情结 Feature Envy
a. 将数据和对数据的操作行为包装在一起。
b. 将总是一起变化的东西放在一块儿 - 数据泥团 Data Clumps
a. 你常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。 - 基本类型偏执 Primitive Obsession
a. 模糊(甚至打破)了横亘于基本数据和体积较大的类之间的界限。 - switch惊悚现身 Switch Statements
a. 大多数时候可用多态替代
b. 在单一函数中使用则显杀鸡用牛刀 - 平衡继承体系 Parallel Inheritance Hierarchies
a. 每当为一个类增加一个子类,也要为另一个类增加一个子类。
b. 策略:让一个继承体系的示例引用另一个继承体系的示例。 - 冗赘类 Lazy Class
a. 如果一个类的所得不值其身价,它就应该消失。
b. 对于几乎没有用的组件,可使用类的内联化处理。 - 夸夸其谈未来性 Speculative Generality
a. 使用各种各样的钩子和特殊情况来处理一些非必要的事情。
b. 过度设计未来不需要使用的功能。 - 令人迷惑的字段 Temporary Field
a. 某个实例变量仅为特定情况而设,通常人们会以为对于全局适用。
b. 把所有和这个类相关的代码都放入一个类。 - 过度耦合的消息链 Message Chains
a. 对象请求一个对象,这个对象又请求另一个。
b. z拆分或加中间层 - 中间人 Middle Man
a. 某个类接口有一半的函数都是委托给其他类,不干实事
b. 可使用内联函数把他们放进调用端,其它行为放入实责对象的子类 - 狎昵关系 Inappropriate Intimacy
a. 两个类过于耦合
b. 把两者共同点提炼到新类,或使用代理代替继承关系 - 异曲同工的类 Alternative Classes with Different Interfaces
a. 合并代码或创建父类 - 不完美的库类 Incomplete Library Class
a. 对于库类的丰富与扩展方法 - 纯稚的数据类 Data Class
a. 拥有一些字段和访问这些字段的函数
b. 尝试把调用取值、设值的方法放进来,将数据及取值设值的函数隐藏 - 被拒绝的遗赠 Refused Bequest
a. 传统方法:新建兄弟类,将父类的非通用的方法push down
b. 建议使用委托代替 - 过多的注释 Comments
a. 长长的代码之所以存在乃是因为代码很糟糕。
重新组织函数
- 提炼函数 Extract Method
a. 创建一个函数,根据这个函数的意图来命名,以“做什么”命名,不要以“怎么做”命名。
b. z局部变量需改动时,使用查询(带返回值) - 内联函数 Inline Method
a. 间接性可能带来帮助,但非必要的间接性总让人不舒服
b. 间接层有其价值,但不是所有间接层都有价值 - 内联临时变量 Inline Temp
a. 将所有对该变量的引用动作,替换为对它赋值的那个表达式本身。 - 以查询取代临时变量 Replace Temp with Query
a. 同一个类中所有函数都将获得这份信息 - 引入解释性变量 Introduce Explaining Variable
a. 将复杂的表达式(或其中一部分)的结果放入一个临时变量,以此变量名称来解释表达式的用途。
b. 可以删除注释,因为代码已经可以完美表达自己的意义了。 - 分解临时变量 Split Temporary Variable
a. 针对每次赋值,创造一个独立、对应的临时变量。 - 移除对参数的赋值 Remove Assignments to Parameters
a. 避免对一个参数进行赋值,使用临时变量替换,以免产生引用传递修改原值的问题。 - 以函数对象取代函数 Replace Method with Method Object
a. 将函数放入一个单独对象中,其局部变量成为字段
b. 可在同一个对象内进行函数拆解,而不必担心参数传递问题 - 替换算法 Substitude Algorithm
a. 将原算法替换为新算法。
b. 拆解原算法再进行替换并测试。
重构语录:
- 三次法则:事不过三,三则重构
- 计算机科学是这样一门科学:它相信所有问题都可以通过增加一个间接层来解决。 ——Dennis DeBruler
- 重构往往把大型对象拆成多个小型对象,把大型函数拆成对个小型函数。 如果项目已非常接近最后期限,不应分心于重构。
- 不过多个项目经验显示,重构的确能提高生产力, 如果最后没有足够的时间,通常表示你其实早该进行重构
- Ward Cunningham把未完成的重构工作形容为“债务”,很多公司通过借债来使自己更有效的运转,
- 但是借债就得付利息,过于复杂的代码所造成的维护和扩展的额外成本就是利息。
- 重构肩负一项特殊使命:它和设计彼此互补。
- 你必须培养出自己的判断力,判断一个类内有多少实例变量算是太大,一个函数内有多少行代码算是太长。