代码重构

前一段时间写那篇《API 杂谈》的时候,其实是做 API 设计的时候。我们这个 API 最开始设计的时候支持三个项目,也就是三个产品共享的 API。经过设计阶段的抽象,整个 API 中大约百分之六七十的实现应该是共用的,每个产品只需要根据各自产品不同的部分,实现 API 每个产品相应 Handler 里面的一些程序。

因为三个项目的时间都比较紧张,所以这三个项目组同时针对各自的产品进行 API 的开发。并且由于业务原因,有些地方必须在短期内重用一些老代码。虽然我们有很完善的相互 Code Review 的机制,难免的,因为一些 Dependency 和 Deadline 的原因,最后实现还是和设计慢慢产生了差异。而这些差异如果听之任之,久而久之就会成为 Tech Debt。

因为 API 慢慢成型上线了,逐渐有了更多的产品希望使用我们的 API。理论上,他们只需要实现产品特有的百分之三十左右的逻辑,而且是在一个框架里实现,所以新产品的支持会相对很容易。但是由于并行开发中遗留的一些不完美,因此我们必须对其进行快速重构才能满足这样的需求。

于是,上周老大 JM 说:这周你别的事放一放,我们先把重构这事给做了吧。这一周下来,倒是有了几千行的代码量。因为白天还有很多别的工作,只能晚上写。虽然很累,但是收获颇多,说说自己的一些心得吧。

胸有成竹

之前写过《写代码的四个境界》,重构绝不是简单的把代码从一个地方移到另一个地方,很多时候,有些地方可能比写代码还要需要技巧。因为是基于一些已经成熟的代码,所以一定程度上简单,因为有些细节已经实现了。但从另一个意义上来说,需要把一些功能块抽象、去重复、并重新构造。有的时候想想,有点像整理房间。那么最重要的一点,就是你知道最后整理完的理想状态是什么样,并且始终脑海里有这个构图。

规划

重构的代码,可能还有人在做并行开发,也可能有依然未实现的设计。如何避免和别人的代码冲突,以及为后期开发留下余地,重构的时机很重要。尽量做到代码重构后不需要有大的框架改动。尽可能与同事们协调,挑选一个他们对共享部分改动最少的时候进行。

策略

这次重构,基本上很顺利。有三个自己觉得值得分享的经验。

一、预重构:对框架进行粗略的大刀阔斧的改动,把函数和参数进行抽象和梳理,但是忽略实现的细节。加一些 TODO,说明自己还想做哪些改动。这样做一个大的 PR,并描述出自己想做的东西,发给所有工程师,看看会不会有人对改动的大纲有意见或者建议。因为粗糙,差不多一两天就能弄完。老板和同事当时给出了很多意见,这样就避免了后期做细致改动时再有意见不统一的麻烦。同时这个预重构的代码帮助自己对 「成竹」 有个很细致的把握。

二、端到端的测试例集:大军未动,粮草先行。重构未动,测试先行。因为重构会改变细节的实现,但是已有代码的 API 层的行为是不应该有变动的。重构前,确保足够的 Test Case Coverage,让端到端的 Test Case 尽可能地覆盖所有的场景。这些 Tests 在整个重构中不应该再变动。它们是保证重构没有改变系统行为,没有引入新 Bug 的护城河。

如果有可能,一些 Monitoring 和 Logging 机制也是保证行为一致性的一个重要手段。

三、定计划,化整为零:经过预重构后,大概知道自己需要有哪些改动。根据它们的 Dependency,自己做一个 List,List 里面每一步要做什么,和前后的其他改动有没有关联,怎么关联,怎么保证每一个 PR 的改动、独立进 Production 时不会对其他特性产生影响。这样,一个个小的 PR 出去,一来降低风险,二来同事们做 Code Review 也会轻松很多。(之前关于 Code Review 的一篇文章:说说 Code Review

执行

执行的过程中,三点很重要:

一、每个 PR 出去,一定和相关的工程师做好足够的交流。改别人的代码一定要保证对别人的尊重,因为所有的细节都是别人的劳动成果,他/她们也对这些代码最为熟悉。如果可能,in Person 告诉她们为什么这段你要这么改,征求他们的意见。PR 出去后,也 tag 这些人,因为他们是最好的 Reviewer。

二、每一个小改动,如果可能,改 Test 和 改 Implementation 尽可能的 Decouple。也就是说,尽量避免同时两者都改。这样一定程度上也可以保证行为一致性。虽然这个很多时候比较困难,因为重构中不可避免地会改内部函数的接口。

三、保证自己按照重构计划进行的同时,Keep 一个 Change List,包括 TODO List(便于 K****eep Track 自己的进度以及保证没有漏掉什么)。另外,如果有对设计的变更,一定要保证设计文档的更新。这个一点也拖拉不得,文档一旦产生不一致,会对别的开发者(或 API 使用者)带来极大的困扰。有需要的话,重要的改动应该 Email 所有相关的人。

其他

最后,一点个人感悟。首先,如果重构的代码同时还在开发,尽量速战速决。时间长了就会变得复杂,因为新的代码和重构部分又开始相互影响。所以自己这一两周,倒是加班的厉害些。不过我们工作时间有弹性,需要快的时候动作快些,不忙的时候再休息好了。

另外就是重构中保证 over Communication。几乎每个改动,我都会和老大以及相关的同事再三确认,宁愿显得啰嗦些。程序的世界里,再小心也不为过。

最后,有一帮靠谱的同事真的很幸运。重构其实是一个极其需要协作的活儿。如果不是我周围的人都特别优秀,我觉得这事儿做起来可能要困难的多得多。

The Why·Liam·Blog by WhyLiam is licensed under a Creative Commons BY-NC-ND 4.0 International License.

WhyLiam创作并维护的Why·Liam·Blog采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Why·Liam·Blog (https://blog.naaln.com),版权所有,侵权必究。

本文永久链接:https://blog.naaln.com/2017/03/code-refactoring/