重构的定义
重构:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构与添加新功能
- 添加新功能:不修改既有代码,只添加新功能
- 重构:不添加功能,只改进程序结构,不再添加任何测试
为何重构
- 改进软件设计
没有重构的代码会逐渐腐败变质
- 使软件更容易理解
重构方便后续维护者理解自己写的代码,方便自己理解不熟悉的代码。
帮助找到 bug
提高编程速度
良好的设计是维持软件开发速度的根本。
何时重构
- 添加新功能时重构
- 修补错误时重构
- 复审代码时重构
重构的难题
- 数据库的重构
- 修改接口:不过早发布接口,不发布冗余接口
- 难以通过重构完成的设计改动
- 现有代码无法正常运行时,重写而非重构
重构与性能
重构短期内可能会影响性能,但是重构后的代码更容易调整和优化。
性能优化阶段,对性能进行具体监控后,针对性关注并优化。
代码的坏味道
这里列举 22 种坏味道的代码
1. Duplicated Code(重复代码)
同一个类的两个函数含有相同的表达式;
互为兄弟的子类内含有相同表达式;
毫不相干的类出现重复代码。
2. Long Method(过长函数)
小函数使间接层的解释能力、共享能力、选择能力得到支持;
小函数容易理解的关键在于一个好名字;
需要以注释说明的时候,将说明写进独立函数,哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途就该毫不犹豫地做。因此寻找注释能定位到需要提炼的代码。
3. Large Class(过大的类)
太多实例变量,太多代码,都需要重构。
4. Long Parameter List(过长参数列)
太长的参数列难以理解;
太多参数会造成前后不一致、不易使用,一旦需要更多数据,只能修改签名;
用对象代替参数列。
5. Divergent Change(发散式变化)
某一个特定的类,会由于不同的外部原因,导致不同的变化时,应当拆分此类;
针对某一外界变化的所有相应修改都应只发生在单一类中,且新类的所有内容都应反应此变化。
6. Shotgun Surgery(霰(xiàn)弹式修改)
遇到变化时需要修改多处不同的类,则将修改点提炼成独立的类。
7. Feature Envy(依恋情节)
类中的函数关注其他类胜过自身,则将函数移位到其他类。
8. Data Clumps(数据泥团)
多处类出现相同的字段、相同的函数参数,且互相关联互相影响,则提炼独立对象。
9. Primitive Obsession(基本类型偏执)
将基本类型字段替换为对象字段。
10. Switch Statements(switch惊悚现身)
面向对象程序的一个最明显特征:少用 switch 或 case 语句。switch 的问题在于重复,且不易修改;
尝试用多态或不同函数签名代替 switch。
11. Parallel Inheritance Hierarchies(平行继承体系)
每当为一个类添加子类时,都必须为另一个类相应增加子类;
尝试用实例引用的方式去除重复性。
12. Lazy Class(冗赘类)
删除无足够工作或几乎没用的冗赘类。
13. Speculative Generality(夸夸其谈未来性)
用不到的类或函数均需移除。
14. Temporary Field(令人迷惑的暂时字段)
类中某个实例变量仅为特定情况、特定函数而设置,则将变量和相关代码提炼为单独的类;
通常出现在类中的复杂算法,需要多个变量,同时不易于用参数传递,因而采用了类实例变量的方式。
15. Message Chains(过渡耦合的消息链)
消息链表示由一串对象前后调用的逻辑,代码与导航结构紧密耦合,一旦关系发生变化,则代码就需要修改。
16. Middle Man(中间人)
中间人仅仅承担接口委托的职责,则应该直接与负责对象打交道,去掉中间人。
17. Inappropriate Intimacy(狎昵关系)
类之间耦合严重,关系过于亲密,需要解耦合。
18. Alternative Classed with Different Interfaces(异曲同工的类)
两个逻辑相同而签名不同的函数,需要合并。
19. Incomplete Library Class(不完美的库类)
针对库类的不合理处也需要弥补。
20. Data Class(纯稚的数据类)
数据类除了拥有字段和访问函数外一无长物,需要增添职责,封装和隐藏字段。
21. Refused Bequest(被拒绝的遗赠)
子类继承了超类,而没有完全继承超类的函数和数据;
尝试新建兄弟类,转移函数和数据。
22. Comments(过多的注释)
注释常常意味着代码需要重构,重构使注释变得多余。