浅谈遗留代码的重构

refactor-decision-list

背景

《重构》诞生至今有近17个年头了,日常开发中大家谈到重构,要么非常随意,认为重构就是改代码;要么非常谨慎,把重构描述成焦油坑,像瘟神一样敬而远之。我们应该怎么看重构呢?针对最具挑战的遗留代码的重构,有哪些需要注意的呢?

谈论任何事情,都该有它的上下文。本文谈论的技术背景是大型通信类产品,对于互联网产品不一定适用。另外,本文也不会涉及重构技术,有兴趣读者可以读《重构》或者《Effective Refactoring in C++》。

重构与收拾屋子

为什么收拾屋子?

住酒店,有服务生帮我们收拾房间,在家需要自己收拾,因为自己还要住很长时间。屋子干净了,还是有好处的,东西就好找了,哪些东西放的位置不对,也更容易识别出来。

重构亦是。我们看看《重构》中Martin Fowler对“重构”的定义:

名词定义:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本

定义中给出了重构的两个目的(收益):

  1. 提高可理解性
  2. 降低修改成本

代码编写完成后,自己或者他人还会维护较长一段时间。重构,是期望代码可以更多的被复用,有更长的服役时间。

如何收拾屋子?

根据屋子的布置(结构),每件物品都有它应该放置的位置,发现不在位置上,就调整调整。

重构亦是。重构是对软件内部结构的一种调整。好的软件在满足系统功能正确性、性能等要素同时,还需考虑软件的扩展性、伸缩性和可读性。所以它应该是有架构的,体现为横向切分的不同层次和纵向切分的不同模块,及更细粒度的类、方法、函数。重构就是根据软件本身应该的结构,对代码元素进行调整,放到合适的位置。

什么时候收拾?

发现屋子脏了、乱了就收拾收拾,工作忙,就周末收拾,工作轻松些就每天顺手收拾。但是房子脏,那是欠的债,迟早需要还的。

重构亦是。伴随着开发的过程,重构应该是个习惯,发现代码有坏味道了,就及时消除掉。交付压力太大时,就稍微缓缓,等稍能喘口气,就赶紧把债还掉,不然积少成多,可能就很难收拾了。

Martin Flower 给出了四个重构时机:

  • 产生重复代码时,
  • 新增功能之前
  • 修改故障之前
  • 代码走查之后

Kent Beck提出的XP(eXtreme Programming)中,TDD(Test Driven Development)实践更是把重构作为开发过程中的一部分:
refactor-decision-list

逢年过节,你会来个大扫除,彻底把屋子调整调整。

重构亦是。时间久了,新人多了,交付压力大了,代码难免会产生一些腐化,这些可以通过集中重构,彻底清洗清洗。

发现房间布置,格调有点跟不上时代,需要整的大些,就是装修了。要看自己有没有地儿住,有没有时间折腾。

重构亦是。当系统新增功能改动很大;当系统性能成为瓶颈,无法忍受;当代码穿着一层层补丁贴成的外衣,仍到处漏风时,此时缝缝补补此时已无济于事,需要更大的调整,才能根除这些问题。当然此时系统已经上线,用户嗷嗷待哺,你需要有替代方案过度,需要争取时间调整。

再看一则小故事:

一个建筑队,全国各地到处跑,住的是一个茅草屋。遮风避雨是没有问题的,只是每到一地都要重盖,一到下大雨还到处漏水。修修补补的办法总有的,但总是很狼狈。有一天,有人告诉工程队,可以用可组合的铁框为骨架,彩钢为夹层,这样既可以连续拆装,节省成本,房子也结实牢固。 工人们开始担心了:“怎么组装啊,看上去好麻烦!”。工程队的老人也替工头担心,“那得花多少钱啊”,“会不会住不习惯”,工头出去考察了一番,回来一狠心,整了一套,虽然过程有些费劲,结果收益还是杠杠的。后来大家几乎都忘了曾经住过茅草屋。

当然,这里讲的不再是重构了,这种推倒重来的做法,我们叫它再工程(re-engineering)。与重构相比,他的风险更大,成本更高。但所谓高风险,高回报,如果它能带来更大的收益,甚至颠覆性创新,我们也值得去做。

