摘要:让我们再回到重构的基本概念,思考我们需要怎样的重构辅助服务。
一、背景
代码重构是每一位开发者最熟悉不过的字眼,其出现通常伴随着开发过程。在程序开发、迭代与演进的漫漫长路中,某次不经意的修改就可能破坏程序原有的设计与结构,造成代码结构的流失,而这种流失是具有累积性的,若未及时发现与重构,程序就会逐渐腐烂甚至变质,形成巨大的历史债务。其实重构就好比收拾房间,如果我们天天打扫,那么每天花3分钟就能打扫干净,可如果一个月不打扫,你想想需要多久才能打扫完。
既然代码重构在开发过程中这么重要,怎么能没有相应的服务来支撑它呢?我们能不能开发出相应的“扫帚”辅助我们每天打扫房间?亦或是“扫地机器人”自动的帮我们打扫一个月未收拾的房间?
带着上述疑问,让我们再回到重构的基本概念,思考我们需要怎样的重构辅助服务。
1. 什么是重构
如此书中所说,所谓重构(Refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。这里的重构有两层含义,一个名词含义,一个动词含义:
重构(名词):对软件内部结构的一种调整, 目的是在不改变软件可观察行为的前提下,提高其可理解性, 降低其修改成本。
重构(动词):使用一系列重构手法, 在不改变软件可观察行为的前提下,调整其结构。
至此,我们应该明白,一款好的重构辅助服务应该至少兼具不变与变两个特征:不改变软件可观测行为;优化代码结构,降低修改成本,提高可理解性。
2. 什么时候重构(何时应做怎样的重构)
就重构时机问题,业界也有比较激烈的讨论,有人认为重构应该随时随地地进行,不应该为了重构而重构,就比如我在添加新功能时、修补错误时、或者复审代码时都可以进行重构,我们暂且称之为“开发时重构”,也有人认为“添加新功能”和“重构”是两顶帽子,在添加新功能时,就不应该修改既有代码,只管添加新功能,而在重构时,就不能添加功能,只管改进程序结构,一次只做一件事,我们暂且称之为“维护式重构”。我个人认为这两种说法并不矛盾,真正好的重构应该是两者的有机结合。
如上图所示,红色范围是我认为比较好的重构实践。
我认为无论在做新功能开发时还是在做老版本维护时,都适合做代码重构,只是适合的重构粒度不同而已。对于开发时重构,比较适合做个人级小范围的微重构,这种重构往往影响范围小,且较为简单,不会对开发增加工作难度,例如“重命名”、“函数提取”等原子重构;对于维护时重构,比较适合做架构级大范围的复杂重构,这种重构往往是为了解决项目代码中遗留的技术债务,且通常和代码坏味道的消除结合在一起,例如依恋情结(Feature Envy)、数据泥团(Data Clumps)等,而这些坏味道的重构往往由一系列的重构原子操作组合而成。当然,最好将重构工作尽可能多地做在开发阶段,尽量减少新增代码对已有设计与架构的破坏。
看到这里,我们是否有些似曾相识,这不就对应了上文中提到的“扫帚”以及“扫地机器人”在实际重构工作中的应用?
3. 为什么需要代码智能重构服务
使用过现代IDE开发代码的同学们应该都知道,以IntelliJ IDEA为代表的很多IDE多少都自带一些重构功能,但目前为止,这些重构都是例如“重命名”、“函数提取”等原子性重构,只对重构过程提供了部分支持,绝大部分的代码、架构坏味道重构工作仍然得靠手工完成,就好比你需要打扫一个月未打扫的房子,但手中只有一把扫帚一样。Kent Beck 说过:“手工重构仍然是很耗时的工作。正是这个简单的事实造成了很多程序愿不愿意进行重构,尽管他们知道自己应该重构,但毕竟重构的成本太大了。如果能够把重构变的像调整代码格式那么简单,程序员自然也会乐意像整理代码格式那样整理系统的设计。而这样的整理对代码的可读性、可复用性和可理解性,都能带来深远的正面影响。”正因如此,一款智能的、可以帮助开发者发现代码、架构中的坏味道并且引导开发者完成代码重构的服务尤为重要。
二、Devops全流程下的重构服务需求
对于持续交付过程中的代码开发与发布环节,如上图虚线框所示,几乎每一步都与代码质量的看护或代码重构相关。
开发环节可大致分为两种类型:用于添加新功能的“增量式开发”以及维护老版本的“存量式维护”。对于“增量式开发”而言,需要重构服务的编码规范重构和原子重构能力在开发过程中帮助开发人员提升代码质量与开发效率;对于“存量式整改”而言,则需要重构服务具备架构、代码坏味道的检查、重构机会点挖掘以及重构方案的推荐能力,并将相应的重构方案拆解为彼此独立且正交的原子重构能力,最终作为一个原子重构序列推荐给开发人员,在与人机交互中一步步完成重构应用。
在持续发布环节,MR门禁中的代码静态检测需要触发对坏味道代码的识别,作为一种增量式检测,主要识别由新增代码引入的代码及架构坏味道, 并自动生成一次重构任务,引导开发人员进行一次重构,避免代码中技术债堆积。
在生产运维环节,需要做定期的架构看护,将相应度量结果通过架构可视化图形界面展示出来,当架构某个模块腐化到一个阈值时自动报警,提示版本负责人做相应重构。
三、如何辅助开发人员实现代码分层重构
1. 都有哪些层级的重构
个人认为重构大致可以分为4个层级,这些层级之间的关系可以从上图看出,并非是自底向上依次包含的关系,它们之间会有些重叠也有些不同。单纯从重构本身来讲,越上层级的重构操作非确定性越强,更偏业务性,而越下层级的重构操作确定性越强,更偏技术性。从智能重构服务的角度来讲,越上层越偏检测能力,越下层越偏纯重构能力。下面我们由下至上依次来看看不同层级重构操作的特点与异同。
原子重构能力:上文中也有提到原子重构,原子重构到底有哪些呢?顾名思义,即不可再分的重构操作,例如重命名、移动、删除等重构操作,这些重构操作是完全确定性的,例如我想要抽取一段代码形成一个新方法,是否可抽,可以抽成什么样都是完全确定的。这些重构操作因为其不可再分性位于重构最底层。
编码规范重构:有些同学可能会对这个层级的重构比较陌生,其实它就是基于规范和规则的重构,通常包含了我们所说的微重构,例如对违反了命名风格的标识符进行重命名重构,亦或是对无用代码进行删除的安全删除重构等等,并且这层的重构操作完全可以通过调用下层的1个原子重构来实现。正因为这些重构操作基于规则,其重构结果也是相对确定的。
代码坏味道重构:代码坏味道也包括了架构坏味道,例如Feature Envy、God Class等类型,这些层级重构的目的是为了消除相应的代码坏味道,所以相对来说更复杂,一次重构的完成往往要调用下层多个原子重构操作,例如对God Class进行重构,需要调用至少一次Extract Class原子重构。同时这层重构的非确定性也更高,对一种代码坏味道的重构消除往往可以通过多种手段。
设计模式坏味道重构:这层重构应该属于金字塔顶端的重构,因为它涉及范围太广,更偏向于对业务的理解与预测,从具象到抽象,显得有些虚无缥缈。设计模式有7中坏味道和11中原则,目前无论学术界还是工业界对于这类问题检测、重构相关的研究都还不多。
2. 智能重构服务在不同层级下的应用
结合Devops下的重构服务需求与上图中的四层重构,我们基本可以看出智能重构服务不同层级的重构能力主要运用在开发工作流中的哪个阶段。对于原子重构来说,因为其确定性以及业务无关性,最适合作为插件集成在IDE上,由开发者做增量开发时调用;对于编码规范重构来说,适合同原子重构一起集成进IDE插件,在开发过程中自动且快速的识别到违反特定规则的代码,并辅助开发者重构;对于代码坏味道重构来说,适合在持续发布环节的门禁阶段拦截问题并引导开发者回到IDE进行代码智能重构;对于设计模式坏味道重构来说,因为其涉及业务理解与预测,更适合放进生产运维环节,结合版本演进与迭代历史,给出架构腐化的预测以及相应重构方案的推荐。
3. 搭积木式的重构应用
既然上层重构操作都可以转化为最底层的原子重构操作,那如何表达这种转化呢?表格中列举了几种常见的重构原子操作,每种重构原子操作都可以用一个函数来表达。例如Move Method,函数名为Move Method,我们用三个参数来明确它的行为:Source(所属类型)、Target(目标类型)以及(需要移动的方法),有了这些信息就可以明确一次Move Method原子重构。对于任何一个复杂的重构来说,都可以表示成如下形式的原子重构序列,即一系列原子重构的组合。
就像搭积木一样,任何上层重构都可以通过搭积木的方式组合底层原子重构来实现。
四、重构服务设计原则
重构服务的设计亦如其它很多开发服务一样,最终目标都是提升用户开发效率与代码质量,如果二者皆得不到保证,那这款服务将被永远丢进垃圾堆里。从我们在华为内部的实践经验,总结了以下几点原则:
充分的用户交互:经典的重构工作流程为“小步前进,随时可用,随时可停,随时回退”,小步修改意味着每一步出错的可能性大大减小,要遵循这个流程,就离不开工具和用户频繁的交互,要引导用户一步步的完成复杂的代码重构,每个过程都要做到随时可用,随时可停,随时可退。
友好的重构工作界面:代码重构有点类似代码自动修复,但比代码自动修复涉及范围更广,如何让用户更好的表达重构意图、并且一目了然的看到重构对原代码架构的影响非常重要,这一部分有很多可以创新的地方。
个性化用户配置:上层级的重构往往可以有不同的执行路径,根据代码工程不同、业务场景不同,同一种坏味道重构的实现方式也可能不同,要以用户为中心,根据业务不同、用户不同给出个性化、定制化的重构解决方案,这一部分刚好是AI擅长的领域。
Devops服务深度集成:任何一款开发服务如果离开Devops服务就会成为一个孤立的散点,无法在开发过程中被顺畅的使用,如果我们能把重构服务集成进IDE、代码检视环节、代码入库环节以及验证发布环节,就可以让重构工具Build In在可信开发过程中,让重构服务触手可及。
高效:特别是在IDE上的重构分析服务,如果分析过程需要花费太长时间,程序员很可能就不会使用这些重构服务,他们宁可手工重构。
网友评论