重构《Split Loop(分解循环)》

WHAT

在循环中,一次循环做了两件事情,将循环分解,重复这个循环,每次只做一件事情

 

潜在问题:性能问题,如果遇到性能问题,先让代码清晰可读,让你更快找到性能优化点,再做优化

 

本次重构涉及的基本重构较多,主要有:

  1. Split Loop(分解循环)
  2. Extract Method(提炼方法)
  3. Inline Temp(内联临时变量)
  4. Replace Temp With Query(用查询函数代替临时变量)
  5. Rename Temp(重命名临时变量)
  6. Split Temporary Variable(分解临时变量)

HOW

重构前的代码清单

Student类是一个哑对象,表示一个学生,有年龄(age)和成绩(grade)两个属性

 

package split.loop;

public class Student {

	/**
	 * 年龄
	 */
	private int age;
	/**
	 * 成绩
	 */
	private int grade;
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getGrade() {
		return grade;
	}

	public void setGrade(int grade) {
		this.grade = grade;
	}
}
下面方法 printValues(),用于计算学生们的平均年龄和总成绩
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = 0; // 平均年龄
		int totalGrade = 0; // 总成绩

		for (int i = 0; i < students.length; i++) {
			averageAge += students[i].getAge();
			totalGrade += students[i].getGrade();
		}
		averageAge = averageAge / students.length;

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

}
 

重构步骤

  • (手动)拷贝for循环语句,并除掉各自不相干部分代码,重构后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = 0; // 平均年龄
		int totalGrade = 0; // 总成绩

		for (int i = 0; i < students.length; i++) {
			averageAge += students[i].getAge();
		}

		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		averageAge = averageAge / students.length;

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

}
  • (手动)重新组织语句,让语义相近(for循环中使用到的局部变量)的语句放在一组,为其他重构做好准备,重构后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			averageAge += students[i].getAge();
		}
		averageAge = averageAge / students.length;
		
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

}

延伸重构

  • 本次重构,到此本应该结束,但是,我们还可以继续重构,让代码更优雅。我们观察到,上述代码清单中,printValues方法仍旧很长,到处充满了局部解释性变量(令人疑惑的东西),可以使用Replace Temp with Query(用查询函数代替临时变量,其实这个重构手法应用了两个基本的重构手法:Extract Method(提炼方法)Inline Temp(内联临时变量)),我们可以一步步来操作
    • 首先处理临时变量:averageAge,对上一组方法 Extract Method(提炼方法),使用快捷键(Alt + Shift +M)或者上下文菜单(Refactor -> Extract Method...),这里 averageAge ,在for循环前初始化,并在for循环中赋值,最后在for循环结束后,再次赋值。因此我们选中for循环以及前后语句,一起提炼,并给方法取个好名字( averageAge  ,这个方法是干什么的),提炼后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = averageAge();
		
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double averageAge = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			averageAge += students[i].getAge();
		}
		averageAge = averageAge / students.length;
		return averageAge;
	}

}
  •  
    •  接下来,处理新提炼出来的方法averageAge,按照惯例,我们把方法的返回值命名为result,使用快捷键(Alt + Shit + R)或者上下文菜单(Refactor > Rename ...),改名之后,直接回车即可,代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = averageAge();
		
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		result = result / students.length;
		return result;
	}

}
  •  
    • 在往下,这里又涉及到一个问题,就是对局部变量赋值了多次,这时,需要应用Split Temporary Variable(手动操作,在第二次赋值的地方,重新声明变量,并更新接下来的引用),重构后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = averageAge();
		
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		double result1 = result / students.length;
		return result1;
	}

}
  •  
    • 这时,我们注意到,result1变量只被赋值一次,我们将它内联,应用Inline temp (内联临时变量 ),选中变量,使用快捷键(Alt +Shift + I)或者上下文菜单(Refactor > Inline ...) ,重构后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		double averageAge = averageAge();
		
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge);
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		return result / students.length;
	}

}  
  •  
    •  这时,我们回到调用处,发现printValues方法中,averageAge 变量只被赋值一次,我们也把它Inline掉,重构后的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		int totalGrade = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			totalGrade += students[i].getGrade();
		}

		System.out.println(averageAge());
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		return result / students.length;
	}

}
  •  接下来,同样方法处理这个变量:totalGrade
    • 首先,提炼方法,并重名返回结果变量名
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		int totalGrade = totalGrade();

		System.out.println(averageAge());
		System.out.println(totalGrade);
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		return result / students.length;
	}

	private int totalGrade() {
		int result = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			result += students[i].getGrade();
		}
		return result;
	}

}
  •  
    • 在调用处,inline临时变量totalGrade,重构后的的代码清单
package split.loop;

public class SomeClass {

	private Student[] students;

	void printValues() {
		System.out.println(averageAge());
		System.out.println(totalGrade());
	}

	private double averageAge() {
		double result = 0; // 平均年龄
		for (int i = 0; i < students.length; i++) {
			result += students[i].getAge();
		}
		return result / students.length;
	}

	private int totalGrade() {
		int result = 0; // 总成绩
		for (int i = 0; i < students.length; i++) {
			result += students[i].getGrade();
		}
		return result;
	}

}
  •   这时,代码已经相当清晰了,我们不需要看方法注释,以及方法内部实现,就可以知道printValues这个方法做了哪些事情,这就是重构的魅力,小步慢跑,一步一前进,但是,最最重要的前提是:我们在重构之前,必须有一个运行良好且速度较快的单元测试,让我们在测试、编码、重构不断地切换角色,更换帽子

总结

本重构虽然很简单,一些很小的改动,能够使代码可读性更强,更易维护,希望本实践对你有益,希望大家和我一起动手做重构,也希望你提出宝贵意见或反馈

你可能感兴趣的:(重构,分解循环)