断断续续读完了《重构 改善即有代码的设计2》, 作者是 Martin Fowler 大神,这本书也是很多 IT 大牛推荐的码农必读书之一。把读到的感悟,加上自己的理解,总结成最佳实践分享给大家

本书前五章讲了为什么什么是如何做重构,偏理论论证部份,但是读起来并不枯燥。后半部分都是具体的重构细节,每章节都有案例,前五章推荐反复阅读

同时这本书定位是改善即有代码,所以并不涉及如何重写代码,比如 python 老代码使用 go 重写,也不包括如何重新梳理业务流程,重写微服务

前五章偏理论论证部分,让我们看一下 What, Where, Who, How, Why

为什么做重构

这是个微服务爆炸的时代,大型系统少则几百多则上千个服务,历史遗留代码几经易手,根本没有文档,全靠口口相传,经过几代目交接后,代码己经完全没法读了

增加功能,可能要改动多个模块,写的时候不明所以,上线也战战兢兢。好的设计,增加功能需要的时间比较短,坏的设计可能都无法更改,代码完全腐烂后,只能推倒重来

简单的总结下重构的目标:维持代码的生命力,使之易于理解,易于 Debug, 并且很容易的添加新功能

那么什么是好的代码呢?个人认为从三方面来评价

  • 语言 每种语言都有自己的 code style, 比如 php 转 go 的喜欢用 interface, map 就有很大的问题。有值时返回 List, 无值时返回 0, 这是怎样的痛
  • 设计模式 面试为什么经常问,因为真的有用,SOLID, DIP 这些都不懂写出来的只能是面条代码,过程代码。低耦合,高内聚,怎么强调都不过分
  • 领域 最近 DDD 大火,Domain-Driven-Design, 术语一大堆,大白话就是构建合理的业务模型,建模是错的,不能反映客观规律事实,后续就是噩梦

什么是重构

那么终极问题来了,什么是重构???

在不改变软件可观察行为的前提下,通过一系列方法,设整代码、结构,提高代码的可理解性,降低阅读以及个改成本

修改业务流程是重构,修改整个模块是重构,修改一个小的函数也是。有点类似勿以善小而不为,勿以恶小而为之的感觉,腐烂都是从一个个小的无所谓开始的

重构的挑战

世上没有容易的事情,只有先做困难紧急的事情,其它问题才能迎刃而解。那么重构最大的挑战来自哪里呢?

1.延缓业务开发:销售和业务方以保障业务进度为名,压制重构。而且很多开发者也有这样的想法,自我催眠认为不该重构。这时就要求技术 leader 要有判断力,向团队成员表量,重视改善代码重构的价值所在。当然这种判断力要靠经验与时间积累

2.部门墙:改动仅涉及自己服务,或是同团队的还好。跨团队,或者跨部门很容易触发部门墙,我们是 critical 服务,流程无法修改 一句话就能把你的想法打回。此时就需要有更高级的 leader 来跨团队部门沟通才能达成,有时会让团队感到气馁,直接放弃重构

3.绩效与部门方向偏离:与公司战略方向一致,很容易产生绩效。这就是新服务只需要 20% 的时间,却拿走了 80% 的 KPI, 而维护老服务占用 80% 时间,只能拿 20% KPI, 出了故障还要背锅,别说重构了,代码和我有一个能跑的就行

重构的挑战非常大,需要团队紧密合作,甚至需要公司及部门给予重构 KPI 才可以

何时做

事不过三,第一次做 dirty 事情只管去做,第二次做类似的事会产生反感,但还是要做,第三次就应该要重构。言外之外,闻到了代码腐烂的味道,比如函数过长、重复代码、逻辑混乱,类划分不清、过多硬编码、定义与实现耦合等等

1.预备性重构:书里说最佳时机是在添加新功能之前,修改 bug 时也一样,有时会发现,把部分代码做出调整,更有利于 debug, 或者把数据的更新与写入逻辑拆开,会避免造成错误

