前言
面对重构的两难
- 面对多年的大型遗留系统,越来越多的需求变更让维护成本越来越高,面对越来越多的竞争,有被市场淘汰的风险
- 凑合用一下还可以坚持几年,如果不小心改出问题,企业可能立即歇菜
系统重构的前提是“不改变软件的外部行为”,这保证了不会引入bug. 例如,把显示的日期表示从 ‘2021-12-12’ 变成 ‘2021-12-12 00:00:00’,在开发看来不是bug,但在客户看来就是bug
改代码的原因有4种:
- 满足客户的新需求
- 改bug
- 优化性能
- 优化内部结构
其中1和2源于客户的功能需求,3源于客户的非功能需求。只有4的价值是隐形的,体现在日后长期维护上。
似乎重构与需求无关,但其实不是这样的
- 传统是如何添加新功能的:尽量不改变原代码,这样就不会影响以往的功能。后果是源源不断往里面添加程序,时间一长,代码越来越多、越来越乱。
- “糟糕设计零容忍”策略:接到需求后,分为两步。先重构系统,使其适应要添加的功能;然后添加需求。
等价变换重构。书上给个例子,把一段代码分为多个步骤重构
- 增加注释(原程序没有注释)
- 调整顺序
- 重命名变量(原程序变量名很多事单个字母)
- 分段(用空行把程序分成不同单元)
- 把分段后的代码提取成函数
重构时,最好快速迭代,快速发布一个版本。否则,如果重构深入时发现bug,不得不回滚到原始代码,整个重构就失败了。
第一步:分解大函数
具体步骤还是上面的增加注释、调整顺序、重命名变量、分段、提取
例如:
- 调整顺序(很多大函数在开始定义了一大堆变量,你需要边读边调整,把变量的定义移动到用它们的代码附近)
- 分段和提取函数:
- 你不必把一个大函数读完才提取函数。
- 有些天然的分块,例如条件语句、循环语句,有时候一个分块很多行,就可以抽取成函数
- 有时候,抽取出来的函数,也很大,那么可以继续分解这个函数。
- 经过一系列分解,一个大函数变成很多小函数,可读性强了,但是产生了几十个小函数,这些小函数凌乱的堆砌、没有层级,整个代码依然很大。我们在后面的步骤继续重构
常见问题
- 原代码中的变量可以毫无顾忌的交互数据,但是抽取之后就只能用参数来定义。
- 一个糟糕的设计:把所有有交互的变量都写到参数里面。一方面,可读性很差也很傻;另一方面,如果代码变更需要增加参数,就是灾难。
- 一般可以用值对象(Value Object)来传递参数,重构初期可以把变脸都塞进一个值对象。不过后面需要把值对象变成有实际意义的几个。最终的参数就是几个值对象。函数传入的值对象数量不超过6个,最好1-4个
第二步:拆分大对象
操作其实很简单,就是把原对象中的某些方法移动到其他对象中,这个操作叫做 Extract Class,问题的关键是移动到哪个对象。
- 传统的重构思路是“按照职责做拆分”,每个类单一职责。但这种方法并不奏效。因为开发人员原本不熟悉整个业务的所有细节,而是在开发之后才熟悉。因此一下子想好整个设计是不可能的。
- 更推荐“小步快跑”,一次想不清楚,就分多次,每次实现一部分。
- 合久必分:把大对象中不相关的拆分成多个方法类
- 分久必合:随着开发人员对整个业务更加了解,系统性的审视全局,把分散的方法类合并到业务类中
第三步:提高代码复用
- 找到重复的代码并不难:
- 当你开发新功能时,复制第二次就要注意了。
- 同一个流程的某个环节,例如付款时的付款方式不同,但流程中的大部分应该是可以复用的。
- 不同业务的相似功能。例如,填写付款单、发票。它们虽然是不同的业务,但都需要效验输入是否合法、检查余额等。
- 本身相似的功能。例如收款单和付款单、同行评审和非同行评审。
- 提高代码复用,这才是考验优秀程序员的地方。
- 如果重复代码在同一个对象中:抽取成方法
- 如果重复代码在不同对象中:抽取成工具类。例如,获取时间的代码散布在十几个地方,但功能有不完全相同。就应当做一个工具类。
- 重复代码在不同对象中:另一种方法是抽取成实体类。工具类仅仅是一堆方法的集合,而实体类有一定的业务逻辑。
- 如果代码所在的类有并列关系:抽取父类。例如,正常开票与非正常开票这两个类,都有 valid, save 方法,因此抽取出一个开票类
- 其他。提高复用的方法还有很多,各有使用场景。体现了对优秀程序员的考验。
第四步:发现程序可扩展点
是预先做可扩展性,还是需求变更时增加扩展性,这是个两难的难题,有一些一般原则
- 预先的可扩展性设计不要太多
- 更常见的可扩展性来自需求变更。
第五步:降低程序的依赖
- 接口、实现:工厂模式
- 与外部系统解耦:外部接口和适配器模式.我们想调用一个服务的3.0版本,而不是原先的2.0版本,发现类名、方法名,甚至包名都变了,代码里到处都是对2.0版本的各种引用,像老树一样盘根错节。根本原因是耦合太强。这就适合用适配器模式改造。
- 继承泛滥:桥接模式
- 方法的解耦:策略模式
- 过程的解耦:命令模式
(这里只提一下,详细见于另一篇文章 【Python】设计模式)
第六步:分层
参考文献
《大话重构》,范钢,人民邮电出版社。