重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
从定义上来说,重构意味着,我们不能改变现有软件的行为,你的 bug 还应该是 bug。只是呢,我们可以顺带着修复这个 bug,但是它绝不能冠上重构的名义。如此一来,怕是会被人误解。
既然,不添加新的功能,也不减少现有的功能,那对于业务系统来说有什么价值呢?
重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。—— 《重构:改善既有代码的设计》
所以,总的来说,重构是为了 ¥¥¥
。如果不能创造更多的价值,那么你在扯什么犊子的重构。但是,这种价值往往都不是直接体现。
重构的动机
我知道你已经熟悉了这些说辞,而我也是复制过来的:
- 改善既有代码的设计。副标题是最好的说明。
- 帮助找到软件中的 bug。review 旧有代码的过程中,会发现一些不符合业务的代码。
- 提升开发效率。让代码易于扩展
- 提高编程的趣味性——《重构与模式》
我也懒得打了。
重构不一定能带来什么?
重构的投入与回报并非呈线性关系。—— 《领域驱动设计:软件核心复杂性应对之道》
人们总以为重构能带来:
- 更少的代码 。
- 更稳定的系统 。
- ……
它们都是可能 带来的效果,而不是一定会。如:
- 当我们拆分一个上帝类的时候,我们可能拆出了三个类、四个类,因为那些 import、package、type 的 declaration,反而带来了更多的代码。
- 而当我们发现重复代码,并且它们是可被抽象的,那么我们就可以消去一半的代码。
- 如果我们在重构的过程中,发现 bug,那么必然系统会更加稳定。
- 而如果因为我们重构,导致别人合并代码时发生冲突,反而可能会带来 bug。
- ……
我们是基于一系列的假设,才有了对应的结果描述。
而尽管我们达不成上述的好处,但我们仍然可以追求:
- 编程的技艺 。稳定的业务代码,便容易选择合理的识别设计模式。
- 重构的手法 。是的,如果你持续练习的话。
- 持续重构的意识 。我们真正意识到烂代码带来的问题。
- 模式和原则 。遇到相似的情况时,可以用相似的重构手法。
- 抽象能力 。后期总结,往往比前期更易于实践。
总的来说,它带来了更好的效果。
重构是一种文化
日常的开发中,如果你说出你要重构的时候,有人对此挑战的话,那么说明需要培养相应的文化 —— 程序员的匠艺。好的代码不是一把写出来的,而是持续的演进和重构出来的
如果有开发人员,经常提出需要重构的时候,你要看看是否真的合理。
持续在在日常开发中进行小重构,应该是开发人员的一种自我修养 。
大规模重构的时机
对于大规模重构来说 ,一个最为常见的例子是:在接手别人或者别的系统的代码。觉得原有的代码不符合现有团队的风格,不满足再有的开发习惯。但是,根据我的观察,开发人员往往只是在改造系统,而非真正意义上的重构。
把握重构的时机是很难的一件事情,特别是如果你要进行大的系统改动,如果没有出现真正的痛点。那么,即使我们觉得诸多设计不合理的地方,我们往往可能并不被允许重构,也没有时间进行重构。可一旦连业务人员,也觉得系统需要重构的时候,那么系统也许需要重写了。
即便于对未来有价值,我们也仍需要等待更合适的时机,如:代码库拆分、整洁架构中的领域层拆分等等。我们都知道它们带来的好处,但是我们尚没有那么迫切。反而,因为拆分,我们可能会带来额外的重构成本和维护成本。如我之前在重构 Coca 的时候,我在拆分 domain 层的时候,显得没有价值。我花了一两天的时间,把依赖于操作系统的部分,从代码中剥离了出去。我只是在试验 domain 层的好处,但是我并没有办法直接证明它的好处。
直到,我开始为 Coca 添加 WASM 支持的时候,编译成功了。并且,拆分之后,构建出来的 wasm 包的大小减少到原来的一半,因为有大量的依赖是我所不需要的。如果按正常的重构逻辑,应该是在我们在未来计划添加 WASM 的时候,架构师才允许我们拆分出来。
我们不应该做大规模预先的设计,也不应该过度的设计;我们也没有理由做大规模的预先重构设计,准备好方案,在时间恰当的时候,实施方案。
说清重构的价值
重构,无法在短期内创造价值,哪怕是创造了大量的价值,它也难以直接 体现出来。它可以提升代码的可读性,可维护性……,但是总的来说,它是在以特定的方式降低系统的维护成本。
再说说可维护性,比如,你使用了更好的设计模式代替了复制、粘贴,它可能还不如原来的代码生成器好用。开发的过程快了,维护就是一坨屎。所以,我说没有维护过旧系统的人,不懂得重构的好处。白天不懂得夜的美。
既然如此,让我们就再考虑一下重构的价值。我想了很久这个问题,直到我从《遗留系统重构指南》看到一些示例:
阶段 | 描述 | 业务价值 |
---|---|---|
0 | 初步重构。定义组件接口,将组件拆分为独立的 jar | 清晰的接口能增加代码的可维护性 |
1 | 重写身份认证。更改密码的存储方式 | 更好地遵守数据安全法规 |
2 | 重写搜索组件。切换到不同的搜索引擎实现 | 搜索结果质量更好。用户更容易找到产品 |
3 | 重构推荐组件。 | 可以快速地切换不同的算法 |
不过,仍旧存在大量的难以描述的价值——至少我是不知道从何说起。这里的代码、架构有坏味道,得去重构,便是我,一个工程师的思维。只是呢,真正有意义的业务价值,都是 TBC……。
尽管我们说,重构尽量不要影响业务开发,但是如果正在开发的功能部分,有较大的问题。那么,我们不得不调整策略,优先支持这部分的功能,而不是等完成后,再花费时间来对它们进行重构。
下一节:设计模式强调为开发大规模系统提供可复用的设计指南。 —— 《反模式:危机中软件、架构和项目的重构》