防御性编程失败,我开始优化我写的多重 if-else 代码

2 阅读6分钟

前言

  • 最近防御性编程比较火,不信邪的我在开发中我进行了尝试,然后我写下了如下的代码:
    public static void main(String[] args) {
        // do something
        if ("满足条件A") {
            // 查询权限
            if ("是否具备权限A" && "是否具备权限B") {
                // 查询配置
                if ("配置是否开启"){
                    // do something
                }
            }
        }
        // do something
    }
  • 不出意外我被逮捕了,组内另外一位同事对我的代码进行了 CodeReview,我的防御性编程编程没有幸运逃脱,被标记上了“多重 if-else ”需要进行优化,至此我的第一次防御性编程失败,开始了优化多重 if-else 之路,下面是我总结出的常用几种优化方式。

版本

  • Java8

几种常用的优化方式

提前使用 return 返回去除不必要的 else

  • 如果我们的代码块中需要使用 return 返回,我们应该尽可能早的使用 return 返回而不是使用 else
  • 优化前
    private static boolean extracted(boolean condition) {
        if (condition) {
            // do something
            return false;
        }else {
            // do something
            return true;
        }
    }
  • 优化后
    private static boolean extracted(boolean condition) {
        if (condition) {
            // do something
            return false;
        }
        
        // do something
        return true;
    }

使用三目运算符

  • 一些简单的逻辑我们可以使用三目运算符替代 if-else ,这样可以让我们的代码更加简洁

  • 优化前

        int num = 0;
        if (condition) {
            num = 1;
        } else {
            num = 2;
        }
  • 优化后
int num = condition ? 1 : 2;

使用枚举

  • 在某一些场景我们也可以使用枚举来优化多重 if-else 代码,使我们的代码更加简洁、具备更多的可读性和可维护性。

  • 优化前

        String OrderStatusDes;
        if (orderStatus == 0) {
            OrderStatusDes = "订单未支付";
        } else if (orderStatus == 1) {
            OrderStatusDes = "订单已支付";
        } else if (orderStatus == 2) {
            OrderStatusDes = "已发货";
        } else {
            throw new Exception("Invalid order status");
        }
  • 优化后
public enum OrderStatusEnum {
    UN_PAID(0, "订单未支付"),
    PAIDED(1, "订单已支付"),
    SENDED(2, "已发货"),
    ;

    private final int code;
    private final String desc;

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    OrderStatusEnum(int index, String desc) {
        this.code = index;
        this.desc = desc;
    }

    public static OrderStatusEnum getOrderStatusEnum(int orderStatusCode) {
        for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) {
            if (statusEnum.getCode() == orderStatusCode) {
                return statusEnum;
            }
        }
        return null;
    }
}


// 当然你需要根据业务场景对异常值做出合适的处理
OrderStatusEnum.getOrderStatusEnum(2)

抽取条件判断作为单独的方法

  • 当我们某个逻辑条件判断比较复杂时,可以考虑将判断条件抽离为单独的方法,这样可以使我们主流程逻辑更加清晰
  • 优化前
        // do something
        if ("满足条件A" && "满足条件B") {
            // 查询权限
            if ("是否具备权限A" && "是否具备权限B") {
                // do something
            }
        }
        // do something
  • 优化后
    public static void main(String[] args) {
        // do something
        if (hasSomePermission()) {
            // do something
        }
        // do something
    }

    private static boolean hasSomePermission() {
        if (!"满足条件A" || !"满足条件B") {
            return false;
        }
        // 查询权限
        return "是否具备权限A" && "是否具备权限B";
    }

有时候 switch 比 if-else 更加合适

  • 当条件为清晰的变量和枚举、或者单值匹配时,switch 比 if-else 更加合适,可以我们带好更好的可读性以及更好的性能 O(1)
  • 优化前
