11.3 常用的重构手法
11.3.1 提取子函数
提取子函数顾名思义就是将部分代码段从一个大的代码块提取到子函数中,从而使得原本很长的函数拆分为更多细小的函数。提取子函数又使我们必须为每个子函数取名,良好的命名又会给这个函数产生自解释的作用,也相当于文档的功能,这会使代码更加清晰、易于理解与维护。这是一个化繁为简的拆分过程,它的运用场景就是将很长的函数拆分为多个细小的函数,通过组合这些函数来完成旧代码的功能。
那么一个函数多长才是合适?函数本身的长度并不是问题,因为提取子函数的原因并不是单纯地因为长度,而是在一个函数之内做了太多界限明显的多个操作,它关键在于函数的功能是否内聚。如果提炼函数可以提升代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。
提取一个子函数的第一步就要明确这个函数的作用,因为我们的意图很明确,就是将代码段从一个更粗粒度的函数中提取出来,这个子函数的功能职责必须单一。考虑好这个函数的功能之后,我们就需要根据这个函数的功能来给它赋予一个恰当的名字,好的名字能让这个函数具有自解释性。
然后将要提炼的代码从源函数复制到新建的子函数中,如果新的子函数中引用了其他变量,那么需要通过参数传递或者通过其他函数获取。最后,直到新函数中没有错误,并且代码逻辑正确。此时,我们就可以用新的子函数替代旧代码中的代码段,然后进行测试即可。
例如,我们有一个printAllStudnetsInfo函数,它的功能是从数据库中读取所有学生的信息,然后对这些学生信息按照id进行升序排序,最后输出所有学生的信息。原来的代码如下:
// 输出数据库中所有学生的信息 private void printAllStudnetsInfo() { // 构建结果集 List<Student>allStudents = new ArrayList<Student>(); // 模拟从数据库获取所有学生信息 for (inti = 5; i>0 ;i--) { Student student = new Student(); student.id = i; student.name = "user - " + i; student.tel_no = String.valueOf(new Random().nextInt(200000)); student.cls_id = new Random().nextInt(5); // 添加到集合中 allStudents.add(student) ; } Collections.sort(allStudents, new Comparator<Student>() { @Override public intcompare(Student lhs, Student rhs) { return (int) (lhs.id - rhs.id); } }); for (Student student :allStudents) { System.out.println("### 学生信息 : " + student); } } |
上述的代码中printAllStudnetsInfo函数主要做了如下几步。
(1)从数据库读取所有学生信息(模拟的数据)。
(2)对数据进行排序。
(3)输出所有学生的信息。
从上述可以看到,printAllStudnetsInfo函数内的职责太多了,将各种函数包揽到其中,导致函数体过长。我们现在就要分解这个函数,根据它的功能,我们将它分解为3个函数,分别对应它的3步操作,即读取数据、排序、输出数据。首先我们新建一个loadAllStudentsFromDB函数用于从数据库中读取数据,实现代码如下:
private List<Student>loadAllStudentsFromDB() { List<Student>allStudents = new ArrayList<Student>(); for (inti = 5; i>0 ;i--) { Student student = new Student(); student.id = i; student.name = "user - " + i; student.tel_no = String.valueOf(new Random().nextInt(200000)); student.cls_id = new Random().nextInt(5); // 添加到集合中 allStudents.add(student) ; } return allStudents ; } |
版权声明:51Testing软件测试网获人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。