重构日记一——flatMap与builder模式


   最近在做一个老系统的优化工作,由于里面的代码实在惨不忍睹,所以在做业务逻辑梳理及系统优化的同时,也开始做起了重构工作。由于没有充足的时间去做重新设计,毕竟在阿里白天要和沙雕产品撕逼,晚上要做正事,留给你优化系统的时间基本上少之又少,因此只能先从一些点开始,逐步进行,由点到面,记录下近期的重构工作。废话不多说,进入正题。

   厂里有好多系统都是跑了好多年了,很多代码如果没问题就不会有人来重构,像这样的JDK1.6前的代码,在我们日常的老系统中几乎随地可见,冗长恶心,极易重复,IDEA到处飘黄,为了这样的for循环去专门写个方法或工具类做抽象又小题大做,而且也比较难复用。这段代码的目的是从reasons这样一个数组里,取出所有reasonId的列表并去重,我们先把几个类型定义列出来,不必要的代码先省略了。

1. 用flatMap化解嵌套循环

1         Set reasonIds = Sets.newHashSet();
2         for (RejectItemReasonDO rejectDO : reasons) {
3             List reasonMap = rejectDO.getRejectMapList();
4             if (CollectionUtils.isNotEmpty(reasonMap)) {
5                 for (RejectMapDO rejectInfo : reasonMap) {
6                     reasonIds.addAll(rejectInfo.getRejectIds());
7                 }
8             }
9         }

    reasons是一个如下类型定义的列表:

 1 public class RejectItemReasonDO extends AbstractBaseDO {
 2 
 3     private static final long serialVersionUID = -111111111L;
 4 
 5     @Setter
 6     @Getter
 7     private List rejectMapList    = Lists.newArrayList();
 8 
 9     ...
10 }
RejectMapDO 定义
 1 @Data
 2 public class RejectMapDO extends BaseDO {
 3 
 4     private static final long serialVersionUID = -181828238283L;
 5 
 6     /**
 7      * 原因ID
 8      */
 9     private Set         rejectIds        = Sets.newHashSet();
10     
11     ...
12 }

   让我们用stream和flatMap改写一下,在这里用flapMap将数据结构中两层的list,化解嵌套循环,代码一下子就干净了很多。

1 Set reasonIds = rejectItemReasonDOS.stream().flatMap(rejectItemReasonDO -> rejectItemReasonDO.getRejectMapList()
2                 .stream()).filter(Objects::nonNull).flatMap(rejectMapDO -> rejectMapDO.getRejectIds()
3                 .stream()).filter(Objects::nonNull).collect(Collectors.toSet());

   

    有没有发现有什么问题?

    这里加了两个 .filter(Objects::nonNull) 过滤空元素而原代码中似乎只有一个,为啥要加两个? 原代码中使用的是 reasonIds.addAll(rejectInfo.getRejectIds()),list的addAll方法如果碰到空元素是会抛异常的,我们不希望这样,因此在重构中顺带修复了一个可能的bug,增加代码的健壮性,当然你也可以说我的数据来源保证了不会为空,那也可以,但是在实际代码的编写中,原则上是不能相信他人和数据的,你懂的。

 2. 用builder模式改写复杂参数构造方式

  我们的代码中有许多对象成员变量较多,在构建参数的时候,往往会有许多get/set操作,极为丑陋。这个时候我们想到了builder模式,builder的主要功能就是用来构建复杂对象,分离对象的表示和实现,从而让代码更整洁。示例代码如下,在需要构建的复杂对象中,创建一个static的builder,或者专门为这个复杂对象创建一个builder类,通过builder的操作封装参数类的操作。

      参数类及builder定义:

 1 @Data
 2 public class GlobalPublishAuditContext implements Serializable {
 3 
 4     private static final long serialVersionUID = 7663807161349892L;
 5 
 6     private Long productId;
 7 
 8     private Long sellerId;
 9 
10     private String operator;
11 
12     private Boolean isPublish;
13 
14     private String  source;
15 
16     private Integer newItemStatus;
17  
18     private IqcExpand newIqcExpand;
19 
20     private Integer newItemSubStatus;
21  
22     private ProductForAuditDTO productAuditDTO;
23 
24     private Map skuMap;
25 
26     private Boolean needSendMTeeAuditMsg;
27 
28     private Boolean needSendScmAuditMsg;
29 
30     private Boolean needDoPostApproveIfQcSkip;
31 
32     private Boolean needManualCheck;
33 
34     private Boolean isImageEdit;
35 
36     private Boolean isFirstActive;
37     /**
38      *
39      */
40     private Map extension;
41 
42     /**
43      * new builder
44      * @return
45      */
46     public static GlobalPublishAuditContextBuilder builder(){
47         return new GlobalPublishAuditContextBuilder();
48     }
49 
50     /**
51      * builder for GlobalPublishAuditContext
52      */
53     public static class GlobalPublishAuditContextBuilder {
54 
55         private GlobalPublishAuditContext context;
56 
57         private ProductForAuditDTO productForAuditDTO;
58 
59         private IqcExpand newIqcExpand;
60 
61         private Map extension;
62 
63         private Map skuMap;
64 
65 
66         public GlobalPublishAuditContextBuilder() {
67             context = new GlobalPublishAuditContext();
68         }
69     
70        ...
71 ...
72 }

  使用Demo如下,是不是比各种get/set操作清爽多了。好了,先写到这里,清理一波烂代码后继续。

1 GlobalPublishAuditContext productAuditContext = GlobalPublishAuditContext.builder()
2                 .isPublish(request.isPublish())
3                 .sellerId(request.getSellerId())
4                 .productId(request.getProductId())
5                 .needManualCheck(request.isNeedManualCheck())
6                 .newItemSubStatus(ItemAuditStatus.APPROVED.getValue()).build();