关于重构优化的一点总结

为什么

首先,为什么要做重构优化?重构优化的目标是什么?

这个问题的核心是系统的“应然”与“实然”之间的落差:我们认为系统“应该是”怎样的?它目前“实际是”怎样的?要让二者看齐,就需要开展一系列的重构优化工作。

与业务需求不同的是,重构优化所指向的“应然”,可以从这几个维度来回答:当前问题与长期规划,结构重塑与性能优化,技术指标与业务功能,系统目标与人员目标,等等。

多维度系统.jpg

绝大多数的重构优化都从解决系统当前问题开始。现有系统架构不能容纳新的业务需求,操作性能不满足预期,代码结构不清晰,开发维护消耗资源过多等等。

落实系统长期规划也是启动重构优化的重要原因。由于种种原因,在系统实现的应然与实然之间,必然存在差异。这些差异未必会为当前系统带来问题,但同样应该通过重构优化来消除。

当前问题与长期规划是标准的局部最优与全局最优的辩证关系。局部最优不一定是全局最优,解决当前问题有时会与落实长期规划背道而驰;落实长期规划的过程中,也难免产生新的“当前问题”。在解决当期问题的同时,重构优化项目应当充分考虑系统的长期规划,尽可能让每一次解决当前问题,都成为实现长期规划的一个步骤。

结构重塑和性能优化也是常见的重构优化方向。

在主要由CRUD(而非复杂算法)组成的业务系统中,结构性复杂度会越来越高、越来越显著;不同的代码结构在学习成本、开发成本、维护成本、性能表现、可复用性和可扩展性方面的差异会越来越大,结构重塑带来的质变效果也会越来越明显。因而,无论在代码层面还是系统层面,结构重塑都是重构优化最常见的目标,也是最经常实施的重构工作。但是,结构重塑很难提出定性指标:哪种结构更好,好在哪里,好多少,往往只能定性分析。复杂度、信息熵等分析方法虽然有一定效用,但是本身不够成熟,实用性、接受度也欠佳。

性能优化向来有“不要提前优化”的箴言,通常是性能遇到瓶颈时的才浮现的选项。由于我们几乎不可能提前预判性能优化的“堵点”和优化后的改进量,所以不要在未做监测分析的情况下开展性能优化。但是从战略上来说,性能优化必须提前布局:完善性能监测体系;制定应急方案;预先察觉性能风险、制定性能优化方案等等,这些工作不能等到性能风险变成问题才开始。有了性能监测体系,才能保证性能优化建立在定量分析的基础上,这也是我们不必在展开监测之前“提前”开始性能优化的重要原因。

结构重塑与性能优化之间也存在一定的矛盾。尤其是在追求极致性能时,往往要打破固有结构,“拆墙打洞抄近路”。不过一般情况下,良好的结构也能带来出色的性能,这是二者的统一之处。

技术指标与业务功能之间通常没有争议:重构优化就是要在保证业务功能不受影响的前提下提升业务指标,即所谓“开着飞机换引擎”——飞机还要照常飞,引擎却要更新换代。

实际上,这种观点是“业务引领技术”的滥觞:技术只是实现业务的工具,不应对业务过多地指手画脚。然而,一套出色的技术系统,不仅可以满足业务的现有需求,而且可以容纳可预见的业务发展,甚至可以探索业务的新模式、新方向。当然,这也可以归入系统的“长期规划”当中。

上述目标基本都着眼于系统层面,团队人员似乎只是实现这些目标的工具。其实不然:人既是工具,也是目的。对系统进行重构优化,不仅是为了实现系统的“应然”,也是为了实现人的“应然”:我们这些程序员的工作应该是怎样的?实际是怎样的?二者是否有落差?是否需要抹平落差?

人:工具与目标.png


团队准备

在上述几个方面达成团队共识之后,就可以开始第二个话题:开展重构优化工作应采取怎样的步骤?

首先要做的,当然是制定具体的、明确的目标。本次重构优化需要解决当前的哪些问题?要落实怎样的长期规划?要调整哪些结构框架?要提升哪些性能指标?涉及哪些业务功能,哪些业务要维持原状,哪些业务会有变更?这些问题的答案,不仅要立足于系统和业务的现状,更要有足够的“超越性”、“前瞻性”。而且,其它准备工作都可以边开路边找路,但这个目标最应该“谋定而后动”。

