成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

阿里P8架構師教你kill代碼重復/大量ifelse

開發(fā) 前端
本文就教你如何優(yōu)雅消除重復代碼并改變你對業(yè)務代碼沒技術含量的觀念。

 

 本文就教你如何優(yōu)雅消除重復代碼并改變你對業(yè)務代碼沒技術含量的觀念。

1 crud 工程師之“痛” 

很多 crud 工程師抱怨業(yè)務開發(fā)沒有技術含量,什么設計模式、高并發(fā)都用不到,就是堆CRUD。每次面試被問到“講講常用設計模式?”,都只能把單例講到精通,其他設計模式即使聽過也只會簡單說說,因為根本沒實際用過。
對于反射、注解,也只是知道在框架中用的很多,但自己又不寫框架,更不知道該如何使用。

  • 設計模式是世界級軟件大師在大型項目的經(jīng)驗所得,是被證實利于維護大型項目的
  • 反射、注解、泛型等高級特性在框架被大量使用,是因為框架往往需要以同一套算法應對不同數(shù)據(jù)結構,而這些特性可以幫助減少重復代碼,也是利于維護

提升項目的可維護性是每個 coder 必須注意的,非常重要的一個手段就是減少代碼重復,因為重復過多會導致:

  • 修改一處忘記修改另一處,造成Bug
  • 有一些代碼并非完全重復,而是相似度高,修改這些類似的代碼容易改(cv)錯,把原本有區(qū)別的地方改成一樣

2 工廠+模板方法模式,消除多if和重復代碼 

2.1 需求 

開發(fā)購物車下單,對不同用戶不同處理:

  • 普通用戶需要收取運費,運費是商品價格的10%,無商品折扣
  • VIP用戶同樣需要收取商品價格10%的快遞費,但購買兩件以上相同商品時,第三件開始享受一定折扣
  • 內部用戶可以免運費,無商品折扣

實現(xiàn)三種類型的購物車業(yè)務邏輯,把入?yún)ap對象(K:商品ID,V:商品數(shù)量),轉換為出參購物車類型Cart。 

2.2 菜鳥實現(xiàn) 

購物車

購物車中的商品

2.2.1 普通用戶

2.2.2 VIP用戶 

VIP用戶能享受同類商品多買的折扣。只需額外處理多買折扣部分。

2.2.3 內部用戶 

免運費、無折扣,只處理商品折扣和運費時的邏輯差異。

三種購物車超過一半代碼重復。
雖然不同類型用戶計算運費和優(yōu)惠的方式不同,但整個購物車的初始化、統(tǒng)計總價、總運費、總優(yōu)惠和支付價格邏輯都一樣。

代碼重復本身不可怕,可怕的是漏改或改錯。
比如,寫VIP用戶購物車的同學發(fā)現(xiàn)商品總價計算有Bug,不應該是把所有Item的price加在一起,而是應該把所有Item的price*quantity相加。
他可能只修VIP用戶購物車的代碼,漏了普通用戶、內部用戶的購物車中重復邏輯實現(xiàn)的相同Bug。

有三個購物車,就需根據(jù)不同用戶類型使用不同購物車。

使用多if實現(xiàn)不同類型用戶調用不同購物車process:

就只能不斷增加更多的購物車類,寫重復的購物車邏輯、寫更多if邏輯嗎?
當然不是,相同的代碼應該只在一處出現(xiàn)! 

2.3 重構秘技 - 模板方法模式 

可以把重復邏輯定義在抽象類,三個購物車只要分別實現(xiàn)不同部分的邏輯。
這其實就是模板方法模式。
在父類中實現(xiàn)購物車處理的流程模板,然后把需要特殊處理的留抽象方法定義,讓子類去實現(xiàn)。由于父類邏輯無法單獨工作,因此需要定義為抽象類。

如下代碼所示,AbstractCart抽象類實現(xiàn)了購物車通用的邏輯,額外定義了兩個抽象方法讓子類去實現(xiàn)。其中,processCouponPrice方法用于計算商品折扣,processDeliveryPrice方法用于計算運費。