2.帮助理解性重构:接手新业务时,第一件事情就是去理解代码,然后才能着手修改。无论代码谁写的,都会思考:这段代码到底在做直么?如果重构他会不会更清楚地表达意图?比如我们有一些 hard code 变量整数,抽出来变成 const 会不会更清晰呢?

3.捡垃圾式重构:有些问题代码需要消耗几天人力,但此时有紧急的业务功能上线,为了不影响进度,可以忽略这块。但是要把问题代码,列入技术债 todo list, 回头有时间了,捡垃圾式的进生重构

4.长期工作:上面几种都是随机性质,无预期的重构。做为技术人,或是技术 leader 要制定长期的重构计划,不能等到代码腐烂,业务修改某个模块时顺带进行重构

那么什么时候决定不做重构呢?个人认为两方面:

  • 代码彻底腐烂,重构的成本还不如重写
  • 根据易变性分析,一百年不变的基础且稳定服务,不改也罢

怎么做

首先是构建防护网:重构的第一块基石是测试代码,集成测试和单测。每重构完一块大的函数,都要运行 UT 测试,确认重构有效,不改变原有的外部行为表现。如果我们的改动是 API 级别,或是大的模块改动,只有 UT 是不够的,就需要跑集成测试,从业务测证明没有问题,比如定单成交率,取消率,支付成功率等等

自测试代码、持续集成、重构三架马车缺一不可。但是国内公司基本不写 UT, 业务粗快猛干,规定时间内完成功能上线才是 KPI. 想想接手这样的代码,你敢重构嘛?此外还不包括 UT 不规范的

1
2
3
func BookOrder() error {
......
}

BookOrder 很简单的代码,为了 ut coverage, 根本不检查 error, 那就样的代码又有何意义呢???纯粹的糊弄,为了达到测试覆盖率而写代码

第二个指导原则就是渐进式重构,做任何改动都要有回滚措施,就也是工程能力的一种体现。比如我们 mysql 做表拆分,1 拆成 100 张表,并且分布在不同的 DB 实例中,那么你会怎么做???

建新表-> 实时同步数据 -> 验证数据正确 -> 切读 -> 切写,每一步如果有问题都要能做到回滚。代码也一样的道理

由谁来做

不可以让某些人专职做重构,原因有三个:

  • 对于专职重构的团队来讲,这不是绩效,没有产出,除非整个公司的方向也是梳理重写代码

  • 我们常说代码是屎山,有哪些见屎会有好心情呢?长此以往这是一种劝退

  • 非重构的工程师得不到锻炼,有人负责捡垃圾,那么当然可以肆无忌惮的写垃圾,反正完成功能上线就是 KPI

那么由谁来做重构呢?团队里的所有人,参考上面的何时做部分,预备性的重构,捡垃圾式的重构,长期有计划性的重构,每个人都要参与。只要构建好防护网,渐进式的重构,可能每天都在发生 minor change,容易测试与回滚,也容易合并 master, 整体代码质量不断向好的方向发展

谈谈实战

上面是理论部分,具体到实战细节能做的就太多了。工具 CI + 意识 + Peer Review, 要做到新写出来的是好代码,如果一边生产垃圾,一边捡垃圾,那远远没完

工具 CI lint, 要求代码修复语言书写的问题,这是强制的,能提前避免很多问题。意识需要定期培训,新人 onboarding, 老人也要加强学习。Peer Review 要严格,不能是简单的 Approve, 以前有个段子:你的 MR 只有几行,别人挑出一大堆毛病,但是如果涉及很多文件,别人只会写上 LGTM(Looks Good To Me) …

书里后半段都在描述细节:封装搬移重新组织数据简化条件重构 API处理继承关系等等,这些需要经验,需要多读读优秀的代码,需要仔细学习设计模式

严于律己,重构,从自己不写垃圾代码开始 !!!

小结

写文章不容易,如果对大家有所帮助和启发,请大家帮忙点击在看点赞分享 三连

关于 重构 大家有什么看法,欢迎留言一起讨论,大牛多留言 ^_^