背景
领导:“这个项目,今后就给你维护了啊,仔细点。” 小猫:“好,没问题”。 可当满怀信心的小猫打开项目工程包翻看一些代码之后,瞬间懵逼没了信心。
是这样的:
还是这样的:
平级的if else密密麻麻就算了,但是深套五六层的if else甚至七八层的真的是让人摸不着北。
开启优化
那么就上面小猫遇到的这种情况,面对着几代程序员精心堆积的屎山,试问阁下该如何应对?不慌,老猫罗列了以下解决方案,如果各位还有比较好的优化方法也欢迎留言。
我们对着上述目录从简单的开始介绍吧:
1.提前return法
当我们遇到空对象或者有部分满足条件之后才能执行的时候,不要只想着正向逻辑,其实可以逆向思维,把不满足条件的优先排除掉。这样可以有效避免if else的深嵌套。 优化前代码:
if(condition){
//doSomething
}else{
}
return;
优化后如下:
if(!condition){
return;
}
2.能省则省,规避最后的else
原来的代码:
public Result addUser() {
if (StrUtil.equals(userStatus, "online")) {
return doStep1();
} else {
return doStep2();
}
// else 后面没有其他业务时,可省略最后的else,使代码简洁
}
优化后的代码:
public Result addUser() {
if (StrUtil.equals(userStatus, "online")) {
return doStep1();
}
return doStep2();
}
当然这里面要注意的点是,一定要确认是最后的else,并没有其他的业务逻辑。
3.三目运算符
还是基于上面的代码,如果只有两种业务的话,其实在一个方法里面直接用三目运算法进行执行即可。如下改造:
public Result addUser() {
return StrUtil.equals(userStatus, "online")) ?doStep1() : doStep2();
}
一个方法一行代码搞定。
4.使用optional
很多业务场景下,其实我们写if 是为了判空,自从java8之后其实多了一个Optional神器,Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存null。Optional 提供了很多方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。我们看下下面的优化方式: 代码优化前:
if (user == null) {
throw new Exception("未查询到用户信息");
}
if (user != null) {
update(user); // 执行方法调用
}
代码优化后:
Optional.ofNullable(user).orElseThrow(() -> new Exception("未查询到用户信息"));
Optional.ofNullable(user).ifPresent(user -> update(user));
隐式调用相当优雅。
5.设计模式优化法
设计模式优化法其实也是针对不同的场景使用不同的设计模式从而简化多余的if else。
(1) 第一种,合理使用责任链模式。
我们再具体结合一种场景,比方说现在页面上有新注册的用户,他需要提交相关的身份信息进行认证,此时,我们底层往往会对他提交的信息做相关的校验处理。 底层我们的校验方式(1)需要验证基本字非空性 (2)需要验证身份信息基础字段合法性 (2)需要调用第三方进行要素认证。 原始代码如下:
public void addUser(User user) {
// 1.非空校验
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用户名为空!");
}
if (StrUtil.isBlank(user.getPassword())) {
throw new RuntimeException("密码为空!");
}
...
// 2.格式校验
if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
throw new RuntimeException("身份证号格式错误!");
}
if (!ValidUtil.isEmail(user.getEmail())) {
throw new RuntimeException("手机号格式错误!");
}
if (!ValidUtil.isEmail(user.getEmail())) {
throw new RuntimeException("邮箱格式错误!");
}
...
// 3.要四素认证校验
if(!doFourStampVerify(User user)){
throw new RuntimeException("四要素认证失败!");
}
}
此处可能还有很多其他的省略的场景。所以单个文件中的If else可能比想象中多的多。那么我们如何用责任链模式进行优化呢? 改造代码如下,首先定义一个处理器接口:
/**
* 处理器链接口
*/
public interface UserChainHandler {
void handler(User user);
}
剩下不同的场景校验只要去实现这个接口就可以了,不过需要定义好顺序
@Component
@Order(1) // 指定注入顺序
public class UserParamNullValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 1.非空校验
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用户名为空!");
}
if (StrUtil.isBlank(user.getPassword())) {
throw new RuntimeException("密码为空!");
}
}
@Component
@Order(1) // 指定注入顺序
public class UserParamNullValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 1.非空校验
if (StrUtil.isBlank(user.getUsername())) {
throw new RuntimeException("用户名为空!");
}
...
}
/**
* 格式校验处理器
*/
@Component
@Order(2) // 指定注入顺序
public class UserParamFormatValidChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 2.格式校验
if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
throw new RuntimeException("身份证号格式错误!");
}
...
}
/**
* 四要素处理器
*/
@Component
@Order(3) // 指定注入顺序
public class FourElementVerifyChainHandler implements UserChainHandler {
@Override
public void handler(User user) {
// 2.格式校验
if (!doFourStampVerify(User user)) {
throw new RuntimeException("四要素认证失败!");
}
}
//进行组装
@Component
@RequiredArgsConstructor
public class UserChainContext {
private final List userChainHandlerList; // 自动注入责任链处理器
/**
* 责任链组件执行
*
* @param requestParam 请求参数
*/
public void handler(User user) {
// 此处根据 Ordered 实际值进行排序处理
userChainHandlerList.forEach(x -> x.handler(user));
}
}
最终咱们的原来的add方法进行这样调用就好了:
public void addUser(User user) {
// 执行责任链
userChainContext.handler(user);
}
(2) 第二种,合理使用策略模式+工厂模式。
假设我们遇到这样一个场景,我们目前底层是一个会员系统,目前系统需要计算各种会员套餐的价格,然后套餐的具体模式主要是由上层系统传递指定给我们。如果只关注业务直接撸代码的话,应该是如下。
public Result calcPrice(CalcPriceParam calcPriceParam){
//判断对应的计算价格的场景
Integer type = judgeType(calcPriceParam);
//根据场景调用不同的方法 ,建议更好的编码习惯是把type改成枚举类型哈~
if(type == 1){
return calcPriceForTypeOne();
}
if(type == 2){
return calcPriceForTypeTwo();
}
if(type == 3){
return calcPriceForTypeThree();
}
.....
if(typr == 10){
return calcPriceForTypeTen();
}
}
显而易见随着会员价格场景套餐越来越多,我们的if也会越来越多。 但是如果使用策略模式的话,我们可以做到如下:
public interface Strategy {
Result calcPrice(CalcPriceParam calcPriceParam);
int getBizType();
}
@Service
public Class firstStragy implement Strategy {
Result calcPrice(CalcPriceParam calcPriceParam) {
....
return result;
}
int getBizType() {
return 1;
}
}
public Class secondStragy implement Strategy {
Result calcPrice(CalcPriceParam calcPriceParam) {
....
return result;
}
int getBizType() {
return 2;
}
}
@Service
public class StrategyContext{
Map strategyContextMap = new HashMap();
//注入对应的策略类
@Autowired
Strategy[] strategys;
@PostConstruct
public void setStrategyContextMap(){
for(Stragegy stragegy:strategys){
strategyContextMap.put(stragegy.getCode,stragegy);
}
}
//根据场景调用不同的方法
public Result calcPrice(CalcPriceParam calcPriceParam){
Integer type = judgeType(calcPriceParam);
CalcPriceInterface calcPriceInstance = strategyContextMap.get(type);
return calcPriceInstance.calcPrice(calcPriceParam);
}
}
这样一来,咱们上面的第一个方法中的If else的实现将会变得很简单,如下:
@Autowired
StrategyContext strategyContext;
public Result calcPrice(CalcPriceParam calcPriceParam){
strategyContext.calcPrice(calcPriceParam);
}
这样即使新增新的计算模式,我们只需去实现Strategy接口并且重写里面两个方法即可完成后续业务的拓展。代码优雅简单,可维护性强。 以上就是用设计模式针对大量if else进行改造。
6.表驱动法
这种方式个人觉得有点像策略模式,但是又不需要单独抽出相关类去承载注册方法,而是简单地将方法通过函数式的方式放到Map中,等到需要使用的时候再进行调用。 原始烂代码,我们还是参考上述会员费用金额计算的场景。我们可以进行如下方式优化:
Map