消除过多的if-else
原文章:6 个实例详解如何把 if-else 代码重构成高质量代码 (opens new window) 写 if-else 不外乎两种场景:异常逻辑处理和不同状态处理。
//举例一:异常逻辑处理例子
Object obj = getObj();
if (obj != null) {
//do something
}else{
//do something
}
//举例二:状态处理例子
Object obj = getObj();
if (obj.getType == 1) {
//do something
}else if (obj.getType == 2) {
//do something
}else{
//do something
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
重构 if-else 时,心中无时无刻把握一个原则:尽可能地维持正常流程代码在最外层。
实现的手段有:减少嵌套、移除临时变量、条件取反判断、合并条件表达式等。
# 异常逻辑处理型重构方法实例一:
重构前:
double disablityAmount(){
if(_seniority < 2)
return 0;
if(_monthsDisabled > 12)
return 0;
if(_isPartTime)
return 0;
//do somethig
}
2
3
4
5
6
7
8
9
10
11
12
13
重构后:
double disablityAmount(){
if(_seniority < 2 || _monthsDisabled > 12 || _isPartTime)
return 0;
//do somethig
}
2
3
4
5
6
这里的重构手法叫合并条件表达式:如果有一系列条件测试都得到相同结果,将这些结果测试合并为一个条件表达式。
# 异常逻辑处理型重构方法实例二:
重构前:
double getPayAmount(){
double result;
if(_isDead) {
result = deadAmount();
}else{
if(_isSeparated){
result = separatedAmount();
}
else{
if(_isRetired){
result = retiredAmount();
else{
result = normalPayAmount();
}
}
}
return result;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
重构后:
double getPayAmount(){
if(_isDead)
return deadAmount();
if(_isSeparated)
return separatedAmount();
if(_isRetired)
return retiredAmount();
return normalPayAmount();
}
2
3
4
5
6
7
8
9
10
11
12
重构前后最大的区别是减少 if-else 嵌套。
可以看到,最初的版本 if-else 最深的嵌套有三层,看上去逻辑分支非常多,进到里面基本都要被绕晕。其实,仔细想想嵌套内的 if-else 和最外层并没有关联性的,完全可以提取最顶层。
改为平行关系,而非包含关系,if-else 数量没有变化,但是逻辑清晰明了,一目了然。 另一个重构点是废除了 result 临时变量,直接 return 返回。好处也显而易见直接结束流程,缩短异常分支流程。原来的做法先赋值给 result 最后统一 return,那么对于最后 return 的值到底是那个函数返回的结果不明确,增加了一层理解难度。
总结重构的要点:如果 if-else 嵌套没有关联性,直接提取到第一层,一定要避免逻辑嵌套太深。尽量减少临时变量改用 return 直接返回。
# 异常逻辑处理型重构方法实例三:
重构前:
public double getAdjustedCapital(){
double result = 0.0;
if(_capital > 0.0 ){
if(_intRate > 0 && _duration >0){
resutl = (_income / _duration) *ADJ_FACTOR;
}
}
return result;
}
2
3
4
5
6
7
8
9
第一步,运用第一招:减少嵌套和移除临时变量:
public double getAdjustedCapital(){
if(_capital <= 0.0 ){
return 0.0;
}
if(_intRate > 0 && _duration >0){
return (_income / _duration) *ADJ_FACTOR;
}
return 0.0;
}
2
3
4
5
6
7
8
9
这样重构后,还不够,因为主要的语句(_income / _duration) *ADJ_FACTOR;在 if 内部,并非在最外层,根据优化原则(尽可能地维持正常流程代码在最外层),可以再继续重构:
public double getAdjustedCapital(){
if(_capital <= 0.0 ){
return 0.0;
}
if(_intRate <= 0 || _duration <= 0){
return 0.0;
}
return (_income / _duration) *ADJ_FACTOR;
}
2
3
4
5
6
7
8
9
10
这才是好的代码风格,逻辑清晰,一目了然,没有 if-else 嵌套难以理解的流程。
这里用到的重构方法是:将条件反转使异常情况先退出,让正常流程维持在主干流程。
# 异常逻辑处理型重构方法实例四:
/* 查找年龄大于18岁且为男性的学生列表 */
public ArrayList<Student> getStudents(int uid){
ArrayList<Student> result = new ArrayList<Student>();
Student stu = getStudentByUid(uid);
if (stu != null) {
Teacher teacher = stu.getTeacher();
if(teacher != null){
ArrayList<Student> students = teacher.getStudents();
if(students != null){
for(Student student : students){
if(student.getAge() > = 18 && student.getGender() == MALE){
result.add(student);
}
}
}else {
logger.error("获取学生列表失败");
}
}else {
logger.error("获取老师信息失败");
}
} else {
logger.error("获取学生信息失败");
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
典型的"箭头型"代码,最大的问题是嵌套过深,解决方法是异常条件先退出,保持主干流程是核心流程:
重构后:
/* 查找年龄大于18岁且为男性的学生列表 */
public ArrayList<Student> getStudents(int uid){
ArrayList<Student> result = new ArrayList<Student>();
Student stu = getStudentByUid(uid);
if (stu == null) {
logger.error("获取学生信息失败");
return result;
}
Teacher teacher = stu.getTeacher();
if(teacher == null){
logger.error("获取老师信息失败");
return result;
}
ArrayList<Student> students = teacher.getStudents();
if(students == null){
logger.error("获取学生列表失败");
return result;
}
for(Student student : students){
if(student.getAge() > 18 && student.getGender() == MALE){
result.add(student);
}
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 状态处理型重构方法实例一
重构前:
double getPayAmount(){
Object obj = getObj();
double money = 0;
if (obj.getType == 1) {
ObjectA objA = obj.getObjectA();
money = objA.getMoney()*obj.getNormalMoneryA();
}
else if (obj.getType == 2) {
ObjectB objB = obj.getObjectB();
money = objB.getMoney()*obj.getNormalMoneryB()+1000;
}
}
2
3
4
5
6
7
8
9
10
11
12
重构后:
double getPayAmount(){
Object obj = getObj();
if (obj.getType == 1) {
return getType1Money(obj);
}
else if (obj.getType == 2) {
return getType2Money(obj);
}
}
double getType1Money(Object obj){
ObjectA objA = obj.getObjectA();
return objA.getMoney()*obj.getNormalMoneryA();
}
double getType2Money(Object obj){
ObjectB objB = obj.getObjectB();
return objB.getMoney()*obj.getNormalMoneryB()+1000;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这里使用的重构方法是:把 if-else 内的代码都封装成一个公共函数。函数的好处是屏蔽内部实现,缩短 if-else 分支的代码。代码结构和逻辑上清晰,能一下看出来每一个条件内做的功能。
# 状态处理型重构方法实例二
针对状态处理的代码,一种优雅的做法是用多态取代条件表达式(《重构》推荐做法)。
你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。将这个表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
重构前:
double getSpeed(){
switch(_type){
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed)?0:getBaseSpeed(_voltage);
}
}
2
3
4
5
6
7
8
9
10
重构后:
class Bird{
abstract double getSpeed();
}
class European extends Bird{
double getSpeed(){
return getBaseSpeed();
}
}
class African extends Bird{
double getSpeed(){
return getBaseSpeed()-getLoadFactor()*_numberOfCoconuts;
}
}
class NorwegianBlue extends Bird{
double getSpeed(){
return (_isNailed)?0:getBaseSpeed(_voltage);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到,使用多态后直接没有了 if-else,但使用多态对原来代码修改过大,需要一番功夫才行。最好在设计之初就使用多态方式。