有抽象類,三個子類的實現(xiàn)就簡單了。

普通用戶的購物車NormalUserCart,實現(xiàn)0優(yōu)惠和10%運費

VIP用戶的購物車VipUserCart,直接繼承NormalUserCart,只需修改多買優(yōu)惠策

內部用戶購物車InternalUserCart最簡單,直接設置0運費、0折扣

抽象類和三個子類的實現(xiàn)關系圖

2.4 重構秘技之工廠模式 - 消除多if 

既然三個購物車都叫XXXUserCart,可將用戶類型字符串拼接UserCart構成購物車Bean的名稱,然后利用IoC容器,通過Bean的名稱直接獲取到AbstractCart,調用其process方法即可實現(xiàn)通用。

這就是工廠模式,借助Spring容器實現(xiàn):

若有新用戶類型、用戶邏輯,只要新增一個XXXUserCart類繼承AbstractCart,實現(xiàn)特殊的優(yōu)惠和運費處理邏輯即可。

工廠+模板方法模式,消除了重復代碼,還避免修改既有代碼。這就是設計模式中的開閉原則:對修改關閉,對擴展開放。

3 注解+反射消除重復代碼 

3.1 需求 

銀行提供了一些API接口,對參數(shù)的序列化不使用JSON,而需要我們把參數(shù)依次拼在一起構成一個大字符串。

按照銀行提供的API文檔的順序,把所有參數(shù)構成定長的數(shù)據(jù),然后拼接在一起作為整個字符串

因為每種參數(shù)都有固定長度,未達到長度時需填充:

  • 字符串類型的參數(shù)不滿長度部分需要以下劃線右填充,也就是字符串內容靠左
  • 數(shù)字類型的參數(shù)不滿長度部分以0左填充,也就是實際數(shù)字靠右
  • 貨幣類型的表示需要把金額向下舍入2位到分,以分為單位,作為數(shù)字類型同樣進行左填充。
  • 對所有參數(shù)做MD5操作作為簽名(為了方便理解,Demo中不涉及加鹽處理)。

比如,創(chuàng)建用戶方法和支付方法的定義是這樣的:

3.2 菜鳥實現(xiàn) 

直接根據(jù)接口定義實現(xiàn)填充、加簽名、請求調用:

  1. public class BankService { 
  2.  
  3.     // 創(chuàng)建用戶 
  4.     public static String createUser(String name, String identity, String mobile, int age) throws IOException { 
  5.         StringBuilder stringBuilder = new StringBuilder(); 
  6.         // 字符串靠左,多余的地方填充_ 
  7.         stringBuilder.append(String.format("%-10s"name).replace(' ''_')); 
  8.         stringBuilder.append(String.format("%-18s", identity).replace(' ''_')); 
  9.         // 數(shù)字靠右,多余的地方用0填充 
  10.         stringBuilder.append(String.format("%05d", age)); 
  11.         // 字符串靠左 
  12.         stringBuilder.append(String.format("%-11s", mobile).replace(' ''_')); 
  13.         // MD5簽名 
  14.         stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString())); 
  15.         return Request.Post("http://localhost:45678/reflection/bank/createUser"
  16.                 .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON) 
  17.                 .execute().returnContent().asString(); 
  18.     } 
  19.      
  20.     // 支付 
  21.     public static String pay(long userId, BigDecimal amount) throws IOException { 
  22.         StringBuilder stringBuilder = new StringBuilder(); 
  23.         // 數(shù)字靠右 
  24.         stringBuilder.append(String.format("%020d", userId)); 
  25.         // 金額向下舍入2位到分,以分為單位,作為數(shù)字靠右,多余的地方用0填充 
  26.         stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue())); 
  27.         // MD5簽名 
  28.         stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString())); 
  29.         return Request.Post("http://localhost:45678/reflection/bank/pay"
  30.                 .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON) 
  31.                 .execute().returnContent().asString(); 
  32.     } 

