实战篇:一个核心系统 3 万行代码的重构之旅

经典著做《重构》这本书中有这么一段话:程序员

一开始,我所作的重构都停留在细枝末节上。随着代码趋向简洁,我发现本身能够看到一些设计层面的东西了,这些是我之前理解不到的,若是没有重构,我达不到这种高度。数据库

重构,着实是一件让程序员兴奋的事情。缓存

今年年初,咱们团队完成了一个复杂项目的重构工做,它属于广告系统最核心的引擎部分,大概有 300 多个文件,3 万多行代码。架构

从技术方案设计到最终全量上线仅仅花了 1 个月左右的时间,并且没有产生事故。框架

这应该是我 8 年程序生涯中,经历过的最大型的同时最成功的一次重构项目:速度足够快、计划比较周全、质量过关。模块化


01 先聊聊这个系统的历史包袱

咱们的广告引擎在此次重构前大概经历了1年半时间的迭代,初期针对的是搜索场景,业务单一,流程清晰。性能

2019年开始,公司的广告业务开始快速扩张,收入几乎是指数级的增加。在这个过程当中,咱们的广告引擎面临了两个挑战:测试

一、业务场景开始变得复杂,除了搜索广告,还须要支持信息流推荐以及类似推荐场景。设计

二、广告流量开始快速增长,除了知足功能性需求,还须要兼顾好性能。日志

通过梳理,整个引擎有大部分逻辑是能够公用的,所以咱们定义了一个主体框架,同时将可扩展部分进行了抽象。这样,各个场景可以根据自身业务的特殊性实现某些公共接口便可。另外,从性能角度考虑,咱们牺牲了一些代码可读性,把某些逻辑并行化了。

随着业务的发展,搜索场景开始进入快速迭代期,新增策略愈来愈多,咱们的主体框架也是在这个时候逐渐变得不灵活。

若是动主体框架,搜索之外的场景都须要跟着重构。 在业务的快速发展期,工期根本不容许,所以咱们只能在现有框架上进行补丁式的开发。 这样,带来了两个很明显的问题:

一、为了兼容搜索的特殊逻辑,咱们须要在其余场景中增长各类 if 判断来绕过这些逻辑。

二、广告策略愈来愈多,累计了几十个,当框架失去清晰的结构后,有些策略的实现开始变得定制化,缺乏层次化的划分和可插拔式的抽象设计。

在这样的背景下,随着改动的积累,代码开始偏离了设计的初衷,技术债务愈来愈重。可是,咱们又始终找不到合适的时机进行重构。

起色出如今 2019 年年末,因为广告业务的特殊性,流量开始天然走低,另外产品运营团队将重心放在了第 2 年的工做规划上,所以给了咱们很是好的窗口期开始这次重构。

咱们将工期定成了 1 个月,最终仅比预期晚上线了一天,虽然出现了两个线上问题,可是在灰度期都及时发现和修复了,并无形成线上事故。

整体来讲,这是一次难度颇大而且比较成功的重构项目,下面详细说一下我从这个项目中吸收到的宝贵经验。

02 重构前,咱们作了哪些准备工做?

此次重构的代码量很大,3 万多行,并且是广告系统最核心的引擎部分。启动前,咱们能预期到下面这些困难:

一、业务侧的阻力:广告是极其以业务为导向的,本次重构虽然能带来长期研发效率的提高,可是无法直接提高业务收益,并且开发周期不会过短,如何才能获得业务同窗的支持?

二、技术侧的顾虑 :重构一旦引发线上事故,公司是有处罚制度的,如何让你们轻装上阵?同时,重构过程当中若是还有很是重的业务迭代穿插,交付时间没人敢保证,质量也很可贵到控制。

针对这两方的顾虑,我认为下面这几项工做起到了很关键的做用。

一、让全部人看到痛点

前面提到:随着业务迭代,咱们广告引擎的主体框架已经变得模糊不清,另外几十个广告策略散落在不一样的业务场景中,配置凌乱。

针对这两个痛点,咱们提早1个月启动了现有业务的梳理,走读旧代码、同时翻阅之前的需求文档,最终咱们将不一样场景的核心流程以及广告策略归类成了一张清晰的表格。

正是这一张表格,让技术和产品第一次很清晰地看到了咱们引擎部分的全貌,体会到了业务的复杂度以及当前技术上的瓶颈。

二、明确重构的目标和价值

让全部人感觉到痛点后,咱们规划了本次重构的两个核心目标:

一、主体框架的重构:将主流程模块化,从新定义上下层协议,确保接口清晰;各层级内部也须要作好抽象,具有良好的扩展性。

二、策略灵活可配置:将广告策略按照业务意图进行归类抽象,策略的执行条件动态可配置,同时策略可任意插拔。

此外,咱们将这两个核心目标完成后可带来的预期收益进行了细化:

