你的程序有个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。
针对每次赋值,创造一个独立、对应的临时变量。
double temp = 2 * (height + width); System.out.println(temp); temp = height * width; System.out.println(temp); >>> final double temp1 = 2 * (height + width); System.out.println(temp); final double temp2 = height * width; System.out.println(temp2);动机
临时变量的用途往往不只是被使用,被多次赋值的事情时有发生。循环是一个很经典的例子,特别是for循环,总会有“i”这么个变量存在。
当临时变量被两次赋值,那就意味着,你定位到程序的某个位置时,并不能确定此时临时变量到底存放的是哪个的值。这种情况下,把临时变量分割成两个更易于看懂代码。
另类的单一职责:每个变量只承担一个责任。同一个变量承担两种不同的责任,会令代码阅读者糊涂。
做法
在待分解临时变量的声明及其第一次被赋值处,修改其名称。如果这是个结果收集变量(比如i=i+表达式),那么不要分解它,因为它这个时候的职责还是一致的。
将新的临时变量声明为final。
以该临时变量的第二次复制动作为界,修改此前对该临时变量的所有引用点,让它们引用新的临时变量。
在第二次赋值处,重新声明原先那个临时变量。
编译、测试。
范例
下面范例中我要计算一个苏格兰布丁运动的距离。在起点处,静止的苏格兰布丁会受到一个初始力的作用而开始运动。一段时间后,第二个力作用于布丁,让它再次加速。根据牛顿第二定律,可以这样计算距离:
//注意例子中的acc,它被赋值两次 double primaryForce = 10; double mass = 5; int delay = 20; double secondaryForce = 20; double getDistanceTravelled(int time) { double result; // primaryForce:基本力 // mass:团;块;堆;大量;许多;(常指混乱的)一群,一堆 double acc = primaryForce / mass; int primaryTime = Math.min(time, delay); result = 0.5 * acc * primaryTime * primaryTime; int secondaryTime = time - delay; if(secondaryTime > 0) { double primaryVel = acc * delay; acc = (primaryForce + secondaryForce) / mass; result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime; } return result; }acc变量有两个责任:
保存第一个力造成的初始加速度
保存两个力共同造成的加速度
我们可以通过修改临时变量名称,增加新变量,同时把两个变量用final修饰来保证一次赋值。
double getDistanceTravelled(int time) { double result; // primaryForce:基本力 // mass:团;块;堆;大量;许多;(常指混乱的)一群,一堆 final double primaryAcc = primaryForce / mass; int primaryTime = Math.min(time, delay); result = 0.5 * primaryAcc * primaryTime * primaryTime; int secondaryTime = time - delay; if (secondaryTime > 0) { double primaryVel = primaryAcc * delay; final double secondaryAcc = (primaryForce + secondaryForce) / mass; result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime; } return result; }记住,我在这里一次性替换完成,但是实际应该一个一个地替换——先将第一次赋值的变量改名,然后用final修饰;然后把第二次赋值所在位置先只定义类型,然后确定没有问题时再用final修饰。
大家也许有好奇苏格兰布丁运动是什么,具体我也不清楚,反正就是一群东西混合在一起的,不好吃的东西。
方法的一个形参在内部重新赋值。
此时要以一个临时变量赋值取代对该参数的赋值。
int discount(int inputVal, int quantity, int yearToDate){ if (inputVal > 50) inputVal -= 2; } >>> int discount(int inputVal, int quantity, int yearToDate){ int result = inputVal; if (inputVal > 50) result -= 2; }这样重构是为了保证,参数从传递进去开始,一直没有改变,保持着最初的值。
动机
当你把一个变量作为参数传入方法,然后在方法内部重新对参数赋值,这就意味着你把参数的值改变了,转而引用到另一个对象,此时,无论对这个参数进行怎么样的改变,都不会影响本封装方法之外的内容。
void method(Order order){ //修改了方法之外的对象内的name字段 order.setName("笔记本"); //更改了引用对象 order = new Order(); //方法之外的对象内的name字段仍旧是"笔记本"没有改变 order.setName("笔"); }这里涉及到一个面试题:Java是值传递还是引用传递?老生常谈的一个问题,在此不多说,上网查一大堆。
在Java里,最好不要对参数赋值,这样可能会降低代码可读性,甚至造成误导。
做法
建立一个临时变量,把待处理的参数值赋予它。
以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为临时变量。
把对参数的赋值变成对临时变量的赋值。
编译、测试。
范例
int discount(int inputVal, int quantity, int yearToDate) { if (inputVal > 50) inputVal -= 2; if (quantity > 100) inputVal -= 1; if (yearToDate > 10000) inputVal -= 4; return inputVal; } >>> 用临时变量result替换inputVal进行赋值 int discount(int inputVal, int quantity, int yearToDate) { int result = inputVal; if (inputVal > 50) result -= 2; if (quantity > 100) result -= 1; if (yearToDate > 10000) result -= 4; return result; } >>> 倘若需要强制要求形参不能赋值,那么请给形参加上final修饰 int discount(final int inputVal, final int quantity, final int yearToDate) { int result = inputVal; if (inputVal > 50) result -= 2; if (quantity > 100) result -= 1; if (yearToDate > 10000) result -= 4; return result; }你有一个大型函数,其中对局部变量的使用使得无法采用提炼函数(6.1 Extract Method)。
将这个函数放进一个单独对象中,把局部变量作为对象内的字段,在这个对象中,对函数进行提炼。这往往会以引入解释性变量(6.5 Introduce Explaining Variable)为前提。
class Order{ double price(){ double primaryBasePrice; double secondaryBasePrice; double tertiaryBasePrice; // long computation; ... } } >>> class PriceCalculator{ double primaryBasePrice; double secondaryBasePrice; double tertiaryBasePrice; double compute(){ // long computation; //此时局部变量变成了字段,可以在类的内部任何位置使用,方便对其进行提炼函数操作 ... } } class Order{ double price(){ return new PriceCalculator().compute(); } }动机
程序中总会出现一大段代码无法提炼的情况——往往是因为局部变量的存在导致函数成块聚集,难以分解。
以查询取代临时变量(6.4 Replace Temp with Query)是一种方法,但别忘了它可能会影响性能的风险(所以不能滥用),并且有些时候它并不能完成对代码块的分割。
此时,就应该考虑把这一部分赖在一起的代码直接提成一个类,这里有种提炼类(7.3 Extract Class)的感觉。
这种重构方法会将所有局部变量变成函数对象的字段,然后就可以对这个新对象使用提炼函数(6.1 Extract Method),将大型函数拆解开来。
做法
新建一个新类,起一个合适的名字。
在新类中建立一个final字段,用以保存原先大型函数所在的对象——我们将这个字段称为“源对象”。同时,针对原函数的每个临时变量和每个参数,都在新类中建立一个对应的字段保存之。
说到这里我有个想法,事实上如果合适的话,可以把一个方法中的部分变量作为此方法所在类的字段。
在新类中建立一个构造函数,接收源对象及原函数的所有参数。
在新类中建立一个compute()函数。
将原函数的代码批量复制到compute()中。如果要调用源对象的任何函数,请通过源对象字段调用——这也正是为什么我们要建立一个final修饰的字段,用于存放原先大型函数所在的对象。
编译。
将旧函数的函数本体替换为,实例化新类对象且调用新对象中的compute()函数。
此时,由于所有局部变量成了字段,所以无论你在类中怎么分解大型函数,都不必传递任何参数。
范例
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; return importantValue3 - 2 * importantValue1; } }假设我们是一个正在重构别人代码的程序员,这段代码功能含义不明……
为了把这个函数变成一个函数对象,我首先需要声明一个新类。在此新类中我应该提供一个final字段用以保存源对象;对于函数的每一个参数和每一个临时变量,也以一个字段逐一保存。
// 不晓得用途,暂时就以方法名作为类名即可 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; yearToDate = yearToDateArg; }此时就可以把原先函数的所有内容移到compute()中了:
int compute() { importantValue1 = (inputVal * quantity) + delta(); importantValue2 = (inputVal * yearToDate) + 100; if((yearToDate-importantValue1) > 100) importantValue2 -= 20; importantValue3 = importantValue2 * 7; return importantValue3 - 2 * importantValue1; }最后,修改旧函数,让它把它的工作委托给刚完成的这个函数对象:
int gamma(int inputVal, int quantity,int yearToDate) { return new Gamma( this,inputVal,quantity,yearToDate) .compute(); }本项重构的好处是,我可以在新类中直接进行提炼函数,没有后顾之忧,唯一的缺点就是,多加了一个类。
本节最后,对这个方法进行一次提炼:
int compute() { importantValue1 = (inputVal * quantity) + delta(); importantValue2 = (inputVal * yearToDate) + 100; importantThing(); importantValue3 = importantValue2 * 7; return importantValue3 - 2 * importantValue1; } void importantThing(){ if((yearToDate-importantValue1) > 100) importantValue2 -= 20; }