▄︻┻┳═一Agenda:
▄︻┻┳═一(1/8)[代碼整潔之道]你真的會用枚舉嗎?非也!
▄︻┻┳═一(2/8)枚舉的錯誤用法 之 方法參數
▄︻┻┳═一(3/8)枚舉的錯誤用法 之 方法參數(二)
▄︻┻┳═一(4/8)枚舉的錯誤用法 之 方法返回值
▄︻┻┳═一(5/8)枚舉的錯誤用法 之 方法體內部
▄︻┻┳═一(6/8)枚舉的錯誤用法 之 分支判斷
▄︻┻┳═一(7/8)借助枚舉說一下數據類型定義規范
▄︻┻┳═一(8/8)RPC方法的參數,能用枚舉就請考慮枚舉
我們都知道,在做后台管理系統開發時,很多功能的管理頁都有一堆查詢條件,用來篩選業務數據記錄。其中,最常見的一個,當屬業務數據的狀態了。比如,用戶狀態、企業狀態、任務狀態,每人都能從自己的系統里找出一大堆。
先問大家一個問題:對於一些列表頁,查詢全部狀態的數據,你給服務端接口傳的狀態值是什么?
不用說,大家一般會傳:"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萬!
以上這種情況,沒有經歷過的人,也許體會不到我們當時的五味陳雜。當然,這不是重點,我要說明的是,這種損失,我們責怪人家接口,是不起任何作用的,反而讓人聽着有推卸之嫌。打鐵還需自身硬,綉花要得手綿巧,練好基本功很重要!