经过影片出租店的完整演练,对这样一个如麻雀般完整而小的遗留项目展开重构,使得我们对重构建立了一个整体的印象,也有利于我们将前面介绍的各种重构知识串联起来,现在,有必要对整个重构做一次复盘。为了帮助大家更好地理解重构,我认为可以从道、法、术、器这四个层次做一番总结。道是万物变迁循环中亘古不变的规律,是自然环境、事物的自然规律和发展方向。道以明向,它决定了重构的方向,也决定了在软件研发过程中,什么样的活动才可以称之为是重构。我在第3章给出过重构的定义:在不增加任何新功能的情况下,通过运用一系列可控的改进手段对既有代码做出优化,使其变得更容易理解,更容易复用,更容易扩展。这一定义圈定了重构的范围,主要针对代码层面,也可以具体称之为“代码重构”。自有重构以来,也陆续有人创造更多的概念,如数据库重构、网站重构以及架构重构。它们与代码重构虽然不同,却遵循相同的道,即在不增加任何新功能且不破坏现有功能的前提下,对目标进行优化和改进。违反了这个“道”,就不能说是重构,又或者说没有达成目标的重构。例如:增加了新功能的任何操作,都不能称之为重构;破坏了现有功能的重构,就不能称之为是合格的重构;如果重构之后,目标没有得到任何优化和改善,则不能称之为是好的重构。对代码如此,对数据库、网站和架构,都是如此,只是优化和改进的目标不同罢了。本专栏讨论的重构内容都属于代码重构,因此它的优化和改进目标就是让代码变得“更容易理解,更容易复用,更容易扩展”,即提升代码的可读性、可复用性与可扩展性。法是在探求“道”的过程中经过实践思考、归纳总结出的规则体系和方法原则。法以立本,是实现重构目标的规则和方法。Martin Fowler在《重构》一书中总结的重构手法(不含重构手法的具体操作步骤)是“法”的一部分;我在第4章总结的重构三要素,也可以认为是“法”的一部分;如果重构的代码使用了如Java、C#这样的面向对象语言,则基本的面向对象设计原则和设计模式,也可以称之为是“法”的一部分;由于重构需要单元测试做保护,为单元测试规定的FIRST原则也可以认为是“法”的一部分。通过前面各章对案例实践的讲解,可以看出这些“法”是正确进行重构的基础,也是对具体操作的指导。例如,第5章和第6章先后介绍的迪米特法则与信息专家模式,很好地指导了类的职责分配,从而决定采用提取方法和移动方法等重构手法;又例如在第10章提到的“关注点分离”原则,它指导开发人员学会分辨关注点,将其分离为不同的职责,并采用提取方法、提取委派等重构手法;再例如第23章提到的“差异式编程”,它决定了继承的设计思路,指导我们在重构时,需要将和PriceCode有关的职责分离到单独的继承体系。至于具体该如何运用这些“法”,就属于“术”的层次了。术是在规则体系指导下的具体操作技术,只要“道、法”不变,“术”可千变万化。术以立策,如果不通过“术”将抽象的方法和法则转化为实际操作的过程,代码重构就无法落地。除了具体的重构步骤之外,第22章提出的“深度优先”与“广度优先”的结合策略,多种重构手法结合的策略,如内联与提取成员之间的配合,这些内容都是在“法”的指导下实施的具体方法,属于“术”的范围。器是指有形的物质或有形的工具。器以成事,是实现术和法的物质基础。重构的器主要为IDE(包括与重构相关的插件),也包括重构需要用到的各种框架,如JUnit、AssertJ、Mokito,还包括和代码质量有关的工具,如SonarQube。“器”同样是变化的,且它的变化更其迅速,随着技术的不断进步,它甚至会不断“吞噬”原本属于术的范围,本该由开发人员具体操作和执行的事情,慢慢被“器”所取代。Martin Fowler在刚刚出版《重构》一书之时,只有一款称为“Refactoring Browser(重构浏览器)”的工具,可以对Smalltalk程序实施一些简单的重构。当时,Martin Fowler总结的许多重构手法(属于“术”的层次)都需要开发人员手动完成,以至于他在书中给出了各种重构手法的具体做法。以最常见的“提取方法”重构为例,书中给出的做法为(参见熊节翻译的《重构》第一版第111页,本文有删减,书中将method翻译为函数):- 仔细检查提炼出的代码,看看其中是否引用了“作用域限于源函数”的变量
- 将被提炼代码段中需要读取的局部变量,当做参数传给目标函数
编译,测试
提取方法属于“法”的范围,而具体执行提取方法的以上步骤则属于“术”的范围。之所以一个简单的提取方法都需要定义这么多繁琐的步骤,就是为了执行安全的重构。如果没有工具帮助,就需要开发人员严格地按照这些步骤执行。可是,在本专栏演示“提取方法”重构时,哪有这么麻烦?这是因为这些有规律可循的重构步骤已经被诸如Intellij IDEA提供的重构工具所替代,它帮我们自动完成了对新函数的创建,对提炼代码的复制,对提炼代码段中各种变量的检查,对提炼代码的引用等。如果说这些重构工具只是“器”对“术”的“侵略”,那么AI大模型的发展则进一步开疆拓土,不仅侵占了“术”的领地,还毫不客气地开始对“法”领地的侵略。由于AI工具或AI智能体(如Cursor、Trae、GitHub Copilot等)拥有了LLM作为“超级大脑”,因而它具有了甚至比人类更强的学习能力。它不仅学会了“术”的知识,还进一步学会了“法”的知识。在后续文章就可以看到,只要在提示词中明确要求AI工具按照迪米特法则或信息专家模式对遗留代码进行重构,它就知道该选择提取方法和移动方法等重构手法,智能地完成代码的重构。由于AI大模型为重构工具注入了“智力”,使得过去的自动重构升级为智能重构,也让重构的操作体验,从过去的菜单操作、鼠标操作与快捷键操作(即所谓的GUI),变革为以自然语言为载体的聊天式操作(即所谓的LUI)。AI工具引发的是软件研发整个生态全方位的变革,代码重构必然也会受到这一轮冲击波的影响。但以目前的AI智能水平,它还不能完全替代一名重构专家,特别在面对一个稍显复杂的遗留代码时,假如操作人员只是向AI工具发出一个简单的指示:“请重构这段代码!”那么,它就只能完成一些基本的重构操作,如重命名以提升代码可读性,提取方法以明确清晰的职责。要执行更加复杂的重构,还需要开发人员向AI工具发出更加明确的指示,才能更漂亮地完成重构。如果开发人员不具备重构的知识,尤其不具备“道”和“法”的知识,就可能难以给出清晰的重构方向与操作指引,此时,AI工具就可能变成一个不断制造麻烦或拒不配合的团队成员,加上大模型还存在诸如AI幻觉之类的问题,有时也会做出让人啼笑皆非的重构操作,这时候,就需要开发人员运用自己扎实的重构功底,为整个过程指引方向,保驾护航。在接下来的章节,我仍然以影片出租店为例,分别采用IntelliJ IDEA的AI重构插件TONGYI Lingma与基于Claude-3.5-sonnet模型的Cursor(或字节新推出的基于Claude 3.5模型的Trae)演示AI对重构过程的辅助与赋能。如此对比,既可以看出AI工具对重构效率的提升,又能发现目前的AI工具还存在的不足;同时,还可以充分认识到在AI时代,操作人员具备的重构能力依旧是不可或缺的。
以下是重构练功房部分章节
以下是重构练功房专栏简介