遗留系统重建实战
和冷冰冰的概念比起来,这本书更像是经验丰富的老司机们的重构项目经验之谈.适合1-3年的工程师审视自己的项目,找出问题所在,并结合书上的方案去解决问题
第一部分 开始
第1章 了解遗留项目中的挑战
遗留项目
- 老旧
- 庞大
- 继承而来
- 文档不完善
遗留代码
- 没有测试和无法测试的代码
- 不灵活的代码
- 被技术债拖累的代码: 仅仅为了满足当下功能的代码,会随着负载变高和需求变更而不堪重负
遗留基础设施
- 开发环境
- 过时的依赖
- 异构环境: 开发,测试,生产环境的差异
遗留文化
- 害怕变化
- 知识仓库
第2章 找到起点
恐惧和沮丧
- 修改代码会破坏一些根本不相关的功能(对未知的恐惧)
- 不知道从哪里开始动手,每一个类看起来都一样糟糕(漫长的无用工作带来的沮丧)
如何克服
- 使用探索性重构: 重命名类与方法,引入新的接口,添加注释等不影响系统功能的方式加深对系统的理解
- 借助Git,IDE等工具替代繁琐的复制粘贴劳作进行安全的重构
- 进行性能测试,性能监控找到痛点(优化性能大多安全并且有成就感)
剩下的监控,持续集成工具放最后一章来说
第二部分 通过重构改善代码库
第3章 准备重构
重构最大的问题从来就不是重构代码本身
达成团队共识
首先要和团队成员达成共识,虽然很少有人是绝对的传统主义者或者反传统主义者.但是身上或多或少有这两类人的影子(处于两者之间吧)
面对团队中的传统主义者
传统主义者是一个强烈反对任何形式变化的开发者.他们认为重构让我们的开发人员花费太多精力在任务之外的事情,并且重构会带来不必要的风险
- 结对编程
- 解释技术债务
面对团队中的反传统主义者
反传统主义者厌恶一切遗留代码.但是过度的重构也会带来新的bug,并且过度重写团队中其他人的代码会带来互相埋怨和士气的降低
- 代码评审,并且注意粒度
- 自动化测试
- 结对编程
- 划定(本次重构)代码区域
获得组织的批准
小范围的代码调整并不需要耗费工作计划中可见的时间,但是大规模的重构是需要的.
- 使它变得正式: 向非技术的合作者解释重构的价值并且说服他们同意技术资源投入在这件事上
- 备用计划-神秘的20%计划: 进行一个不太费时间的重构demo,向团队展示成果后推进大规模的重构
选择重构目标
根据难度/风险和痛点/价值的象限来判断,当然是先捡软柿子捏
决策时间: 重构还是重写
重写一般情况下都是不现实的
- 新项目的bug可能比之前的还多
- 重写的功能可能和之前的功能有所偏差
- 项目耗时会比预估的更长
- 如果重写失败了,不会带来任何好的收益
当然,如果重构无法实现目的或者整个公司的技术栈转向,重写可能是更好的选
Tips 绞杀者模式(Strangler pattren)
在绞杀者模式的重写中,新系统围绕现有系统构建,截取其输入和输出,并逐渐承担越来越多的功能,直到最终原有系统静静地消亡
第4章 重构
有纪律的重构
漫无目的的重构带来过度修改,整个重构工程开始了却无法收尾,最后只好放弃所有的改动
- 把重构和日常工作分开: 发现问题的话切新分支来重构,而不是放进现有的工作中去引入新风险
- 依赖IDE
- 依靠版本控制系统
- Mikado方法: 画出类之间的依赖图,在出现问题的时候先修复再进行新的工作.
常见的遗留代码的特征和重构
- 陈旧代码: 被注释的/无法达到的逻辑语句/过期的逻辑
- 有毒的测试: 测不出任何东西/脆弱的测试/随机失败的测试
- 过多的null: 尝试使用空对象模式???
- 不必要的可变状态: 能用常量的不要用变量
- 错综复杂的业务逻辑: 进行分层
- 视图中的复杂性: 引入中间层避免视图层中的业务逻辑
测试遗留代码
一般情况下要重构的代码都是要有单元测试的,这样可以方便的知道重构会不会带来回归问题.然而大多数需要重构的代码都没单元测试
- 测试不可测试的代码: 先重写再加单元测试吧
- 没有单元测试的回归测试: 单元测试不是银弹,要再多个抽象级别上进行测试防止重构带来的回归问题
- 让用户为你工作: 发灰度
第5章 重搭架构
把大的单体服务分层,或拆分成多个小服务
将单体应用分解为模块
将一个有多种功能职责的模块分解为多个小模块,每个模块的职责单一,有显示的接口供其他模块进行调用
将web应用程序分发到服务
各种Web应用程序架构的比较
架构 | 技术优势 | 技术挑战 | 组织优势 | 组织挑战 |
---|---|---|---|---|
单体 | 低延时 开发简单 没有重复的模型/验证 |
伸缩 由于代码库过大引发的复杂度 意想不到的交互的危险 |
特性内沟通的开销低 | 失败的恐惧 特性间沟通的开销大 |
前后端分离 | 能够单独的扩展前端和后端 将业务逻辑与表示分离 能够复用后端并构建多个前端 |
由于网络调用引起的复杂度 | 专业性 能够更快的迭代前端 通往面向服务架构的阶梯 |
沟通开销 知识壁垒 前后端开发相互阻塞 |
面向服务架构(SOA) | 细粒度的伸缩性 隔离 封装 |
运维开销 延时 服务发现 追踪/调试/日志记录 热点服务 API文档,客户端 集成测试 数据碎片 |
自治 | 自治程度的困境 重复工作的风险 |
微服务 | 与面向服务架构一样,只会更多 | 与面向服务架构一样,只会更多 隐形耦合的风险 |
由于有界上下文会产生更多的自治 | 意味着需要DevOps 需要一个平台团队 需要思维方式的重要切换 |
第6章 大规模重写
决定项目范围
- 确定项目的目标
- 记录项目的范围
如何处理数据库
共享现有数据库
- 实现简单
- 无需更新其他应用程序和脚本
- 无法选择数据存储技术
- 无法重搭架构
- 新旧系统同时使用有损坏数据的风险
一个好的迁移方法是实现一个转换层,新系统的数据通过转换层和旧的数据库进行通信
创建一个新的数据库
特性和共享现有数据库是完全相反的
麻烦点在于新旧系统同时使用期间的数据同步问题
第三部分 重构之外——改善项目工作流程与基础设施
第7章 开发环境的自动化
面对一个复杂系统开发环境的构建, Vagrant+Ansible大法好
第8章 将自动化扩展到测试环境、预生产环境以及生产环境
用Ansible保持所有环境的一致性
第9章 对遗留软件的开发、构建以及部署过程进行现代化
Jenkins持续集成和自动化
上线的自动化
第10章 停止编写遗留代码
源代码并不是项目的全部
一个成功的软件项目有很多因素,高质量的源代码只是其中之一
源代码的改进只是重构的一小部分:团队沟通交流,维护开发工具链,自动化配置,持续集成和审查对于维护软件的质量更加重要
信息并不是自由的
虽然看起来大家都很乐于分享正在工作的软件和知识,但是事实上有效的信息很少.每一个人都是知识的孤岛,一个开发人员的离职会使团队丢失大量有价值的信息
编写技术文档
- 信息丰富: What How Why
- 易编写: 使用合适的文档格式和工具
- 易发现: 尽可能靠近源代码的地方,最好和代码在一个Git仓库内
- 易阅读: 简洁的文档更容易阅读
- 可信赖: 过期/无效的文档还不如没有,请及时的维护文档
促进团队沟通
- 代码评审
- 结对编程
- 技术访谈
- 向其他团队展示项目
- 黑客日
工作是做不完的(维护代码质量是日常的工作)
- 定期进行代码评审
- 修复一扇窗户(破窗效应)
- 自动化一切
- 小型为佳