统一团队认识也是必须要做的准备工作。任何重构优化都要依靠团队成员来完成,团队成员同样是重构优化的目的之一。这项工作一定是一个长期的过程,无法一蹴而就:提出目标本身就需要对系统现状和未来规划进行充分调研分析,这就需要花费大量的时间精力,不可能拍脑门决定。对团队成员的工作也耐心细致地做足、做到位,光靠下级服从上级,只会“一楚人傅之、众齐人咻之”,执行结果必然变味。

上述工作虽然分列两项,实际是同一项工作的过程和结果:有了团队共同调研分析的过程,才有共同目标、统一认识这一结果。因此,这项工作天然具有民主集中的特性。无论不够民主还是不够集中,都会导致“莫衷一是”和“各行其是”,甚至“一无是处”。而一旦对重构优化的目标形成共识,重构工作不待启动就会自然而然地开始推进。

确立目标之后,研发团队还需要制定实施计划,有步骤有节点、有分工有协作地落实项目目标。这是后续工作,后续再说。

在项目正式启动之前,研发团队还应该在人员贮备方面做好准备,让团队不仅能够“齐心协力”,而且有足够的“力”可以协同:即应当保证研发团队掌握足够的业务知识和技术能力。业务知识是系统“应然”的基石,掌握业务知识才能做出正确的未来规划。技术能力是变“实然”为“应然”的能力,掌握技术能力才能更好地将“未来规划”变成现实。两者的关系类似“做正确的事”和“正确地做事”,如果一定要排个座次,业务知识当居首要。

除了开发团队之外,其它团队也需要为重构优化工作做一些准备。其中,最主要的是测试团队。

测试团队深度介入研发项目,也是重构优化项目的重要同盟。测试需要准备充足的测试用例,以保障重构优化成果的正确性:业务功能正确,性能优化方向正确,等等。

严格来讲,测试工作分三层:由研发主导的单元测试,测试主导的功能/性能测试,以及业务主导的业务验收。单元测试面向逻辑片段,与代码结构深度绑定,而重构优化工作几乎无一例外要打破原有代码结构。因此,过往积累的单元测试在重构优化项目中往往要全部重写。功能/性能测试则面向业务功能,与需求高度匹配,而与代码结构关系较小,可以充分复用日常需求中积累的测试用例。业务验收一般没有什么积累,其它前文已述。

由于代码实现的是“规则”,而测试验证的是“场景”,再加上系统中的代码复用、逻辑啮合、数据关联等因素,越是复杂系统越容易“牵一发动全身”(甚至“牵一发不知道哪儿会动”),开发测试中就必然出现“改一行代码、测十个场景”这类测试工作量暴涨问题。因此,高效率测试验证也是重构优化项目必不可少的一项准备工作。如果测试团队无法保证自动化测试,开发团队应设法用双线验证、流量切换、线上流量收录回放等方式,自动化地覆盖和测试重构优化后的业务场景。

运维团队和大数据团队在重构优化项目中的工作相对较少,集中在性能优化需求中负责性能监测、指标分析、中间件运维、数据脚本等支持性工作。此外,如果能提供devops所需自动化、流水线支持,对研发测试也能起到很大帮助。

业务团队主要在一头(需求)一尾(验收)参与项目。在重构优化工作期间,业务需求往往会受影响,如与重构优化工作并项导致工期延长等,业务团队应当知悉相关情况,并尽量配合。此外,重构优化完成后,业务团队应适当参与验收工作,以确保业务功能不受影响。


实施方案

实践中,几乎没有哪一项工作是在准备万全之后才开始,何况这些工作也没有明确的“完成”节点。比较切实的路线是“以赛带练”,在项目前期或试点阶段完成准备工作,在准备基本就绪后开始全面铺开、大力推动。这就涉及到重构优化项目的实施步骤问题了。

一般而论(或者说经验而论),重构优化项目的实施方案有以下几种。

第一种,在原系统基础上,随业务需求或日常需求,进行小范围的逐步重构优化。

这种方案最大的优点是“小步快跑”:每次优化的代码改动量小,影响范围小,上线风险小。因而,这是研发团队实施准备工作最常见的方案。调研现存问题,分析未来规划,掌握业务知识,增进技术能力,都需要实打实地扎进系统代码中,并且需要带着“用另一种方式去写代码、做业务”的思路去做需求。借助这样小范围的重构优化,可以完成研发团队的基本准备工作;加以围绕重构优化的充分讨论,可以完成统一团队认识、树立共同目标的核心准备工作。

