字数:5061

1 引文

此篇举一个示例,说明一下日常开发中一个复杂的接口,主要有两个想法:

  • 审视一下开发,特别复杂功能的开发,这个花费了大量的时间和精力的事情,也是不断想去优化的事情,以求庖丁解牛。

最近这个项目,业务开发量庞大繁杂,虽然大部分是在一个确定的写代码的模式下实现不同的业务功能,但业务功能本身并不简单, 所以,每当复杂的东西摆在面前时,不免要担心一下,这其中会不会有隐患?复杂性会不会叠加? 会不会雪崩?未来怎么面对?有没有办法减少和分离这种情况?等等。

  • 留一些痕迹,日后或回顾可查,每次问应聘的人,说一下他们开发工作的重点在哪些地方,其实也就在这些细枝末节之中,当然这很难口头上将此类表述清楚, 需要有很强的抽象表达能力,抽象中还要见具体,具体中见要害。

  • 想试着用文章的形式,将事情来龙去脉说清楚,因此会涉及到一些设计和代码,但不会影响产品的安全。

2 需求

考虑到产品的安全,这里的就不详尽描述了,直接抛硬料。首先看数据表关系图,去掉了次要属性——>

数据表关系图.jpg

**这个接口要做什么呢?**说起来很简单,就是往 ggh_rfid_exchange 添加数据,但要注意以下事项:

  • 已知的参数就只有两个:ggh_exchange_book 表中的 id_exchange ,和 ggh_rfid 表中的 rfid

  • rfid 不存在,禁止添加;

  • id_exchange 下,如果已经存在 rfid,禁止添加;

  • 如果 amount_by_official + amount_by_third 大于等于 amount_apply,禁止添加;

  • 往 ggh_rfid_exchange 添加完数据后,amount_by_official 加1。

以上禁止添加的情况,还需要抛出书籍名称 name_simple。

好了,以上是全部需求,想想应该怎么做?

3 实现

分析完以上要点,再看看关系型数据表,当即列了一个大纲:

1 存在性判断,rfid是否在发货单中
    1.1 查rfid是否已经存在
    1.2 查rfid是否在发货单中
        1.2.1 查出发货单中书籍编号
        1.2.2 查出发货单中书籍编号对应的所有Rfid
    1.3 存在则找出书名等信息,给出信息提示,不要重复发此rfid的书籍,返回

2 约束性判断,rfid对应的书籍是否在发货单中
    2.1 查rfid对应的绘本库此书编号
    2.2 再查其对应书籍编号
    2.3 对应的书籍编号是否在书单中,无则信息提示,返回

3 一致性,发货单中的书籍数量是否已经达到申请数量;
    3.1 发货单中的书籍数量是否已经达到申请数量

4 添加到发货单

5 发货单官方发货数量+1

而真正到了代码阶段,考虑到充分利用遍历,顺序又有一些穿插,代码如下:

