▄︻┻┳═一Agenda:html
▄︻┻┳═一(1/8)[代码整洁之道]你真的会用枚举吗?非也!前端
▄︻┻┳═一(2/8)枚举的错误用法 之 方法参数web
▄︻┻┳═一(3/8)枚举的错误用法 之 方法参数(二)后端
▄︻┻┳═一(4/8)枚举的错误用法 之 方法返回值安全
▄︻┻┳═一(5/8)枚举的错误用法 之 方法体内部app
▄︻┻┳═一(6/8)枚举的错误用法 之 分支判断框架
▄︻┻┳═一(7/8)借助枚举说一下数据类型定义规范前后端分离
▄︻┻┳═一(8/8)RPC方法的参数,能用枚举就请考虑枚举socket
咱们都知道,在作后台管理系统开发时,不少功能的管理页都有一堆查询条件,用来筛选业务数据记录。其中,最多见的一个,当属业务数据的状态了。好比,用户状态、企业状态、任务状态,每人都能从本身的系统里找出一大堆。ide
先问你们一个问题:对于一些列表页,查询所有状态的数据,你给服务端接口传的状态值是什么?
不用说,你们通常会传:"0" 或者 "ALL" 或者 空串。
咱们是ToB的系统。
如上图,在企业管理页,有一个筛选条件是product,意为企业所开通的业务产品,好比机票、酒店、保险。程序common包里定义了ProductEnum。
public enum ProductEnum { AirTicket("AIRTICKET","机票"), Hotel("HOTEL","酒店"), Insurance("INSURANCE","保险"), ; ... }
网站采用的是先后端分离,后端经过rpc调用dubbo接口。任务分工方面,其中一同窗写dubbo服务;另外一同窗写页面先后端,调用dubbo服务获取数据。
我在codereview时,发现获取企业数据的dubbo服务接口EnterpriseService是以下定义的:
public interface EnterpriseService { /** * 获取企业分页数据 * @param pageNo * @param pageSize * @param vo * @param product 业务线 * @return */ Result getPage(int pageNo, int pageSize, EnterpriseVO vo,String product); }
其中,product参数是String。
web端呢,直接获取页面vo,而后调用这个dubbo接口
@GetMapping(value = "/list") public Result<?> queryPageList(EnterpriseVO enterprise, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req) { long start = System.currentTimeMillis(); try { log.info("企业管理查询开始>>>>>>>>"); String product; if(StringUtils.isEmpty(enterprise.getProduct())){ product = ""; }else{ product = enterprise.getProduct(); } return enterpriseService.getPage(pageNo, pageSize ,enterprise,product); } finally { log.info("查询企业列表,耗时={}",System.currentTimeMillis()-start); } }
个人直觉是,product参数为何不用ProductEnum来限定呢。而后,再看接口实现类,发现这个getPage方法里直接将product参数赋值给了pojo。而后就调用o/rm框架的query方法了。
当前端页面查询全部product的企业时,页面传的值,不论是"0" 或者 "ALL" 或者 空串,都会导致查的数据有误呀。
终于,墨菲定律又奏效了!在QA测试的时候,bug暴露出来了。
dubbo接口——EnterpriseService.getPage
Result getPage(int pageNo, int pageSize, EnterpriseVO vo,@Nullable ProductEnum product);
web服务端——EnterpriseController.queryPageList
@GetMapping(value = "/list") public Result<?> queryPageList(EnterpriseVO enterprise, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req) { long start = System.currentTimeMillis(); try { log.info("企业管理查询开始>>>>>>>>"); if (StringUtils.isEmpty(enterprise.getProduct())) { return enterpriseService.getPage(pageNo, pageSize, enterprise, null); } else { ProductEnum productEnum = ProductEnum.getBean(enterprise.getProduct()); return enterpriseService.getPage(pageNo, pageSize, enterprise, productEnum); } } finally { log.info("查询企业列表,分页列表查询,查询企业数据,耗时={}",System.currentTimeMillis()-start); } }
咱们能够看到,当product改为枚举类型ProductEnum以后,就限定了调用方只能传特定的枚举值。不像String那样开放,就像我前文阐述的,灵活自由每每会带来更多隐患。
再者,你定义了String,调用方不清楚当所有的时候传什么,可能就按本身固有的方式来传值了。在复杂的查询逻辑里,这样的bug并非一测就能测出来的。QA把bug指出来时,就老实修复吧,别说调用你接口的同窗的传值不对了。因此,给你们一个忠告,写代码能不随意就别随意。你们好才是真的好。
曾经在作支付系统的出款时,由于咱们起初缺少安全意识,整个出款方法有作异常捕获,当捕获到异常后,即会把出款单的付款状态改成失败。无独有偶,碰巧,某个上游支付渠道http接口响应超级慢,频繁致使socket响应超时。socket超时,并不表明服务方没有处理完成。因此,可怕的事情出现了,有一批付款单,渠道方是付款成功,我方呢,由于http超时而置为了付款失败。而后,商户得知是付款失败后,从新发起了出款。。。一夜两个小时的时间,重复出款金额达到8万!
以上这种状况,没有经历过的人,也许体会不到咱们当时的五味陈杂。固然,这不是重点,我要说明的是,这种损失,咱们责怪人家接口,是不起任何做用的,反而让人听着有推卸之嫌。打铁还需自身硬,绣花要得手绵巧,练好基本功很重要!