一、技术收益:代码结构更清晰,更容易理解和维护;可扩展性加强,引擎的开发效率将进一步提高。

二、业务收益:策略能作到更细粒度的配置和扩展,对业务支持更友好;研发提效后能进一步加快业务的迭代速度。

将重构的价值同步给你们后,进一步提高了全部人的兴奋度,让你们有了更强的动力参与进来。

三、总体节奏的把控

总体节奏的把控也是很是重要的一环,能让全部人对这件事情有一个时间上的预期。

首先,咱们将工期定成了 1 个月,一方面考虑了业务侧能够接受的最大周期,技术上也但愿速战速决;另外一方面,春节即未来临,咱们必须赶在公司封网前上线,同时预留出1-2周的 buffer 以防意外状况发生。

此外,咱们和业务侧达成了一致:重构期间,引擎部分的非紧急需求一概不接,这样可最大限度地减小并行开发和代码冲突,让团队精力更集中。


03 执行过程当中有哪些可分享的经验?

此次重构可以实施得如此顺利,有 4 点我认为颇有价值的经验跟你们分享下。

一、高质量的技术设计方案

这一点得益于平常的要求,针对开发周期超过3天的项目咱们都会进行技术方案设计,本次重构固然也不例外。

框架部分的总体架构、模块之间的协议设计、以及策略的可扩展性设计是本次技术方案的重点,团队先后讨论了不下3次。

在大方案定稿后,团队进一步对数据库、接口字段、缓存结构、日志埋点等公共部分进行了细化,由于涉及到多人协做开发,团队约定以文档做为沟通界面,文档始终保持和代码同步。

在这样的高要求下,团队产出了 5000 多字的技术方案文档,合计 36 页,这些为总体质量的保障打下了很好的基础。

二、 预重构出框架性代码

这一个 PR 很是关键,是咱们从技术方案落地到代码最重要的一步。咱们把重构后的包结构、模块划分、各层之间的API定义、不一样广告策略的抽象进行了梳理,先忽略实现的细节。

这样主体代码基本成型,能很清楚地描绘出咱们理想中的框架。而后,咱们组织了屡次集中代码审查,最终造成了统一意见。

这一步能很好地避免过早陷入实现细节,致使主体框架关注不够、代码不稳固,后期再返工反而会拖累效率。

三、 频繁沟通和成对代码 Review 机制

进入到细节实现阶段后,很重要的一点是:对现有逻辑的理解。引擎代码通过一年半的迭代,历史上被不少人开发过,可是本次只有 3 个同窗参与重构。

整个过程当中,咱们遇到任何代码逻辑不明确的地方,都是反复沟通和求证,不主观猜测,这一份谨慎其实很关键。

另外在代码审查上,咱们按模块分配了对这块业务比较熟悉的同窗来负责,成对搭配,机制灵活。

四、 有效的测试方案

重构未动,测试先行。这个原则是《重构》一书中重点强调的,也是咱们本次技术方案讨论的重点,我这里单独拎出来详细展开下。

首先,咱们前期便约定好:不动任何老代码,彻底建新的 package 进行重构。这样方便比对重构先后的结果,同时进行线上灰度实验。


测试方案上,如下 4 点值得借鉴:

一、端到端测试:本次重构不涉及功能性的调整,所以外层API的行为是不会有任何变化的,这样端到端的测试方法最为有效,这个是研发和QA测试最主要的手段。

二、冒烟测试:QA同窗提供冒烟 Case,由研发同窗进行冒烟,研发提测前必须保证全部冒烟 Case 执行经过。这一点在大部分互联网公司都不常见,可是对于大型项目绝对有效。

三、沙箱环境双流程验证:前面提到咱们重构先后的代码都保留了,所以能够经过脚本抓取线上环境的入参做为case,而后用自动化的方式对 API 的返回字段进行逐一比对。

四、线上环境灰度实验:灰度对于重构很是重要,咱们利用已有的ABTest平台,逐步放开灰度流量,从5%、到10%、到30%、最后到100%,制定了很谨慎的放量节奏,而后经过日志以及业务指标监控进行验证。


写在最后

回顾整个重构的过程,总结成下面 7 个关键点:

一、把握好重构时机

二、前期梳理很重要,先找到痛点

三、明确出目标和价值,让你们兴奋起来

四、不宜长线做战,不宜和业务并行

五、须要高质量的技术方案

六、重构未动,测试先行

七、当心求证,为每行代码负责

固然,最关键的因素仍是人,大型项目重构极其考验团队的协做能力,若是每一个人都很靠谱,重构就已经成功了一半。



做者简介:985硕士,前亚马逊工程师,现58转转技术总监

欢迎扫描下方的二维码,关注个人我的公众号:IT人的职场进阶

相关文章
相关标签/搜索