public ExchangeBook addRfidToExchangeBook(long idExchange, long rfid) {
    // 1 存在性判断
    // 1.1 查rfid是否已经存在
    Rfid rfidObj = rfidRepository.findByRfid(rfid);

    if (rfidObj == null) {
        throw new MyException("RFID "+rfid+" 无对应的书籍,是否错误?如未绑定到书,馆长先去小程序绑定");
    }
    // 1.2 查rfid是否在发货单中
    // 1.2.1 查出发货单中书籍编号
    List<ExchangeBook> exchangeBooks = exchangeBookRepository.findByIdExchange(idExchange);

    if (exchangeBooks == null && exchangeBooks.isEmpty()) {
        throw new MyException("书单中尚无书籍,无法操作");
    }

    // 2 约束性判断
    // 2.1 查rfid对应的绘本库此书编号
    Optional<LibraryBook> optLibraryBook = libraryBookRepository.findById(rfidObj.getIdLibraryBook());

    if (!optLibraryBook.isPresent()) {
        throw new MyException("RFID "+rfid+" 不合法,无存在的绘本库书籍编号");
    }
    LibraryBook libraryBook = optLibraryBook.get();

    // 2.2 再查其对应书籍编号
    long idBookRfidReferTo = libraryBook.getIdBook(); //rfid关联的书籍编号
    boolean isExchangeIncludeBookOfRfid = false; //书单中是否包含rfid关联的书籍编号(通过书籍编号来匹配)
    ExchangeBook exchangeBookRefer = null; //关联于书单中的某书,包含多个信息
    List<RfidExchange> rfidExchangesRef = null; //书单中关联书对应的rfid关系列表

    for (ExchangeBook exchangeBook : exchangeBooks) {
        long idBook = exchangeBook.getIdBook();

        // 1.2.2 查出发货单中书籍编号对应的所有Rfid
        List<RfidExchange> rfidExchanges = rfidExchangeRepository.findByIdExchangeBook(exchangeBook.getId());

        if (rfidExchanges != null && !rfidExchanges.isEmpty()) {
            for (RfidExchange rfidExchange : rfidExchanges) {
                // 1.3 存在则找出书名等信息,给出信息提示,不要重复发此rfid的书籍,返回
                if (rfidObj.getId() == rfidExchange.getIdRfid()) {
                    throw new MyException("RFID "+rfid+" 对应的书是《"+ (cacheMgrService.findBookById(idBook) != null
                            ? cacheMgrService.findBookById(idBook).getNameSimple() : "未找到该书...") +"》,已经在发货单,放弃重复添加");
                }
            }
        }

        if (idBook == idBookRfidReferTo) {
            isExchangeIncludeBookOfRfid = true;
            exchangeBookRefer = exchangeBook;

            // 3 一致性
            // 3.1 发货单中的书籍数量是否已经达到申请数量
            if (exchangeBookRefer.getAmountByThird() + exchangeBookRefer.getAmountByOfficial()
                    >= exchangeBookRefer.getAmountApply()) {
                throw new MyException("RFID "+rfid+" 对应的书是《"+ (cacheMgrService.findBookById(idBookRfidReferTo) != null
                        ? cacheMgrService.findBookById(idBookRfidReferTo).getNameSimple() : "未找到该书...") +"》,发货单中此书的数量已经达到申请数量,放弃添加");
            }
            rfidExchangesRef = new ArrayList<>();

            if (rfidExchanges != null && !rfidExchanges.isEmpty()) {
                rfidExchangesRef.addAll(rfidExchanges);
            }
        }
    }

    // 2.3 对应的书籍编号是否在书单中,无则信息提示,返回
    if (!isExchangeIncludeBookOfRfid) {
        throw new MyException("RFID "+rfid+" 对应的书是《"+ (cacheMgrService.findBookById(idBookRfidReferTo) != null
                ? cacheMgrService.findBookById(idBookRfidReferTo).getNameSimple() : "未找到该书...") +"》,不在此书单中,放弃添加");
    }

    // 4 添加到发货单
    RfidExchange newRfidExchange = utils.createEntity(RfidExchange.class);
    newRfidExchange.setIdExchangeBook(exchangeBookRefer.getId());
    newRfidExchange.setIdRfid(rfidObj.getId());
    rfidExchangeRepository.save(newRfidExchange);

    // 5 发货单官方发货数量+1
    exchangeBookRefer.setAmountByOfficial(exchangeBookRefer.getAmountByOfficial() + 1);
    exchangeBookRepository.save(exchangeBookRefer);

    rfidExchangesRef.add(newRfidExchange);
    exchangeBookRefer.setRfidsExchange(rfidExchangesRef);
    return exchangeBookRefer;
}

最后,这种接口的测试用例覆盖都比较费事,有些点应该是黑盒测试很难遇到的,这就需要单元测试,写单元测试代码全覆盖是一件非常有技术含量的事