if (day == Day.MONDAY) {
    // 处理星期一的逻辑
} else if (day == Day.TUESDAY) {
    // 处理星期二的逻辑
} else if (day == Day.WEDNESDAY) {
    // 处理星期三的逻辑
} else if (day == Day.THURSDAY) {
    // 处理星期四的逻辑
} else if (day == Day.FRIDAY) {
    // 处理星期五的逻辑
} else if (day == Day.SATURDAY) {
    // 处理星期六的逻辑
} else if (day == Day.SUNDAY) {
    // 处理星期日的逻辑
} else {
    // 处理其他情况
}
  • 优化后
// 使用 switch 处理枚举类型
switch (day) {
    case MONDAY:
        // 处理星期一的逻辑
        break;
    case TUESDAY:
        // 处理星期二的逻辑
        break;
    // ...
    default:
        // 处理其他情况
        break;
}

策略模式 + 简单工厂模式

  • 前面我们介绍一些常规、比较简单的优化方法,但是在一些更加复杂的场景(比如多渠道对接、多方案实现等)我们可以结合一些场景的设计模式来实现让我们的代码更加优雅和可维护性,比如策略模式 + 简单工厂模式。

  • 优化前

    public static void main(String[] args) {
        // 比如我们商场有多个通知渠道
        // 我们需要根据不同的条件使用不同的通知渠道
        if ("满足条件A") {
            // 构建渠道A
            // 通知
        } else if ("满足条件B") {
            // 构建渠道B
            // 通知
        } else {
            // 构建渠道C
            // 通知
        }
    }
// 上面的代码不仅维护起来麻烦同时可读性也比较差,我们可以使用策略模式 + 简单工厂模式
  • 优化后
import java.util.HashMap;
import java.util.Map;

// 定义通知渠道接口
interface NotificationChannel {
    void notifyUser(String message);
}

// 实现具体的通知渠道A
class ChannelA implements NotificationChannel {
    @Override
    public void notifyUser(String message) {
        System.out.println("通过渠道A发送通知:" + message);
    }
}

// 实现具体的通知渠道B
class ChannelB implements NotificationChannel {
    @Override
    public void notifyUser(String message) {
        System.out.println("通过渠道B发送通知:" + message);
    }
}

// 实现具体的通知渠道C
class ChannelC implements NotificationChannel {
    @Override
    public void notifyUser(String message) {
        System.out.println("通过渠道C发送通知:" + message);
    }
}

// 通知渠道工厂
class NotificationChannelFactory {
    private static final Map<String, Class<? extends NotificationChannel>> channelMap = new HashMap<>();

    static {
        channelMap.put("A", ChannelA.class);
        channelMap.put("B", ChannelB.class);
        channelMap.put("C", ChannelC.class);
    }

    public static NotificationChannel createChannel(String channelType) {
        try {
            Class<? extends NotificationChannel> channelClass = channelMap.get(channelType);
            if (channelClass == null) {
                throw new IllegalArgumentException("不支持的通知渠道类型");
            }
            return channelClass.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("无法创建通知渠道", e);
        }
    }
}

// 客户端代码
public class NotificationClient {
    public static void main(String[] args) {
        // 根据条件选择通知渠道类型
        String channelType = "A";
        // 使用简单工厂创建通知渠道
        NotificationChannel channel = NotificationChannelFactory.createChannel(channelType);

        // 执行通知
        channel.notifyUser("这是一条通知消息");
    }
}
  • 有时候我们还可以借助 Spring IOC 能力的自动实现策略类的导入,然后使用 getBean() 方法获取对应的策略类实例,可以根据我们的实际情况灵活选择。

如何优化开头的代码

  • 好了现在回到开头,如果是你会进行怎么优化,下面是我交出的答卷,大家也可以在评论区发表自己的看法,欢迎一起交流:
   public static void main(String[] args) {
        // do something
        if (isMeetCondition()) {
            // 查询配置
            // 此处查询配置的值需要在具体的任务中使用,所有并没抽离
            if ("配置是否开启") {
                // do something
            }
        }
        // do something
    }

    /**
     * 判断是否满足执行条件
     */
    private static boolean isMeetCondition() {
        if (!"满足条件A") {
            return false;
        }
        // 查询权限
        return "是否具备权限A" && "是否具备权限B";
    }

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。