文章目录

为什么我们写的代码都是 if-else
在今年秋招时,就遇到这样一道笔试题,问:
怎么优化多层嵌套的if-else
结构,为什么要去优化?
其实,自己第一能够想到的就是用switch来优化,因为相对于多个 else-if,switch的逻辑结构更加清晰。简单的逻辑可以使用三元运算符,如果是多层嵌套的 if-else 可以试着进行拆分。当时大概就是这么简短的回答的,现做一个总结。
写 if-else 不外乎两种场景: 异常逻辑处理 和 不同状态处理
两者最主要的区别是:
- 异常逻辑处理说明只有一个分支是正常流程,另一个是异常情况的处理
- 而不同状态处理中 所有分支都是正常流程(类比switch)
异常逻辑处理举例:

不同状态处理举例:

第一个例子 if (obj != null) 是异常处理,是代码健壮性判断,只有 if 里面才是正常的处理流程,else 分支是出错处理流程;
而第二个例子不管 type 等于 1,2,3 还是其他情况,都属于业务的正常流程。对于这两种情况重构的方法也不一样。
if-else 代码太多有什么缺点
如果是复杂的业务的逻辑或者代码量很多时,就会存在问题:
最大的问题是代码逻辑复杂,维护性、拓展性差,极容易引发 bug。如果使用 if-else,说明 if 分支和 else 分支的重视是同等的,但大多数情况并非如此,容易引起误解和理解困难。想要新加一个分支的时候,就会很难添加,极其容易影响到其他的分支。
优化的思路
- 重构 if-else 的结构
- 策略模式
重构优化
重构 if-else 时,心中无时无刻把握一个原则:
尽可能地维持正常流程代码在最外层。
意思是说,可以写 if-else 语句时一定要尽量保持主干代码是正常流程,避免嵌套过深。
实现的手段有:减少嵌套、移除临时变量、条件取反判断、合并条件表达式等。
异常逻辑处理型重构
优化一:合并条件表达式
如果有一系列条件测试都得到相同结果,将这些结果测试合并为一个条件表达式。
这个重构方法简单易懂,带来的效果也非常明显,能有效地较少if语句,减少代码量逻辑上也更加易懂。
重构前:
public double disablityAmount() {
if(_seniority < 2)
return 0;
if(_monthsDisabled > 12)
return 0;
if(_isPartTime)
return 0;
// TODO
}
重构后:
public double disablityAmount() {
if(_seniority < 2 || _monthsDisabled > 12 || _isPartTime) {
return 0;
}
// TODO
}
优化二:减少嵌套
重构前:
public double getPayAmount() {
double result;
if (_isDead) {
result = deadAmount();
} else {
if (_isSeparated) {
result = separatedAmount();
} else {
if (_isRetired) {
result = retiredAmount();
else{
result = normalPayAmount();
}
}
}
return result;
}
}
重构后:
public double getPayAmount() {
if (_isDead)
return deadAmount();
if (_isSeparated)
return separatedAmount();
if (_isRetired)
return retiredAmount();
if (_isRetired)
return retiredAmount();
return normalPayAmount();
}
【总结重构的要点】:如果 if-else 嵌套没有关联性,直接提取到第一层,一定要避免逻辑嵌套太深。尽量减少临时变量改用 return 直接返回。
-
重构的前提:要理清楚多层嵌套之间的关联
-
结果:很大程度上减少了
if-else
的多层嵌套,逻辑更加清晰明了。
因为嵌套内的if-else
和最外层并没有关联性的,它们之间是平行关系而非包含关系;取消了result
临时变量,直接return返回,因为return之后后面的逻辑是执行不到的,虽然是多个if
,但是实际上只执行了一个。
原来的做法先赋值给 result 最后统一 return,那么对于最后 return 的值到底是那个函数返回的结果不明确,增加了一层理解难度。
【覆写Object类的equals方法实例】
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
// 判断地址是否相等,地址相同则二者指向的是用同一个引用
if (this == obj) return true;
// 判断传入的对象是否属于要比较的类的对象,不是则无比较性
if (!(obj instanceof Person)) return false;
//向下转型,将Object类转为Person类
Person per = (Person) obj;
return per.name.equals(this.name) && per.age == this.age;
}
优化三:条件反转使异常情况先退出
重构前:
public double getPayAmount() {
double result = 0.0;
if (_capital > 0.0) {
if (_intRate > 0 && _duration > 0) {
resutl = (_income / _duration) * ADJ_FACTOR;
}
}
return result;
}
重构后:
首先,运用第一招,减少嵌套和移除临时变量:
public double getPayAmount() {
if (_capital <= 0.0) return 0.0;
if (_intRate > 0 && _duration > 0) {
return (_income / _duration) * ADJ_FACTOR;
}
return 0.0;
}
这样重构后,还不够,因为主要的语句 (_income / _duration) *ADJ_FACTOR; 在 if 内部,并非在最外层,根据优化原则(尽可能地维持正常流程代码在最外层),可以再继续重构:
public double getPayAmount() {
if (_capital <= 0.0) return 0.0;
if (_intRate <= 0 && _duration <= 0) {
return 0.0;
}
return (_income / _duration) * ADJ_FACTOR;
}
这里用到的重构方法是:将条件反转使异常情况先退出,让正常流程维持在主干流程。
这才是好的代码风格,逻辑清晰,一目了然,没有 if-else 嵌套难以理解的流程。
优化四:合并条件表达式
重构前:
典型的"箭头型"代码,最大的问题是嵌套过深,解决方法是异常条件先退出,保持主干流程是核心流程
/** 查找年龄大于18岁且为男性的学生列表 **/
public ArrayList<Student> getStudents(int uid) {
ArrayList<Student> result = new ArrayList<>();
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;
}
重构后:
public ArrayList<Student> getStudents(int uid) {
ArrayList<Student> result = new ArrayList<>();
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;
}
状态逻辑处理型重构
优化一:封装内部函数
重构前:
public 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;
}
}
重构后:
public double getPayAmount() {
Object obj = getObj();
if (obj.getType == 1) {
return getType1Money(obj);
} else if (obj.getType == 2) {
return getType2Money(obj);
}
}
public double getType1Money(Object obj) {
ObjectA objA = obj.getObjectA();
return objA.getMoney() * obj.getNormalMoneryA();
}
public double getType2Money(Object obj) {
ObjectB objB = obj.getObjectB();
return objB.getMoney() * obj.getNormalMoneryB() + 1000;
}
这里使用的重构方法是:把 if-else 内的代码都封装成一个公共函数。函数的好处是屏蔽内部实现,缩短 if-else 分支的代码。代码结构和逻辑上清晰,能一下看出来每一个条件内做的功能。
优化二:多态取代条件表达式
针对状态处理的代码,一种优雅的做法是用 多态 取代条件表达式(《重构》推荐做法)。
你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。将这个表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
重构前:
public double getSpeed() {
switch (_type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
}
重构后:
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);
}
}
使用多态后直接没有了 if-else,但使用多态对原来代码修改过大,需要一番功夫才行。最好在设计之初就使用多态方式。
策略模式优化
抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接 if-else 来的好维护些。
就是几个if else场景我需要用到策略模式吗?
我们所不满的其实就是传统接口实现的缺点:
-
策略类会很多。
-
业务逻辑分散到各个实现类中,而且没有一个地方可以俯览整个业务逻辑
有没有什么更好的代码结构来实现策略模式的吗?
针对传统策略模式的缺点,分享了利用Map与函数式接口来实现的思路。
优化实例
假设有这么一个需求:
一个电商系统,当用户消费满1000 金额,可以根据用户VIP等级,享受打折优惠。
根据用户VIP等级,计算出用户最终的费用。
- 普通会员 不打折
- 白银会员 优惠50元
- 黄金会员 8折
- 白金会员 优惠50元,再打7折
【策略模式优化】
我们定义来一个 Strategy 接口,并且定义 四个子类,实现接口。在对应的 compute方法 实现自身策略的计费逻辑。
public interface Strategy {
// 计费方法
double compute(long money);
}
接口实现类:
// 普通会员策略
public class OrdinaryStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("普通会员 不打折");
return money;
}
}
// 白银会员策略
public class SilverStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("白银会员 优惠50元");
return money - 50;
}
}
// 黄金会员策略
public class GoldStrategy implements Strategy{
@Override
public double compute(long money) {
System.out.println("黄金会员 8折");
return money * 0.8;
}
}
// 白金会员策略
public class PlatinumStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("白金会员 优惠50元,再打7折");
return (money - 50) * 0.7;
}
}
然后对应 getResult 方法,根据 type 替换为对应的 用户VIP 策略。这里代码上出现了重复的调用 compute ,我们可以尝试进一步优化。
private static double getResult(long money, int type) {
if (money < 1000) {
return money;
}
Strategy strategy;
if (type == UserType.SILVER_VIP.getCode()) {
strategy = new SilverStrategy();
} else if (type == UserType.GOLD_VIP.getCode()) {
strategy = new GoldStrategy();
} else if (type == UserType.PLATINUM_VIP.getCode()) {
strategy = new PlatinumStrategy();
} else {
strategy = new OrdinaryStrategy();
}
return strategy.compute(money);
}
可以看到,虽然我们使用了策略模式,但是优化的效果并不是很理想,仍然出现了if-else
【工厂 + 策略】
接口不变,我们先在 Strategy 新增一个 getType 方法,用来表示 该策略的 type 值
public class OrdinaryStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("普通会员 不打折");
return money;
}
// 添加 type 返回
@Override
public int getType() {
return UserType.SILVER_VIP.getCode();
}
}
public class SilverStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("白银会员 优惠50元");
return money - 50;
}
// type 返回
@Override
public int getType() {
return UserType.SILVER_VIP.getCode();
}
}
....省略剩下 Strategy
我们再着手创建一个 StrategyFactory 工厂类。StrategyFactory 这里我使用的是静态内部类单例,在构造方法的时候,初始化好 需要的 Strategy,并把 list 转化为 map。
public class StrategyFactory {
private Map<Integer, Strategy> map;
public StrategyFactory() {
List<Strategy> strategies = new ArrayList<>();
strategies.add(new OrdinaryStrategy());
strategies.add(new SilverStrategy());
strategies.add(new GoldStrategy());
strategies.add(new PlatinumStrategy());
strategies.add(new PlatinumStrategy());
// 看这里 看这里 看这里!
map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
/* 等同上面
map = new HashMap<>();
for (Strategy strategy : strategies) {
map.put(strategy.getType(), strategy);
}*/
}
public static class Holder {
public static StrategyFactory instance = new StrategyFactory();
}
public static StrategyFactory getInstance() {
return Holder.instance;
}
public Strategy get(Integer type) {
return map.get(type);
}
}
List 转化为 Map,转化就是“灵魂”所在,再次优化上面策略模式下的getResult()
方法,消除其if-else
private static double getResult(long money, int type) {
if (money < 1000) {
return money;
}
Strategy strategy = StrategyFactory.getInstance().get(type);
if (strategy == null){
throw new IllegalArgumentException("please input right type");
}
return strategy.compute(money);
}
通过一个工厂类,在我们在 getResult()调用的时候,根据传入 type,即可获取到 对应 Strategy
再也没有可怕的 if-else 语句,完结撒花撒花 : )。
【List转化为Map】
通常情况下,我们遍历 List,手动put到 Map 中。
map = new HashMap<>();
for (Strategy strategy : strategies) {
map.put(strategy.getType(), strategy);
}
来看看 Java8 语法中的小技巧
map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
toMap 第一个参数是一个Function,对应 Map 中的 key,第二个参数也是一个Function,strategy -> strategy, 左边strategy 是遍历 strategies 中的每一个strategy,右边strategy则是 Map 对应 value 值。
【原文链接】
[1] CSDN-yinnnnnnn.6个实例详解如何把if-else代码重构成高质量代码.https://blog.csdn.net/qq_35440678/article/details/77939999
[2] 公众号Hollis-上帝爱吃苹果.用策略模式干掉 if-else
[3] hyzhan43.探讨复杂的 if-else 语句“优雅处理”的思路.juejin.im/post/5def654f51882512302daeef