字数:5061
1 引文
此篇举一个示例,说明一下日常开发中一个复杂的接口,主要有两个想法:
- 审视一下开发,特别复杂功能的开发,这个花费了大量的时间和精力的事情,也是不断想去优化的事情,以求庖丁解牛。
最近这个项目,业务开发量庞大繁杂,虽然大部分是在一个确定的写代码的模式下实现不同的业务功能,但业务功能本身并不简单, 所以,每当复杂的东西摆在面前时,不免要担心一下,这其中会不会有隐患?复杂性会不会叠加? 会不会雪崩?未来怎么面对?有没有办法减少和分离这种情况?等等。
-
留一些痕迹,日后或回顾可查,每次问应聘的人,说一下他们开发工作的重点在哪些地方,其实也就在这些细枝末节之中,当然这很难口头上将此类表述清楚, 需要有很强的抽象表达能力,抽象中还要见具体,具体中见要害。
-
想试着用文章的形式,将事情来龙去脉说清楚,因此会涉及到一些设计和代码,但不会影响产品的安全。
2 需求
考虑到产品的安全,这里的就不详尽描述了,直接抛硬料。首先看数据表关系图,去掉了次要属性——>
**这个接口要做什么呢?**说起来很简单,就是往 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;
}
最后,这种接口的测试用例覆盖都比较费事,有些点应该是黑盒测试很难遇到的,这就需要单元测试,写单元测试代码全覆盖是一件非常有技术含量的事
。