方法内/外校验?用户通知服务的校验方案选型疑问
问题分析与解决方案
你的场景核心是平衡职责边界清晰和避免重复代码,先拆解两种方案的利弊,再给出最优处理方式:
方案1的优缺点
- 优点:职责划分明确——
MainJob负责筛选符合条件的用户,UserNotificationService只专注执行通知发送,逻辑一目了然,排查问题时定位成本低。 - 缺点:校验逻辑无法复用,若后续有多个地方调用
notifyUser,每个调用点都要重复写国家校验代码,违反DRY(Don't Repeat Yourself)原则;一旦合格国家列表变更,所有调用点都要同步修改,维护成本高。
方案2的优缺点
- 优点:校验逻辑集中在
UserNotificationService,复用性强,规则变更只需修改这一处。 - 缺点:用异常控制业务流程是反模式——异常应用于处理意外错误,而用户不符合国家条件是预期内的业务场景,靠捕获异常跳过会让代码可读性下降,还可能不小心捕获其他非预期异常,掩盖真正的错误。
推荐的最优处理方式
结合两者优势,给出两种可行方向:
方向1:拆分职责,新增独立校验层
- 单独抽离
UserEligibilityChecker类,专门负责校验用户是否符合通知条件(包括国家规则,后续可扩展其他规则如用户状态)。 - 所有需要调用
notifyUser的地方,先调用该校验类的isEligibleForNotification(User user)方法,符合条件再触发通知。 - 示例代码:
// 独立校验类,专注处理用户 eligibility 规则 public class UserEligibilityChecker { private static final List<String> ELIGIBLE_COUNTRIES = Arrays.asList("CN", "US"); public boolean isEligibleForNotification(User user) { return ELIGIBLE_COUNTRIES.contains(user.getCountry()); } } // MainJob 中的调用逻辑 public class MainJob { private final UserEligibilityChecker eligibilityChecker; private final UserNotificationService notificationService; // 构造注入依赖 public MainJob(UserEligibilityChecker eligibilityChecker, UserNotificationService notificationService) { this.eligibilityChecker = eligibilityChecker; this.notificationService = notificationService; } public void processUsers(List<User> users) { for (User user : users) { if (eligibilityChecker.isEligibleForNotification(user)) { notificationService.notifyUser(user); } } } } - 好处:既保持职责清晰(校验归校验,通知归通知),又实现了规则复用,后续修改合格国家列表只需调整
UserEligibilityChecker。
方向2:在通知服务中新增“安全调用”方法
- 在
UserNotificationService中新增tryNotifyUser(User user)方法,内部先执行校验,符合条件则发送通知并返回true,不符合则直接返回false,不抛出异常。 - 保留原有的
notifyUser方法作为纯净的通知执行入口,通过注释明确要求调用方确保用户已符合条件(适合需要严格控制调用场景的情况)。 - 示例代码:
public class UserNotificationService { private static final List<String> ELIGIBLE_COUNTRIES = Arrays.asList("CN", "US"); // 纯净通知方法:仅负责发送,要求调用方确保用户符合条件 public void notifyUser(User user) { // 执行具体的通知发送逻辑(如推送短信、APP通知) } // 带校验的安全调用方法:处理预期内的不符合场景 public boolean tryNotifyUser(User user) { if (!isEligible(user)) { return false; } notifyUser(user); return true; } private boolean isEligible(User user) { return ELIGIBLE_COUNTRIES.contains(user.getCountry()); } } // MainJob 中的调用逻辑 public class MainJob { private final UserNotificationService notificationService; public MainJob(UserNotificationService notificationService) { this.notificationService = notificationService; } public void processUsers(List<User> users) { for (User user : users) { notificationService.tryNotifyUser(user); } } } - 好处:校验逻辑集中维护,调用方代码简洁,同时避免了用异常控制业务流程的反模式。
相关设计原则参考
- 单一职责原则(SRP):每个类只负责一件事,避免将校验与通知逻辑混在同一方法中。
- DRY原则:将通用规则抽离到单一入口,避免重复代码,降低维护成本。
- 避免用异常控制流程:异常用于处理意外错误,预期内的业务分支应通过返回值判断,提升代码可读性和可维护性。
内容的提问来源于stack exchange,提问作者eugenedrvnk
相关产品推荐
相关产品推荐

