快速失败原则

让我们来分析一下为什么使用反例(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)模式,是一种常见的编程实践。这样设计的原因是:

  1. 早期验证,提前返回: 使用反例判断可以尽早发现不满足条件的情况,并立即返回错误信息。这样可以避免不必要的后续处理,提高程序效率。

  2. 逻辑清晰: 每个条件检查都是独立的,一旦发现不符合条件就立即返回错误信息,使得代码逻辑更加清晰易懂。

  3. 减少嵌套层级: 如果使用正例判断,代码会变成多层嵌套的if语句,可读性会变差:

    // 不推荐的写法示例
    if (voucher != null) {
        if (!voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            if (!voucher.getEndTime().isBefore(LocalDateTime.now())) {
                if (voucher.getStock() >= 1) {
                    // 正常业务逻辑
                }
            }
        }
    }
  4. 错误处理优先: 这种方式优先处理异常情况和边界条件,符合”防御性编程”的思想。先检查所有可能出错的情况,确保只有在一切条件都满足的情况下才执行正常的业务逻辑。

  5. 用户体验考虑: 对于秒杀场景,用户最关心的是为什么不能购买。通过逐一检查各种失败条件并返回具体错误信息,可以让用户清楚地知道失败原因。

总结来说,这种使用反例判断的方式遵循了”快速失败”原则,可以提高代码的可读性和执行效率,同时便于维护和调试。这是一种良好的编程实践,特别是在处理复杂的业务逻辑时非常有用。

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))

这行代码的作用是:

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>

为什么要使用这种方式?

  1. 数据传输对象(DTO)模式

    • User实体类可能包含敏感信息(如密码等)
    • UserDTO只包含需要传输给前端的字段(id、昵称、头像)
    • 通过DTO模式可以避免敏感信息泄露
  2. 函数式编程风格

    • 代码更加简洁、可读性强
    • 避免了传统的for循环或迭代器方式
    • 更容易进行链式操作
  3. 类型安全

    • 明确指定返回类型为List<UserDTO>
    • 编译时就能发现类型错误

这种写法是现代Java开发中非常常见的模式,特别是在需要进行数据转换和处理的场景中。