前言
之前在组内负责核心项目的重构(详见记一次项目的重构),刚好在Phodal在文章里推荐了这本书,虽然当时重构工作已经进行了大半,但个人工作经验毕竟不足,希望能从前辈那里系统地学习一下软件重构之道,当时就在京东下了单(购书链接,想完整阅读的推荐购买)。阅读时发现很多场景和我在重构时遇到的问题类似,解决方案也是比我更加全面和成熟,对于准备着手重构或者正在进行重构的程序员来说,的确是一本好书。
此书主要讲重建遗留系统所需要的工作,不仅有源代码重构的具体方法论,还包含了心理建设,团队交流,基础设施优化等多个方面,通过作者自身经历的重建项目详细地介绍了如何完成一次优秀的重建。虽然书中代码片段主要为Java,但大部分内容还是和软件工程相关,各语言各方向有兴趣的程序员都可以阅读。Phodal推荐的阅读人群:
- 有一定工作经验(2~3 年),并且对代码有追求的程序员。
- 面向复杂的遗留/旧系统,无法下手的项目
- 熟悉面向对象的程序员
下面是我阅读过程中的读书笔记(数字代表章节)
遗留项目的挑战(1)
遗留项目
老旧、庞大、经历好几代开发、文档不完善。
遗留代码
- 没有测试或者无法测试
- 不灵活,多处耦合
- 技术债务拖累(多次临时取巧的修复导致代码质量下降)
遗留基础设施
- 开发环境搭建困难
- 项目依赖过时
- 环境异构(测试/预发/线上环境不同步)
遗留文化
- 害怕变化(为了稳定而保持现状,忽略了改变带来的潜在好处)
- 缺乏沟通(知识不共享)
图表:遗留项目更改、收益及风险
重构的准备(2-3)
心理建设
克服恐惧
部分代码过于脆弱或者非常核心,害怕自己的更改会影响到很多未知的部分,解决方案:
- 利用探索性重构,每次做一些尝试性的改动,增加对代码的了解
- 利用版本管理系统和IDE等工具保证自己任何破坏性修改都可以恢复
- 增加特征测试,保证被重构部分行为的稳定
克服沮丧
代码过于庞大复杂,导致失去动力修改,或者孤注一掷进行危险的大型重构,解决方案:
- 定期度量代码质量的指标,图形化显示其趋势,从而激励团队
- 通过数据决定重构的下一个目标,从而系统地进行重构
收集软件数据
- bug和编码标准违例
- 性能指标
- 错误计数
- 为工作任务计时(搭建开发环境/发布和部署/修复bug)
- 找到经常改动的文件
达成团队共识
对于遗留代码,团队内开发人员的态度可以分为传统主义者和反传统主义者,我们希望在两种态度之间达到平衡。
传统主义者
为了稳定反对任何形式的变化,认为重构会带来不必要的风险。解决方案:
- 结对编程,让他明白重构带来了哪些具体的好处
- 解释技术债务,让他明白稳定之下隐藏的问题和风险
反传统主义者
厌恶遗留代码,热衷于提高代码质量,经常一次重写大量代码,导致重构风险增大,解决方案:
- 代码评审,告诉他将改动控制在合适的大小
- 自动化测试,所有改动必须要有测试覆盖,减缓他改动的步伐
- 结对编程,引导他们去重构最核心的代码
- 划定代码区域,区分出核心与非核心代码,理解重构代码带来的价值和风险
增加沟通
- 代码评审
- 结对编程
- 团队活动
获得组织批准
- 如果计划比较大,说服领导,展示重构的价值,让整个重构计划变得正式
- 如果计划比较小,可以先自己进行一些小改动,在得到实际收益之后,再向团队内推广。
选择重构目标
根据难度,风险,价值三个坐标轴放置重构目标,其中包含:
- 容易实现的目标(风险=低,难度=低)——可以作为重构的起点
- 痛点(价值=高)——重构的核心,需要足够多的时间和精力
决策:替换、重构还是重写
开始前,首先应该研究是否有第三方的解决方案,将模块替换为成熟的第三方案可以省掉大量时间和精力。如果没有的话,再决定是需要重构还是重写。重写的利弊如下:
- + 自由
- + 可测试性增强
- - 风险更大
- - 可能造成回归问题
- - 开销更大
- - 性价比可能较低(部分糟糕的代码源于复杂的需求而不是代码问题)
还可以选择重构和重写的中间路径——增量重写,在低风险的情况下获得相对明显的收益。
具体方法论(4-6)
常见遗留代码特征和重构
陈旧代码
- 注释代码
- 不会被执行的代码
- 过期代码
解:移除
有毒的测试
- 没有测试任何实际效果
- 测试十分脆弱,拖累重构
- 测试不稳定,会因为随机因素失败
解:修复或移除
错综复杂的业务逻辑
- 大量耦合,多处依赖
- 简单逻辑复杂化
解:整理逻辑并合理拆分
视图层的复杂性
- 视图层中夹杂大量业务逻辑
解:在视图层之前添加一层转换层,用于放业务逻辑
大规模重写
当第三方方案和重构都不能得到好的效果时,就需要进行重写,重写形式分为:
- 黑盒式重写——功能保持一致
- 温习式重写——探索功能更新的机会
- 补偿式重写——一部分新功能和重写一起进行
测试遗留代码
- 重构时回归测试比单元测试更重要
- 不要过度追求覆盖率
- 利用用户帮助测试,方法:
- 渐进式发布新版本
- 收集真实数据帮助测试
- 灰度发布新版本
重搭架构
主要看下web应用架构的比较,直接看图吧:
几个小点:
- 虽然这几年架构都是逐渐向表中下面这个方向走的,但是还是要具体业务具体对待,每种架构都有利弊,不能哪个架构比较火就用哪个。
- 服务越独立,意味着运维工作越多,使用时不能仅考虑开发成本。
- 微服务是SOA架构的一个特例,特别强调解耦。意味着服务间只能用API通信,并且要不惜任何代价避免API的破坏性变更。
改善项目工作流程和基础设施(7-9)
- 开发环境自动化搭建
- 测试/预发/线上环境自动化搭建,保证环境一致性
- 构建和部署流程优化,实现持续集成和自动化
避免重蹈覆辙(10)
遗留代码已经焕然一新,现在要做的就是防止你的代码以后变成别人的噩梦
- 不要过于关注代码,完善易懂的文档和良好的沟通更重要
- 工作是做不完的,鼓励大家共同承担代码质量责任,定期进行代码评审。尝试定期修复代码中的质量问题,确保让其他人看到这份努力从而督促他们
- 自动化一切可以自动化的流程(测试/构建/部署)
- 保持代码库的轻盈灵活,使其可以低风险地被遗弃和重写
小结
重建项目虽然艰苦,但收获远比日常开发要高,祝准备进行或者正在进行重建的程序员都能熬过难关并获得成长~