重构日记一——flatMap与builder模式
最近在做一个老系统的优化工作,由于里面的代码实在惨不忍睹,所以在做业务逻辑梳理及系统优化的同时,也开始做起了重构工作。由于没有充足的时间去做重新设计,毕竟在阿里白天要和沙雕产品撕逼,晚上要做正事,留给你优化系统的时间基本上少之又少,因此只能先从一些点开始,逐步进行,由点到面,记录下近期的重构工作。废话不多说,进入正题。
厂里有好多系统都是跑了好多年了,很多代码如果没问题就不会有人来重构,像这样的JDK1.6前的代码,在我们日常的老系统中几乎随地可见,冗长恶心,极易重复,IDEA到处飘黄,为了这样的for循环去专门写个方法或工具类做抽象又小题大做,而且也比较难复用。这段代码的目的是从reasons这样一个数组里,取出所有reasonId的列表并去重,我们先把几个类型定义列出来,不必要的代码先省略了。
1. 用flatMap化解嵌套循环
1 SetreasonIds = 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 ListrejectMapList = 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 SetrejectIds = Sets.newHashSet(); 10 11 ... 12 }
让我们用stream和flatMap改写一下,在这里用flapMap将数据结构中两层的list,化解嵌套循环,代码一下子就干净了很多。
1 SetreasonIds = 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 MapskuMap; 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();