其实重新组织函数的方法,我们在没有学过重构的情况下,也是经常使用的。只不过我们没有统一规范,每次使用都要分析使用情况,都比较费劲。

例如有时候我们从大函数提炼一个小函数,我们并没有关心提炼的原则和什么时候适合提炼。只是胡乱吧方法提出来,让逻辑和以前一样就行,这样效率低,而且易出错。这次正好可以总结一下。

提炼函数 (Extract Method)

提炼函数是我们经常用到的重构手法。从一个大函数提炼小段代码。我们主要会遇到这样的问题。

函数命名

这个是很需要注意的,命名应该是函数做了什么而不是怎么做。

引用了变量

函数内部引用变量时候,我们需要分情况:

  • 如果没有引用变量,或者引用了全局变量,这很好办,直接提炼。
  • 如果引用的变量只是使用没有发生重新赋值。把变量作为函数参数传入。
  • 如果局部变量再赋值,只在提炼的代码段被使用,那么可以直接在目标函数进行修改。如果源函数还使用了这个变量,那就应该作为返回值。如果返回值不止一个,如果是 scala 好办,但是如果java,尽量吧两个值的操作放在两个方法中。

引用了源函数参数

  • 大部分情况和 引用了变量 一样操作。
  • 如果局部变量再赋值,而且源函数的参数被赋值,应该马上使用Remove Assignments to Parameters

内联函数

内联我没太懂啥意思。翻译的人也没有解释。这里感觉就是A函数 调用了B函数,A B 就是内联函数。

内联函数处理,主要就是判断函数是不是不具备多态性,逻辑也是比较固定,而且代码简单,而且和函数名一样清楚明了,就直接吧函数调用换成代码。

内联临时变量

如果临时变量只被调用一次,完全没必要。如果是某个临时变量某个函数的返回值,而且妨碍了其他重构手法,就应该想办法去掉。

Replace Temp with Query(以查询取代临时变量)

某个临时变量是一堆判断后的逻辑。这时候完全可以吧判断逻辑放到一个方法中。这样做的原因是临时变量会妨碍后续重构。

临时变量如果被多次赋值,怎么处理呢?我们通过 Split Temporary Variable 将它分解为多个变量。否则,临时变量申明为 final,确保只被赋值一次。然后后续赋值提炼到独立函数。 提炼的函数必须没有副作用,如果他修改了对象内容,也就是有副作用,需要进行 Separate Query From Modifler 。

这样重构你可能担心函数多次调用又性能问题,大部分情况是不会有的。性能问题主要发生在 IO、查数据库 等上面。

举个例子:

double getPrice(){
	int basePrice = _quantity * _itemPrice;
	double discountFactor;
	if (basePrice > 1000)discountFactor = 0.95;
	else discountFactor = 0.98;
	return basePrice * discountFactor;
}

第一步,吧 basePrice 申明为final,然后编写 basePrice 方法, 使用 Inline Temp 吧 basePrice 的引用点改成 basePrice() 方法。

第二步,类似的吧 discountFactor 提炼出来,最后的结果是

double getPrice(){
	return basePrice()* discountFactor();
}
private double discountFactor(){
	if (basePrice()> 1000)return 0.95;
	else return 0.98;
}
private double basePrice(){
	return _quantity * _itemPrice;
}

Introduce Explaining Variable(引入解释性变量)

这个貌似和上一个 是反过来的步骤,对于比较长的表达式,其中一部分的结果可以用易于解释的变量表达。 做法:申明final变量,赋值表达式。替换。 这个用得比较少,毕竟局部变量没法重用。

Split Temporary Variable(分解临时变量)

这个就是上面说的,如果被多次赋值,没法用 final 表示,怎么办。 循环遍历和收集变量是必须滴,其他的如果多次赋值,就承担了过多的意义,应该换另一个变量表示。

Remove Assignments to Parameters(移除对参数的赋值)

如果你对参数进行赋值,至少在 java 中是没有意义的。

Replace Method whth Method Object (以函数对象取代函数)

局部变量测存在使得函数提取很难,但是用函数对象替换以后就方便多了。因为局部变量都变成了全局变量。

Class Account
	int gamma (int inputVal,int quantity,int yearToDate){
	int importantValue1 = (inputVal * quantity)+ delta();
	int importantValue2 = (inputVal * yearToDate)+ 100;
	if ((yearToDate - importantValue1)> 100)
	importantValue2 -= 20;

	int importantValue3 = importantValue2 * 7;
	// and so on.
	return importantValue3 - 2 * importantValue1;
}

这里有很多局部变量,提取比较麻烦。怎么弄呢:

class Gamma...
	private final Account _account;
	private int inputVal;
	private int quantity;
	private int yearToDate;
	private int importantValue1;
	private int importantValue2;
	private int importantValue3;
	
Gamma (Account source,int inputValArg,int quantityArg,int yearToDateArg){
	_account = source;
	inputVal = inputValArg;
	quantity = quantityArg;
	yearToDate = yearToDateArg;
}

int compute (){
	importantValue1 = (inputVal * quantity)+ _account.delta();
	importantValue2 = (inputVal * yearToDate)+ 100;
	if ((yearToDate - importantValue1)> 100)
	importantValue2 -= 20;
	int importantValue3 = importantValue2 * 7;
	// and so on.
	return importantValue3 - 2 * importantValue1;
}


int gamma (int inputVal,int quantity,int yearToDate){
	return new Gamma(this,inputVal,quantity,yearToDate).compute();
}

这样一来,gamma 相关的都放在 Gamma 类,而 Gamma 的 compute 方法都没有局部变量了。这样就可以随意重构。

Substitute Algorithm (替换算法)

这个不必多说,考验人的水平,需要你了解函数,并且能够思考出简单的算法。