這段代碼的重復粒度更細:

  • 三種標準數(shù)據(jù)類型的處理邏輯有重復
  • 處理流程中字符串拼接、加簽和發(fā)請求的邏輯,在所有方法重復
  • 實際方法的入?yún)⒌膮?shù)類型和順序,不一定和接口要求一致,容易出錯
  • 代碼層面針對每一個參數(shù)硬編碼,無法清晰地進行核對,如果參數(shù)達到幾十個、上百個,出錯的概率極大。 

3.3 重構秘技之注解&反射 

針對銀行請求的所有邏輯均使用一套代碼實現(xiàn),不會出現(xiàn)任何重復。

要實現(xiàn)接口邏輯和邏輯實現(xiàn)的剝離,首先要以POJO類定義所有的接口參數(shù)。

創(chuàng)建用戶API的參數(shù)

  1. @Data 
  2. public class CreateUserAPI { 
  3.     private String name
  4.     private String identity; 
  5.     private String mobile; 
  6.     private int age; 

有了接口參數(shù)定義,就能通過自定義注解為接口和所有參數(shù)增加一些元數(shù)據(jù)。

如下定義一個接口API的注解BankAPI,包含接口URL地址和接口說明

再定義一個自定義注解@BankAPIField,描述接口的每一個字段規(guī)范,包含參數(shù)的次序、類型和長度三個屬性:

定義CreateUserAPI類描述創(chuàng)建用戶接口的信息,通過為接口增加@BankAPI注解,來補充接口的URL和描述等元數(shù)據(jù);通過為每一個字段增加@BankAPIField注解,來補充參數(shù)的順序、類型和長度等元數(shù)據(jù):

類似的還有PayAPI類

這2個類繼承的AbstractAPI類是一個空實現(xiàn),因為該案例中的接口無公共數(shù)據(jù)。

通過這倆類,即可在幾秒鐘內完成和API清單表格的核對。若我們的核心翻譯過程(即把注解和接口API序列化為請求需要的字符串的過程)沒問題,只要注解和表格一致,API請求翻譯就不會有問題。

通過注解實現(xiàn)了對API參數(shù)的描述。看反射如何配合注解實現(xiàn)動態(tài)的接口參數(shù)組裝:

  1. private static String remoteCall(AbstractAPI api) throws IOException { 
  2.     // 從類上獲得BankAPI注解,然后拿到其URL屬性,后續(xù)進行遠程調用 
  3.     BankAPI bankAPI = api.getClass().getAnnotation(BankAPI.class); 
  4.     bankAPI.url(); 
  5.     StringBuilder stringBuilder = new StringBuilder(); 
  6.     // 使用stream快速實現(xiàn)獲取類中所有帶BankAPIField注解的字段,并把字段按order屬性排序,然后設置私有字段反射可訪問。 
  7.     Arrays.stream(api.getClass().getDeclaredFields()) //獲得所有字段 
  8.             //查找標記了注解的字段 
  9.             .filter(field -> field.isAnnotationPresent(BankAPIField.class)) 
  10.             // 根據(jù)注解中的order對字段排序 
  11.             .sorted(Comparator.comparingInt(a -> a.getAnnotation(BankAPIField.class).order())) 
  12.             .peek(field -> field.setAccessible(true)) //設置可以訪問私有字段 
  13.             .forEach(field -> { 
  14.                 // 實現(xiàn)了反射獲取注解的值,然后根據(jù)BankAPIField拿到的參數(shù)類型,按照三種標準進行格式化,將所有參數(shù)的格式化邏輯集中在了這一處 
  15.                 // 獲得注解 
  16.                 BankAPIField bankAPIField = field.getAnnotation(BankAPIField.class); 
  17.                 Object value = ""
  18.                 try { 
  19.                     // 反射獲取字段值 
  20.                     value = field.get(api); 
  21.                 } catch (IllegalAccessException e) { 
  22.                     e.printStackTrace(); 
  23.                 } 
  24.                 // 根據(jù)字段類型以正確的填充方式格式化字符串 
  25.                 switch (bankAPIField.type()) { 
  26.                     case "S": { 
  27.                         stringBuilder.append(String.format("%-" + bankAPIField.length() + "s", value.toString()).replace(' ''_')); 
  28.                         break; 
  29.                     } 
  30.                     case "N": { 
  31.                         stringBuilder.append(String.format("%" + bankAPIField.length() + "s", value.toString()).replace(' ''0')); 
  32.                         break; 
  33.                     } 
  34.                     case "M": { 
  35.                         if (!(value instanceof BigDecimal)) 
  36.                             throw new RuntimeException(String.format("{} 的 {} 必須是BigDecimal", api, field)); 
  37.                         stringBuilder.append(String.format("%0" + bankAPIField.length() + "d", ((BigDecimal) value).setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue())); 
  38.                         break; 
  39.                     } 
  40.                     default
  41.                         break; 
  42.                 } 
  43.             }); 
  44.     // 實現(xiàn)參數(shù)加簽和請求調用 
  45.     // 簽名邏輯stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString())); 
  46.     String param = stringBuilder.toString(); 
  47.     long begin = System.currentTimeMillis(); 
  48.     //發(fā)請求 
  49.     String result = Request.Post("http://localhost:45678/reflection" + bankAPI.url()) 
  50.             .bodyString(param, ContentType.APPLICATION_JSON) 
  51.             .execute().returnContent().asString(); 
  52.     log.info("調用銀行API {} url:{} 參數(shù):{} 耗時:{}ms", bankAPI.desc(), bankAPI.url(), param, System.currentTimeMillis() - begin); 
  53.     return result; 

所有處理參數(shù)排序、填充、加簽、請求調用的核心邏輯,都匯聚在remoteCall。有這方法,BankService中每一個接口的實現(xiàn)就非常簡單了,只是參數(shù)的組裝,然后調用remoteCall。

涉及類結構性的通用處理,都可按照該模式減少重復代碼。

  • 反射使得我們在不知類結構時,按固定邏輯處理類成員
  • 注解給我們?yōu)檫@些成員補充元數(shù)據(jù)的能力,使得我們利用反射實現(xiàn)通用邏輯的時候,可以從外部獲得更多我們關心的數(shù)據(jù)

4 屬性拷貝 

對于三層架構系統(tǒng),層間解耦及每層對數(shù)據(jù)的不同需求,每層都會有自己的POJO實體。
手動寫這些實體之間的賦值代碼,容易出錯。對于復雜業(yè)務系統(tǒng),實體有幾十甚至幾百個屬性也很正常。比如ComplicatedOrderDTO,描述一個訂單中幾十個屬性。如果轉換為一個類似的DO,復制其中大部分的字段,然后把數(shù)據(jù)入庫,勢必需要進行很多屬性映射賦值操作。就像這樣,密密麻麻的代碼是不是已經(jīng)讓你頭暈了?

  1. ComplicatedOrderDTO orderDTO = new ComplicatedOrderDTO(); 
  2. ComplicatedOrderDO orderDO = new ComplicatedOrderDO(); 
  3. orderDO.setAcceptDate(orderDTO.getAcceptDate()); 
  4. orderDO.setAddress(orderDTO.getAddress()); 
  5. orderDO.setAddressId(orderDTO.getAddressId()); 
  6. orderDO.setCancelable(orderDTO.isCancelable()); 
  7. orderDO.setCommentable(orderDTO.isComplainable()); //屬性錯誤 
  8. orderDO.setComplainable(orderDTO.isCommentable()); //屬性錯誤 
  9. orderDO.setCancelable(orderDTO.isCancelable()); 
  10. orderDO.setCouponAmount(orderDTO.getCouponAmount()); 
  11. orderDO.setCouponId(orderDTO.getCouponId()); 
  12. orderDO.setCreateDate(orderDTO.getCreateDate()); 
  13. orderDO.setDirectCancelable(orderDTO.isDirectCancelable()); 
  14. orderDO.setDeliverDate(orderDTO.getDeliverDate()); 
  15. orderDO.setDeliverGroup(orderDTO.getDeliverGroup()); 
  16. orderDO.setDeliverGroupOrderStatus(orderDTO.getDeliverGroupOrderStatus()); 
  17. orderDO.setDeliverMethod(orderDTO.getDeliverMethod()); 
  18. orderDO.setDeliverPrice(orderDTO.getDeliverPrice()); 
  19. orderDO.setDeliveryManId(orderDTO.getDeliveryManId()); 
  20. orderDO.setDeliveryManMobile(orderDO.getDeliveryManMobile()); //對象錯誤 
  21. orderDO.setDeliveryManName(orderDTO.getDeliveryManName()); 
  22. orderDO.setDistance(orderDTO.getDistance()); 
  23. orderDO.setExpectDate(orderDTO.getExpectDate()); 
  24. orderDO.setFirstDeal(orderDTO.isFirstDeal()); 
  25. orderDO.setHasPaid(orderDTO.isHasPaid()); 
  26. orderDO.setHeadPic(orderDTO.getHeadPic()); 
  27. orderDO.setLongitude(orderDTO.getLongitude()); 
  28. orderDO.setLatitude(orderDTO.getLongitude()); //屬性賦值錯誤 
  29. orderDO.setMerchantAddress(orderDTO.getMerchantAddress()); 
  30. orderDO.setMerchantHeadPic(orderDTO.getMerchantHeadPic()); 
  31. orderDO.setMerchantId(orderDTO.getMerchantId()); 
  32. orderDO.setMerchantAddress(orderDTO.getMerchantAddress()); 
  33. orderDO.setMerchantName(orderDTO.getMerchantName()); 
  34. orderDO.setMerchantPhone(orderDTO.getMerchantPhone()); 
  35. orderDO.setOrderNo(orderDTO.getOrderNo()); 
  36. orderDO.setOutDate(orderDTO.getOutDate()); 
  37. orderDO.setPayable(orderDTO.isPayable()); 
  38. orderDO.setPaymentAmount(orderDTO.getPaymentAmount()); 
  39. orderDO.setPaymentDate(orderDTO.getPaymentDate()); 
  40. orderDO.setPaymentMethod(orderDTO.getPaymentMethod()); 
  41. orderDO.setPaymentTimeLimit(orderDTO.getPaymentTimeLimit()); 
  42. orderDO.setPhone(orderDTO.getPhone()); 
  43. orderDO.setRefundable(orderDTO.isRefundable()); 
  44. orderDO.setRemark(orderDTO.getRemark()); 
  45. orderDO.setStatus(orderDTO.getStatus()); 
  46. orderDO.setTotalQuantity(orderDTO.getTotalQuantity()); 
  47. orderDO.setUpdateTime(orderDTO.getUpdateTime()); 
  48. orderDO.setName(orderDTO.getName()); 
  49. orderDO.setUid(orderDTO.getUid()); 

如果原始的DTO有100個字段,我們需要復制90個字段到DO中,保留10個不賦值,最后應該如何校驗正確性呢?

  • 數(shù)數(shù)嗎?即使數(shù)出有90行代碼,也不一定正確,因為屬性可能重復賦值
  • 有時字段名相近,比如complainable和commentable,容易搞反
  • 對兩個目標字段重復賦值相同的來源字段
  • 明明要把DTO的值賦值到DO中,卻在set的時候從DO自己取值,導致賦值無效

使用類似BeanUtils這種Mapping工具來做Bean的轉換,copyProperties方法還允許我們提供需要忽略的屬性:

5 總結 

重復代碼多了總有一天會出錯。

  • 有多個并行的類實現(xiàn)相似的代碼邏輯
  • 考慮提取相同邏輯在父類中實現(xiàn),差異邏輯通過抽象方法留給子類實現(xiàn)。使用類似的模板方法把相同的流程和邏輯固定成模板,保留差異的同時盡可能避免代碼重復。同時,可以使用Spring的IoC特性注入相應的子類,來避免實例化子類時的大量if…else代碼。
  • 使用硬編碼的方式重復實現(xiàn)相同的數(shù)據(jù)處理算法
  • 考慮把規(guī)則轉換為自定義注解,作為元數(shù)據(jù)對類或對字段、方法進行描述,然后通過反射動態(tài)讀取這些元數(shù)據(jù)、字段或調用方法,實現(xiàn)規(guī)則參數(shù)和規(guī)則定義的分離。也就是說,把變化的部分也就是規(guī)則的參數(shù)放入注解,規(guī)則的定義統(tǒng)一處理。
  • 業(yè)務代碼中常見的DO、DTO、VO轉換時大量字段的手動賦值,遇到有上百個屬性的復雜類型,非常非常容易出錯

不要手動進行賦值,考慮使用Bean映射工具進行。此外,還可以考慮采用單元測試對所有字段進行賦值正確性校驗。

代碼重復度是評估一個項目質量的重要指標,如果一個項目幾乎沒有任何重復代碼,那么它內部抽象一定非常好。重構時,首要任務是消除重復。

參考

  • 《重構》
  • 搞定代碼重復的三個絕招
  • https://blog.csdn.net/qq_32447301/article/details/107774036 

 

責任編輯:姜華 來源: JavaEdge
相關推薦

2018-09-12 20:12:11

MySQL慢查詢優(yōu)化索引優(yōu)化

2019-07-22 22:22:02

架構運維技術

2019-02-26 12:40:10

程序員架構師阿里

2018-08-07 10:04:11

數(shù)據(jù)庫分布式緩存Redis

2018-08-28 16:22:57

數(shù)據(jù)庫NoSQLSQL

2018-08-28 12:37:27

數(shù)據(jù)庫數(shù)據(jù)庫中間件MySQL

2020-10-26 09:02:45

如何校驗參數(shù)

2021-04-19 08:25:03

架構師公司系統(tǒng)

2021-09-30 11:58:05

阿里P8雨水

2020-01-14 14:37:29

JVMJava體系

2019-09-02 09:21:16

Zookeeper架構師集群

2020-11-03 09:10:18

JUC-Future

2019-02-22 10:00:45

Java開發(fā)代碼

2021-01-18 08:40:41

年薪阿里團隊

2013-07-18 16:18:00

架構師

2020-01-16 15:35:00

高并發(fā)架構服務器

2021-08-20 10:53:21

技術阿里P8

2019-08-22 10:54:05

分布式系統(tǒng)架構

2020-01-21 09:51:32

結構化思維互聯(lián)網(wǎng)

2021-02-01 07:40:55

架構師阿里技專家
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品久久久久久久久免费樱桃 | 日本电影韩国电影免费观看 | 精品国产一区二区三区久久狼黑人 | 日本a网站 | 北条麻妃99精品青青久久主播 | 精品在线一区 | 午夜影院黄 | 久久中文免费视频 | 日韩av免费在线观看 | 欧美亚洲国产精品 | 久www| 国产日韩精品一区 | 中文字幕欧美一区二区 | 国产日韩一区二区 | 亚洲 中文 欧美 日韩 在线观看 | 欧美黑人体内she精在线观看 | 最近日韩中文字幕 | 久艹网站 | 日韩电影a | 香蕉视频91| 99免费在线视频 | 你懂的免费在线 | 日韩高清国产一区在线 | 小h片免费观看久久久久 | 日韩成人在线看 | 好姑娘高清在线观看电影 | 色资源在线观看 | 人人做人人澡人人爽欧美 | sese视频在线观看 | 久久精品国产一区二区电影 | 久久久久久国产精品免费免费男同 | 欧美精品一区二区三区在线播放 | 妖精视频一区二区三区 | 欧美日韩国产在线观看 | 成人性视频免费网站 | 久久人体视频 | 福利一区在线观看 | 特级一级黄色片 | 国产高清一区二区三区 | 在线亚洲人成电影网站色www | 国产黄色小视频 |