因此,即使暂不展开重大的重构优化项目,这类“微重构”也应该是日常工作中必不可少的事项。没有专门的重构项目时,这也许是对抗系统“腐化”的唯一途径;没有专门的技术培训时,这也许是提升团队能力的最佳途径。此前说“一旦对重构优化的目标形成共识,重构工作不待启动就会自然而然地开始推进”,指的也是以这种方案启动并推进重构。

这个方案的缺点也比较明显。首先,步子太小,虽然步频快,但落实系统长期规划还是偏慢。其次,小步快跑对测试的压力会指数级增长:测试不仅要验证本次需求的业务功能,还要回归重构优化相关的其它需求。“快跑”带来的高频次的反复回归,对测试自动化的要求更高。由此又会引出需求工期问题。业务需求的工期通常是倒排而来,压得比较紧实。“见缝插针”的余地本就不多。最后,再怎么精细规划,也很难把重构优化目标全部拆分成可以“小步快跑”的步骤;团队总有一天要面对中大规模的重构优化项目。

因此,这一方案适合在没有专门的重构项目时(或者暂时没有必要启动专门项目)时,在日常工作中常抓不懈。或者作为专门项目的前期工作,先将可以精细化拆分、独立实施的小范围优化推动落实;然后再集中精力推动专门的项目。

第二种方案,即启动专门的重构优化类项目,在原系统基础上开展大规模重构,以逐步将原系统逻辑、业务迁移到重构后的代码上来。

这是最常见的一种方案。通常会启动专门项目,因而可以集中各方力量快速落实。对比第一种方案,这个方案的成效更加显著。

这个方案最主要的缺点来自重构范围与上线风险之间的矛盾。重构优化范围越大,带来的风险就越大;风险越大,应对风险所投入的成本就越大。而且,只要不以邻为壑,无论多么周全的工作都不可能完全消弭风险,而只能最大限度地降低风险发生概率、减少风险影响范围。而要做到这一点,最可行的方案就是减小重构范围——然而,如果范围足够小,按第一种方案执行即可,并没有必要启动专门项目。

此外,工作量越大,往往意味着所需工期就越长。工期越长,重构优化项目与日常业务需求的矛盾就越突出,缩短工期的压力就越大……当前系统很多问题的直接原因就是赶工;重构优化工作如果不顾这一前车之鉴,通常很快就“殷鉴不远”了。

因此,这一方案比较适合与第一种方案开展协同:能够拆解为独立小步骤的,应尽量按第一种方案实施;确有需要的,再以集中力量以第二种方案进行攻关。

第三种方案是重敲锣鼓另开张,把原系统放到一边,重做一套系统;然后或逐步、或一次性地迁移流量。

与其叫“重构”,这个方案不如叫“重做”。重做系统可以规避前历史包袱的问题,因而可以降低很大一部分与历史问题兼容和过渡的成本。但同时,也放大了第二个方案的主要缺点:重做系统的改造范围最大,风险也就最高。不到万不得已、或者时机非常成熟(例如已有现成的、成熟的新系统),一般不会采取这种方案。

定目标、指路径,下一步该是带团队了。严格来说,这并不是重构优化项目才需要考虑的事情,而且在前面已经阐述过,再多说反而啰嗦。

那么,重构优化项目何时完结呢?

按理说,既然事先定下了目标,那么完成既定目标之后,项目自然就结束了。但是重构优化项目的特殊性在于:它的目标十之八九是动态变化的。在解决现有问题的同时,难免还会引入新的问题;在落实系统的未来规划的同时,难免还会形成新的规划……重构优化类项目的目标直指系统和团队;系统和团队在项目过程中发生变化,项目目标也就难免发生变化。

由此而论,重构优化类项目就像——乐观的说,就像芝诺的乌龟;悲观的说,就像希绪弗斯的巨石。这要求我们一方面能够“知止”,接受“苟完”而不“苟美”的成果。另一方面,这也要求我们将重构优化项目转化为持续性的、群众性的日常工作,持之以恒地、全员负责地不断向前推进。

持续工作.jpg