再工程不是本文讨论的重点,打住!

遗留代码重构

遗留代码的重构属于上文中提到的“大扫除”或“装修”场景。对遗留代码进行重构,很容易形成“吃力不讨好”的局面,究其原因,我们先回顾下重构的目的:

  • 提高可理解性
  • 降低修改成本

这两点,无论从可验证性,还是可被度量角度都比较困难。如果项目仅以短期结果度量,重构成果很难自证明。再加之改动较大,可能引入一系列不确定因素,无功还有过,自然吃力不讨好,所以我们在进行遗留代码重构时要充分考虑收益和风险,收益尽量考虑可被验证、被度量要素,风险充分考虑成本、时间、范围等项目关注要素。在一个工程师话语权不是那么大的公司,这一点尤为重要。

基于上述场景分析,定义了遗留代码重构决策表:
refactor-decision-list

从重构带来的收益和风险两个维度,综合考量、打分,给出一个简单、可度量、易被执行的决策表。下面我们逐一分析下每条决策项:

收益

  1. 性能瓶颈

看到这条,你一定很不解:一些经验也告诉我们,软件的扩展性,常会牺牲一些性能;再看看《重构》书中一段描述:

为了让软件易于理解,你常会做出一些使程序运行变慢的修改

而更好的可读性及好的扩展性,恰是重构追求的,岂不是自相矛盾?

关于性能优化,我会在另一篇文章中详细阐述,我们先看结果,重构会给我们带来如下在性能方面的改善:

  • 结构良好的代码,在性能分析时有更细的粒度,更容易发现性能瓶颈
  • 逻辑清晰的软件,更容易反映软件业务本质,而清楚我们真正要解决的问题,对性能往往有意想不到的提升
  • 对软件结构的调整,使得对象及对象之间的关系更合理,可以大量减少内存浪费
  • 多核、分布式场景下,性能的瓶颈往往不是计算本身,而是不合理的调度,对软件结构的调整,可以从根本上解除该部分约束

另外,性能优化很容易被度量,该部分产生的成果很容易被项目接受。

  1. 高危、高频故障

看到这条,你又开始不解了,重构是“在不改变软件可观察行为的前提下”进行的,而故障本身就是软件在特定场景下的错误行为,所以重构是改变不了故障本身的。那对高危、高频故障模块,重构的价值在哪里呢?

  • 某模块故障总是消灭一波,又来一波,攻不死,杀不完,一方面,说明该模块需求变化还是很频繁的,另一方面,说明模块设计出了问题,要么是逻辑混乱,要么是内部耦合太大,这些都可以通过重构来消除。
  • 重构的一个目的是“提高可理解性”,逻辑清晰、整洁的代码,使故障就像白墙上的苍蝇,很容易发现,解决。
  • 重构的另一个目的是“降低修改成本”,软件容易修改,需要软件遵循开放封闭原则,修改代码不影响原有功能,也就避免了增加功能、修改故障引入的新问题。
  • 故障数是一个容易度量的指标,效果很容易可视化。
  1. 新功能扩展困难

软件之所以需要设计,而不仅仅实现功能,一方面可以被复用;另一方面容易增加功能。新增功能困难,并非是无法增加功能,而是,增加功能需要改动很多代码,从而带来更多风险,更大维护成本。 重构通过对软件内部结构的调整,不断消除重复,划分不同层次,使得新增功能对原有功能影响尽量小。

  1. 代码逻辑混乱,可读性差

编写易读、易理解的代码,并不像说的那么容易,因为它是反直觉的,它产生的价值不是对当下的自己,而是以后的自己或者其他人,需要换位思考。

简单分享下自己对编码认识的几个阶段:

  1. 实现功能,追求性能
  2. 考虑扩展性,增加功能比较容易
  3. 考虑易理解,维护代码比较容易
  4. 考虑易复用,除了自己,期望他人也可以用

重构对易理解性带来的收益:

  • 对代码重构的过程,是对代码所表述业务逻辑再理解的过程。
  • 易理解的代码,更容易发现业务本质
  1. 人员能力提升

