▄︻┻┳═一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萬!
以上這種狀況,沒有經歷過的人,也許體會不到咱們當時的五味陳雜。固然,這不是重點,我要說明的是,這種損失,咱們責怪人家接口,是不起任何做用的,反而讓人聽着有推卸之嫌。打鐵還需自身硬,繡花要得手綿巧,練好基本功很重要!