试试在飞驰的汽车上换一个全新的发动机-如何重构系统重写代码
F1赛车中,经常跑几圈就换一次轮胎,每次换4只车轮仅仅需要2秒钟。而在互联网行业,像这种边开车,边换轮胎的系统重写的事情也不少见。
最近看到一个关于重构的问题,让我想起以前一次换语言重写服务端的经历。
· 01 ·
起因
外包转为组建自有团队研发
项目的背景情况:两年前决定开拓新业务,由于本身是传统行业,没有自有的研发团队。所以采用了外包方式实现业务需求的技术系统的开发、测试及上线维护。
外包方式在项目初期运行得还比较好。因为不需要维护一个固定的技术团队,所以不需要经历开发招聘,各类人力、物力上的相关投入,人员磨合等等一系列的问题,直接就拿到一个现成系统,上线试水新业务方向。
而后,新业务逐渐发展后,外包的弊端就慢慢的显现出来。
产品的需求沟通不方便;
开发、测试完成后,反馈到业务方,再次修改调整的周期较长;
运行和维护也是由外包方负责,线上的问题响应和回复并不及时;
我接手这个项目的时候,老大希望将创新项目推进得更进一步,进行深度的独立运行。系统的研发方式,由外包转为内部自主开发的方式。
早期的为了试水新业务,或者是出于快速占领市场的需要,又或者是因为能组建起的技术团队人手少,业务系统的开发上,就直接以短、平、快作为唯一要求。
这种情况下的开发方案可能就不太考虑将来业务的可扩展性,技术的架构也就不过多的考虑性能、并发性等等,开发语言,使用的技术框架等也不考虑是否比较大众化,便于降低开发成本。
这种情况不少,早期的淘宝商城系统就是在买的源码基础上二次开发而来。
又由于业务发展的不可确定性,例如,
淘宝在发展期间,拓展出交易信用评价体系,接着又派生出来支付宝这样的支付交易系统。
滴滴从最开始的出租车业务,拓展出来快车、专车、顺风车,甚至共享单车业务,囊括了全面的出行业务。
美团从最开始餐饮团购拓展到吃喝玩乐全品类。
这样,“放飞”的业务不断发展,早期短平快的开发方式适应不了后期业务发展需要。从业务和技术两方面的要求来看,重构或者重写是不可避免的大概率事件。
· 02 ·
重写的原因
降低维护的人力成本
接手时面临两项急迫的任务:
从现有两个移动端开发人员的基础上,组建完整的技术研发团队。
现有系统的功能改版,以及日常维护。
外包公司实现采用 Golang + MongoDB 的方式,交接了详细的接口文档和源代码。采用 Go 是因为天生对并发支持比较好,而且开发效率高。采用 MongoDB 也是基于高性能方面考虑。
并发和高性能是早期外包的要求之一。虽然实际运行两年,从用户数和业务量上看,并没有并发量上的需求。
接手后,一边现学现卖,学习 Go 语言开发,阅读源代码,归纳原有的产品业务逻辑,理解代码实现的程序流程,做几个小功能上的迭代。一边着手组建技术团队,开始招聘后端、测试、设计、产品、前端。
结果在后端开发招聘碰到了问题:投递的简历中,会 Golang 的寥寥无几,而且待遇也无法满足对方的要求。
而此时,后续的大版本迭代需求眼看要排上日程。
怎么办?当时有两个选择:
仍然招聘 Go 开发,项目继续在原有的基础上开发。
改弦更张,招 JAVA 开发,把后端服务用 JAVA 重写一遍。
方案一有不可控的风险:可能招不到合适的人,后续维护乏力。
方案二的重写则是一条耗时长、容易失败的路。
重写对项目也没有直接的好处。因为用户不关心代码,他们只想使用 App。在用户看来,能够解决他们问题的产品就是好产品。否则,他们就不会用它。
用户不关心代码是否重写,所以重写后的版本必须和旧版本一样能用。
重写对公司也没有好处,面临在重写期间,版本无法升级,也无法对产品作出有效的调整,无法增加用户希望的功能。而这段时间,市场上可能会发生新的变化。
经过和老大几次沟通,最终获得理解,对重写方案的意见取得一致:
老大也比较理解这次重写,是偿还之前的外包技术债,需要在现在这个阶段去还技术债。
尽快招聘 JAVA 开发到岗。
重写的开发周期预估一个半月到两个月:一个月开发,半个月测试。
重写的过程中,如果有一些小功能修改,能实现的,尽量在现有版本实现。
重写的新版本上线后尽量不出问题,或者说不出大问题。
做出决定的时候,正在年底,所以发布上线时间预定在春节回来之后。
· 03 ·
如何重写
充分理解原有代码
重写工作量按模块拆解到人
重写开发有三大块工作任务:
JAVA 重写所有的 API 接口。
将原来的 MongDB 文档型数据库,转换为 Mysql 关系型数据。
测试重写前后的接口表现是否一致。
接下来招聘的同时,我通读了所有的接口代码,梳理了每个接口的实现业务逻辑。有不理解的地方,也找之前的实现人询问了细节。
完成代码通读后,做了一张任务划分表,把所有的 API 接口,按模块列在一个 Excel 表 上。
后端小伙伴到位后,我把负责写安卓端的小伙伴也拉入了重构开发小组,这样,重写小组加上我自己一共三个人。大约 60 多个接口,每个人平均分到 20 多个。
除了 JAVA 重写的工作量大的问题,中间还碰到原有 MongDB 数据的迁移到 Mysql 太慢的问题。
最开始执行一遍迁移数据要费持续好几个小时,拖慢了重写的进展。临时对这个没预计到的问题,花了大量精力,对数据转换过程做了优化。最终降到几分钟,可以接受的程度。
在移动端接口写得差不多的阶段,我们启动了转换数据的项目,用转换后的数据来测试接口是否正确:
先用同样的参数调用一个接口,获得返回的结果数据,
然后在新的系统同样参数调用同一个接口,对比返回的数据是否一致。
重点关注下面几个业务逻辑:
后台管理系统添加、修改数据后,移动端的展现是否一致。
移动端的业务是否正常:用户是否可以完成正常注册、登录、浏览、评论、下单、支付等业务操作
后台管理系统是正常:是否能正确反映用户数据,订单数据,商品库存数据,是否可以正常发布内容,等等。
在这个试错、修正、反馈、再修正的过程中,重写项目组在一个接着一个加班的日子里来到春节前夕。
每个项目上线前,照例是一阵鸡飞狗跳的时间。最后一周时间中,我们三个小伙伴就住在了公司附近的七天连锁酒店,用半封闭的方式调试和修改各种上线前细节问题。
· 04 ·
发布上线,直接替换,测试通过
切换上线的时候,由于前面的阶段比较有把握,同时也出于简化的考虑,我们采取的是直接替换方案(如果是其他的系统,可以考虑部分模块逐步替换,或者是灰度方案):
先停止原有的 Go + MongoDB 系统。
然后运行 MongoDB -> Mysql 的数据迁移过程,大约几分钟。
运行新的 Java 服务器端,和原系统用同一个地址,同一个端口,直接替代。
在移动端,提前做了一次迭代发布,碰到后端接口请求失败时,提示:“系统正在升级中...”。
重写代码时,心情是焦灼的。部署切换时,心情是紧张的。
上线后,我们打开 app 测试:
首页打开正常,浏览活动正常,发个评论也正常,再注册一个用户,正常,后台管理系统各种内容发布也都正常。
初步测试正常,这次直接切换新旧系统的“冒险”行动获得了成功。
从接手开始的三个月忙碌的时间终于得到了回报,心情是喜悦和放松的。
终于长出了一口气。后续的问题修复和功能迭代,就可以在新的 JAVA 系统上修改了。
后来在向其他部门的小伙伴解释这个重写的项目时,用了一个比喻:就像给一辆高速行驶的汽车换发动机。很冒险也很疯狂,但是成功了。
这次,初创的技术研发团队顺利的完成了重写任务。回顾起来,有下面几个重要的因素:
-
老大和其他部门的理解。
老大不太懂技术,但是很理解技术人的想法,支持我们还技术债,完成 JAVA 的重写方案。
-
团队内小伙伴们理解。
在重写的阶段,加班加点费尽心力的支持,尤其最后上线冲刺阶段的努力。
-
任务分解到位。
任务分解到每个人,粒度足够细,进度得以控制。从上线时间倒推每个接口的开发时间点,并要求估算时间包括测试,修正以及回归的任务量。
-
测试方案行之有效。
每一个接口,新旧系统的输入和输出对照一致,保证了系统的平稳过渡。
技术人的生活,就是一个接着一个的重写。不断学习新的知识,用新的认知形成迭代后的新知识体系。
··················END··················
长按下图二维码关注,这是一个有趣的博主。
其他原创文章可以戳:
嗨,你在看吗?