这里的人员能力提升包括两个方面:

  1. 业务能力提升。重构过程中是对业务逻辑再理解的过程,通过一层层抽丝剥茧,我们也更了解业务本身。
  2. 技术能力提升。无论是重构到Clean Code,还是重构到模式,我们的抽象能力、设计能力会伴随着这个过程逐渐提升。

风险

任何一件事,当我们看到收益的同时,应该评估它带来的风险。对于遗留代码的重构,在动工之前,我们需要回答如下问题:

  • 重构的主要目标是什么?因为在重构过程中,难免会遇到抉择和舍弃,如果没想清楚我们的主要目标,容易摇摆不定或者迷失了方向。
  • 重构的范围是什么?重构最容易掉入的一个陷阱就是,重构范围越来越大,大到无法收手。
  • 重构的计划是什么?虽然重构过程中,有太多的不确定因素,极端场景下,重构的结果给当初认为的完全不一样,但我们确实需要一个时间盒,在它的约束下,我们更容易集中精力达成我们预期的目标。
  • 重构真的必要吗?有没有低成本的替代方案?虽然我们鼓励用技术解决问题,但生活中的确存在很多在研发来看很重要,从商业角度“然并卵”的事。

想清楚上面的问题后,继续考虑如下维度:

  1. 人员支撑情况

人是重构的核心资源,靠谱的人才能做出靠谱的产品。一方面,重构的质量、完成的速度依赖人,另一方面,重构过后代码的维护及架构的演进也依赖人。需靠考虑如下几个方面:

  • 重构要求不能改变软件的外部行为,我们还期望通过重构可以简化设计,缩小业务与实现之间的Gap,这就需要有熟悉业务人员。你可能会说:“业务全在代码里了,自己看不就行了”,说的没错,只是太累了
  • 严格按照重构手法,基本可以做到重构前后业务逻辑的一致,这就需要至少有人熟悉重构技法。
  • 高效率来自专注,如果不能全身心投入,或者任务不断切换,结果往往劳力又劳心。
  • 团队中有Tech Lead,不但可以帮助提升团队重构技能,在团队产生技术争执时,还可以进行裁决。
  • QA是团队交付产品质量的最后一道防线,如果重构过程中,能不断得到对重构质量的反馈,可以大大降低重构带来的风险。
  1. 重构周期

每个产品都有版本计划及市场使命。如果产品即将退市,对它进行的重构,无疑是没有任何意义的,因为重构后的软件已经没有上场表演的机会。重构需要根据市场需求和重构时间,选择能切入的时机。比较有效的一个方法是Small Step重构,把重构任务进行拆解,切分到一个个迭代中增量完成。

遥遥无期的重构,由于项目看不到短期收益,容易动摇支持重构的决心;另外,在重构期间,可能还不断有新功能加入,为了做到可以替代原有产品,在重构同时,还需要不断追赶这些功能,巨大的压力,容易使团队身心疲惫。

  1. 代码度量数据

平均圈复杂度、函数平均行数、代码总行数、重复度等代码度量,可以作为是否进行重构的参考,也是预估重构周期的一个重要指标。另外,重构过程中,在CI部署代码度量检查,可以看到代码复杂度不断下降,提升坚持重构的信心。

  1. 自动化测试包围情况

保证重构“不改变软件可观察行为”最有效的举措,就是待重构代码已经有大量自动化测试用例包围。考虑如下情况:

  • 测试用例最好是基于业务进行拆分,并且覆盖场景比较全面
  • 测试框架支持不同平台,可以减少重构对平台环境的依赖,自由选择
  • 如果已有测试用例执行速度较快,可以保证重构有更好的节奏感。

如果测试用例覆盖场景较少,不推荐补充完所有场景测试用例后再进行重构。一个推荐的做法是,按照重构计划,先补充某个场景用例,然后对其进行重构,交付后继续进行下一个场景,循环迭代,直到所有场景都完成。

另外,在CI中部署分支覆盖率监控工具,可以感知到分支覆盖情况逐渐变好,在代码重构完成同时,也交付了一份自动化测试用例(当然,分支覆盖率仅能保证分支被跑到,并不能保证逻辑正确)。

遗留代码重构决策表(Excel版)

refactor-decision-list

下载地址:https://github.com/liyongshun/refactor/blob/master/refactor_decision_list.xlsx

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容