快速失败原则
让我们来分析一下为什么使用反例(negative cases)而不是正例(positive cases)进行判断。
public Result seckillVoucher(Long voucherId) {
// 查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
if (voucher == null) {
return Result.fail("优惠券不存在!");
}
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("优惠券尚未开始!");
}
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("优惠券已过期!");
}
// 判断库存是否充足
if (voucher.getStock() < 1) {
return Result.fail("库存不足!");
}
// ... 其他逻辑
}这种设计模式叫做”快速失败”(Fail-Fast)模式,是一种常见的编程实践。这样设计的原因是:
-
早期验证,提前返回: 使用反例判断可以尽早发现不满足条件的情况,并立即返回错误信息。这样可以避免不必要的后续处理,提高程序效率。
-
逻辑清晰: 每个条件检查都是独立的,一旦发现不符合条件就立即返回错误信息,使得代码逻辑更加清晰易懂。
-
减少嵌套层级: 如果使用正例判断,代码会变成多层嵌套的if语句,可读性会变差:
// 不推荐的写法示例 if (voucher != null) { if (!voucher.getBeginTime().isAfter(LocalDateTime.now())) { if (!voucher.getEndTime().isBefore(LocalDateTime.now())) { if (voucher.getStock() >= 1) { // 正常业务逻辑 } } } } -
错误处理优先: 这种方式优先处理异常情况和边界条件,符合”防御性编程”的思想。先检查所有可能出错的情况,确保只有在一切条件都满足的情况下才执行正常的业务逻辑。
-
用户体验考虑: 对于秒杀场景,用户最关心的是为什么不能购买。通过逐一检查各种失败条件并返回具体错误信息,可以让用户清楚地知道失败原因。
总结来说,这种使用反例判断的方式遵循了”快速失败”原则,可以提高代码的可读性和执行效率,同时便于维护和调试。这是一种良好的编程实践,特别是在处理复杂的业务逻辑时非常有用。
Stream流和map函数结合使用
List<UserDTO> userDTOS = userService.query()
.in("id", ids).last("order by field(id," + idStr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());代码使用了Java 8的Stream API来处理数据集合。下面详细解释每个部分:
1. Stream流(Stream)
Stream是Java 8引入的一个重要特性,它允许我们以声明式的方式处理数据集合。Stream不是数据结构,而是对集合对象进行各种操作的工具。
2. map函数
map是Stream的一个中间操作,用于将流中的每个元素按照指定的函数进行转换。在你的代码中:
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))这行代码的作用是:
- 对流中的每个
user对象(是User类型) - 使用BeanUtil.copyProperties(user, UserDTO.class)将User对象转换为UserDTO对象
- 返回一个新的流,其中包含转换后的UserDTO对象
map操作是一对一的转换,即流中有多少个元素,转换后仍然有多少个元素。
3. collect(Collectors.toList())
collect是一个终端操作,用于将Stream中的元素收集到集合中。Collectors.toList()是一个收集器,它将流中的元素收集到一个List中。
.collect(Collectors.toList())这行代码的作用是:
- 将经过map转换后的UserDTO对象流收集到一个List中
- 最终返回一个包含所有UserDTO对象的List
整体流程
userService.query()
.in("id", ids).last("order by field(id," + idStr + ")").list() // 查询数据库得到List<User>
.stream() // 转换为Stream<User>
.map(user -> BeanUtil.copyProperties(user, UserDTO.class)) // 转换为Stream<UserDTO>
.collect(Collectors.toList()); // 收集为List<UserDTO>为什么要使用这种方式?
-
数据传输对象(DTO)模式:
- User实体类可能包含敏感信息(如密码等)
- UserDTO只包含需要传输给前端的字段(id、昵称、头像)
- 通过DTO模式可以避免敏感信息泄露
-
函数式编程风格:
- 代码更加简洁、可读性强
- 避免了传统的for循环或迭代器方式
- 更容易进行链式操作
-
类型安全:
- 明确指定返回类型为
List<UserDTO> - 编译时就能发现类型错误
- 明确指定返回类型为
这种写法是现代Java开发中非常常见的模式,特别是在需要进行数据转换和处理的场景中。