《重构:改善既有代码的设计》第六章摘要

    技术2026-01-08  15

    《重构:改善既有代码的设计》第六章摘要

    1.提炼函数(Extract Function)说明:把一个方法里的部分代码抽出为一个方法使用条件注意要点做法 2.内联函数(Inline Function)说明:是提炼函数的反操作.即把一个方法删除,把其方法内部的代码放在调用该方法的位置.使用场景注意要点做法 3.提炼变量(Extract Variable)说明:是把一个表达式的返回值赋值给一个变量使用场景注意要点做法 4.内联变量(Inline Variable)说明:提炼变量的逆操作.把被表达式赋值的变量,还原为使用表达式使用场景注意要点做法 5.改变函数声明(Change Function Declaration)说明: 修改原有函数使用场景注意要点做法(添加函数举例):更渐进适合复杂情况简单粗暴直接 6.封装变量(Encapsulate Variable)说明:把对象的属性private化,只提供函数的get/set方法来读取/设置属性的访问途径使用场景注意要点做法 7.变量改名(Rename Variable)说明:重命名变量(可能是对象或是String,Int等基础类型)使用场景注意要点做法 8. 引入参数对象(Introduce Parameter Object)说明:如果好几个属性经常一起作为某些方法的参数,可以考虑把这些参数封装为一个对象,专门作为这些方法的入参.使用场景注意要点做法 9.函数组合成类(Combine Functions into Class)说明:如果好几个函数,经常操作相同的数据对象(几个相同的变量,可以先使用8.引入参数对象先重构下)使用场景注意要点做法 10.函数组合成变换(Combine Functions into Transform)说明:类似9,只是深拷贝返回新的对象,不修改参数对象.使用场景注意要点做法 11.拆分阶段(Split Phase)说明:一段代码在同时处理两件不同的事,可以考虑把它拆分成各自独立的模块,因为这样到了需要修改的时候,就可以单独处理每个模块,而不必同时在脑子里考虑两个不同的模块使用场景注意要点做法

    1.提炼函数(Extract Function)

    说明:把一个方法里的部分代码抽出为一个方法

    使用条件

    过长的代码;

    容易被复用的代码片段;

    “将意图与实现分开”:如果你需要花时间浏览一段代码才能弄清它到底在干什么,那么就应该将其提炼到一个函数中,并根据它所做的事为其命名

    注意要点

    不用在意提炼的函数语句过小.

    命名要以它“做什么”来命名,而不是以它“怎样做”命名

    做法

    给新抽取的函数起名!

    复制原来代码到新方法体内

    调整形函数的参数,是否需要传入原方法函数里的一些参数

    将新方法在原方法内使用,删掉原方法内被实现的源码部分

    OR用IDEA抽取方法功能取代以上步骤

    测试重构是否正确.

    2.内联函数(Inline Function)

    说明:是提炼函数的反操作.即把一个方法删除,把其方法内部的代码放在调用该方法的位置.

    使用场景

    代码足够清晰,不需要使用提炼的函数;一般提炼函数代码量也不会很多的情况

    汇聚一些提炼函数在一起,进行内联重构,再重新对代码块重新提炼组合出不同的函数.

    注意要点

    被内敛的函数不能具有多态性:即一个父类的方法,如果子类继承了这个方法,就不适合内敛重构了

    做法

    确认是否可以内敛

    找出这个要被内敛函数的所有调用地方

    替换成源码(可以使用IDEA全局替换功能)

    测试重构是否正确.

    3.提炼变量(Extract Variable)

    说明:是把一个表达式的返回值赋值给一个变量

    使用场景

    表达式很长或者不好理解其结果用意,可以赋值给一个有意义的变量名

    表达式返回的值被反复使用,可以把表达式的返回值放入一个变量里避免表达式的重复运行

    注意要点

    根据表达式的被使用范围,考虑被赋值变量的作用域是否需要扩大.

    如果变量的作用域被扩大,要注意是否有其他的影响需要额外处理

    做法

    确认要提炼的表达式没有副作用。

    在合适的位置把表达式的值赋值给变量

    如果确认该变量是不可变的可以声明为一个不可修改的变量

    把原来用到表达式的地方都替换为新的变量

    测试重构是否正确.

    4.内联变量(Inline Variable)

    说明:提炼变量的逆操作.把被表达式赋值的变量,还原为使用表达式

    使用场景

    但有时候,这个名字并不比表达式本身更具表现力。

    还有些时候,变量可能会妨碍重构附近的代码

    脑补一种场景该区域可能要被提炼函数,又不想把程序上面的某个提炼变量作为提炼函数的参数.就把该提炼变量进行内联变量.

    注意要点

    表达式如果每次执行效率很慢或者消耗很大要酌情考虑.

    可以适当部分内联变量,看情况是否必须全部内联变量

    做法

    检查确认变量赋值语句的右侧表达式没有副作用。

    如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试。

    设置为不可变变量并测试的目的是为了确认后续改变量是否还被修改过.

    逐一找到使用该变量的地方,将其替换为直接使用赋值语句的右侧表达式。

    测试重构是否正确.

    5.改变函数声明(Change Function Declaration)

    说明: 修改原有函数

    重命名函数改变函数的参数改变参数类型增加/减少参数

    使用场景

    为了是方法更容易被复用

    书中例子:比如一个格式化电话号码的方法,入参是Person对象,还是一个String的电话号码更容易被复用呢? 使方法更适用以后的需求变化 借用上面格式化电话号码例子:如果格式化电话号码需要用到Person的一些属性,那么适用Person作为参数将会更合适.

    因为重构导致需要添加/删除方法参数

    把方法的参数,整合进已有对象参数的属性

    比如doSomeThing(Person,schoolName);可以把SchoolName作为Person的属性,那么就可以只传入Person对象就可以了.

    注意要点

    为了兼容其他项目使用该方法的情况(其他项目现在还无法修改到),可以对老方法加@Deprecated进行保留,等老方法不在被使用就可以移除了

    做法(添加函数举例):

    迁移式做法(增加参数为例)

    更渐进适合复杂情况

    1.如果有必要的话,先对函数体内部加以重构,使后面的提炼步骤易于开展。

    2.使用提炼函数将旧函数体提炼成一个新函数。删除旧函数源码,直接调用新函数.

    如果你打算沿用旧函数的名字,可以先给新函数起一个易于搜索的临时名字。

    3.如果提炼出的新函数需要新增参数,用前面的简单做法添加即可。也就是还要去修改旧函数里调用新函数的地方也要添加新参数

    测试下

    4.对之前调用用旧函数使用内联函数重构手法,也就是在调用旧函数的地方替换为就函数体当前的方法内代码,即对新方法的调用

    5.如果新函数使用了临时的名字,再次使用改变函数声明(124)将其改回原来的名字。 final.测试重构是否正确.

    简单做法(移除参数为例)

    简单粗暴直接

    1.如果想要移除一个参数,需要先确定函数体内没有使用该参数。

    2.修改函数,使其成为你期望的状态。

    3.找出所有使用旧的函数声明的地方,将它们改为使用新的函数声明。

    final.测试重构是否正确.

    6.封装变量(Encapsulate Variable)

    说明:把对象的属性private化,只提供函数的get/set方法来读取/设置属性的访问途径

    使用场景

    一个是public共有属性的对象,迁移修改.一般不适合java情况

    注意要点

    属性私有,get/set public就好

    本书作者习惯不用get前缀,直接属性名作为完整的get属性名()的方法名.比如getName();作者习惯使用name();

    对于java可以用lombok @Data注解就OK了;或者靠ide的生成get/set方法.

    做法

    private化属性,提供public的get/set方法

    把之前直接获取/设置属性的地方改为get/set方法

    测试重构是否正确.

    7.变量改名(Rename Variable)

    说明:重命名变量(可能是对象或是String,Int等基础类型)

    使用场景

    一个是public共有属性的对象,迁移修改.一般不适合java情况

    注意要点

    只在一行的lambda表达式中使用的变量,跟踪起来很容易,经常只用一个字母命名,因为变量的用途在这个上下文中很清晰

    在JavaScript这样的动态类型语言中,推荐把类型信息也放进名字里(于是变量名可能叫aCustomer)。

    建议使用,看名字就省的再去鼠标指向看ide提示是什么具体对象类型了.PS:如果回头这个类名被改变变量名没有更新会有误导性.

    做法

    对于javascript来说:如果变量被广泛使用,考虑运用6.封装变量(132)将其封装起来。

    IDEA->shift+f6 重构变量/方法/属性名字

    测试重构是否正确.

    8. 引入参数对象(Introduce Parameter Object)

    说明:如果好几个属性经常一起作为某些方法的参数,可以考虑把这些参数封装为一个对象,专门作为这些方法的入参.

    使用场景

    同说明

    注意要点

    把参数封装为对象,注意接收该参数对象的方法是否对该对象的属性产生修改,是否是业务想要的效果.

    除了简单的封装到一个对象里,也可以在对象内部提供一些基本的适合该对象的额外方法,拓展get/set添加格式化.或者定制一些其他的适合业务使用关于封装属性的常用处理方法.提供某几个属性的组合结果.比如对于一个Circle圆对象,我们有属性半径.也可以额外提供get直径,get周长,get面积等方法

    做法

    如果暂时还没有一个合适的数据结构,就创建一个数据对象类。

    使用5.改变函数声明(124)给原来的函数新增一个参数,类型是新建的数据结构类。

    调整所有调用者,传入新数据结构的适当实例。每修改一处,执行测试。

    测试重构是否正确.

    用新数据结构中的每项元素,逐一取代参数列表中与之对应的参数项,然后删除原来的参数。

    9.函数组合成类(Combine Functions into Class)

    说明:如果好几个函数,经常操作相同的数据对象(几个相同的变量,可以先使用8.引入参数对象先重构下)

    使用场景

    同说明

    注意要点

    把对于操作数据对象的函数放入数据对象的类中.进行组合.类似刚才举例Circle类:除了有获取半径R的方法,也可以把常用的获取周长,面积的方法放入Cricle累里,方便使用

    做法

    运用封装记录对多个函数共用的数据记录加以封装。

    如果多个函数共用的数据还未组织成记录结构,则先运用引入参数对象将其组织成记录。

    对于使用该记录结构的每个函数,运用搬移函数将其移入新类。

    如果函数调用时传入的参数已经是新类的成员,则从参数列表中去除之。

    用以处理该数据记录的逻辑可以用提炼函数提炼出来,并移入新类。

    测试重构是否正确.

    10.函数组合成变换(Combine Functions into Transform)

    说明:类似9,只是深拷贝返回新的对象,不修改参数对象.

    使用场景

    同说明

    注意要点

    与9.函数组合成类的区别是,9的具体对象的值只有本体一份.10.函数组合成变换虽然也是把函数放在一起(类似Util的感觉),但是每个函数都会先深度拷贝下参数对象,并返回这个新的参数对象,不会对原有参数对象造成影响.

    做法

    创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值。

    这一步通常需要对输入的记录做深复制(deep copy)。此时应该写个测试,确保变换不会修改原来的记录。

    挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录中。修改客户端代码,令其使用这个新字段。

    如果计算逻辑比较复杂,先用提炼函数提炼之。

    针对其他相关的计算逻辑,重复上述步骤。

    测试重构是否正确.

    11.拆分阶段(Split Phase)

    说明:一段代码在同时处理两件不同的事,可以考虑把它拆分成各自独立的模块,因为这样到了需要修改的时候,就可以单独处理每个模块,而不必同时在脑子里考虑两个不同的模块

    使用场景

    如果一块代码中出现了上下几段,各自使用不同的一组数据和函数,这就是最明显的线索。将这些代码片段拆分成各自独立的模块,能更明确地标示出它们之间的差异。

    注意要点

    与9.函数组合成类的区别是,9的具体对象的值只有本体一份.10.函数组合成变换虽然也是把函数放在一起(类似Util的感觉),但是每个函数都会先深度拷贝下参数对象,并返回这个新的参数对象,不会对原有参数对象造成影响.

    做法

    将第二阶段的代码提炼成独立的函数。

    引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中。

    如果计算逻辑比较复杂,先用提炼函数提炼之。

    逐一检查提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用到,就将其移入中转数据结构。每次搬移之后都要执行测试。

    对第一阶段的代码运用提炼函数,让提炼出的函数返回中转数据结构。

    测试重构是否正确.

    Processed: 0.017, SQL: 9