Java 8函数式编程模式:不要使用巨长的Stream流

假设你已经使用了lambdas流,巨长的Stream的代码如下:

<b>public</b> List<Product> getFrequentOrderedProducts(List<Order> orders) {
        <b>return</b> orders.stream()
                        .filter(o -> o.getCreationDate().isAfter(LocalDate.now().minusYears(1)))
                        .flatMap(o -> o.getOrderLines().stream())
                        .collect(groupingBy(OrderLine::getProduct, summingInt(OrderLine::getItemCount)))
                        .entrySet()
                        .stream()
                        .filter(e -> e.getValue() >= 10)
                        .map(Entry::getKey)
                        .filter(p -> !p.isDeleted())
                        .filter(p -> !productRepo.getHiddenProductIds().contains(p.getId()))
                        .collect(toList());

以上实现功能是:计算上一年订购产品的次数。现在,只接受频繁订购的产品(> = 10)并返回它们,前提是如果它们没有被逻辑删除或显式隐藏在数据库中。

你写完这段代码很快乐地回家了…

但我们会找到你的!管理层无法解雇你,谁可以读懂这堆代码?!谁愿意和你合作?

这段代码最糟糕的是每行返回不同的类型。除非您在IDE将鼠标悬停其中,否则你将看不到这些类型。

清洁代码最重要的规则之一是:小方法。所以,让我们通过查看我们.collect(..)后面看到的代码,将这个长链分成两个方法.stream()。既然你Collect了一个集合中的项目,为什么我们不通过提取一个好的方法名来解释那个集合是什么?

<b>public</b> List<Product> getFrequentOrderedProducts(List<Order> orders) {
          <b>return</b> getProductCountsOverTheLastYear(orders).entrySet().stream()
                             .filter(e -> e.getValue() >= 10)
                             .map(Entry::getKey)
                             .filter(Product::isNotDeleted)
                             .filter(p -> !productRepo.getHiddenProductIds().contains(p.getId()))
                             .collect(toList());
}
<b>private</b> Map<Product, Integer> getProductCountsOverTheLastYear(List<Order> orders) {
          <b>return</b> orders.stream()
                             .filter(o -> o.getCreationDate().isAfter(LocalDate.now().minusYears(1)))
                             .flatMap(o -> o.getOrderLines().stream())
                             .collect(groupingBy(OrderLine::getProduct, summingInt(OrderLine::getItemCount)));
}

但是,只有这样我们才注意到在第6行,我们可能会在循环中查询外部系统!我的天啊!这是你永远不应该做的事情。

让我们开始流之前先获得hiddenProductIds 列表,我们甚至可以进一步检查产品是否隐藏在Predicate局部变量中:

<b>public</b> List<Product> getFrequentOrderedProducts(List<Order> orders) {
        List<Long> hiddenProductIds = productRepo.getHiddenProductIds();
        Predicate<Product> productIsNotHidden = p -> !hiddenProductIds.contains(p.getId());
        <b>return</b> getProductCountsOverTheLastYear(orders).entrySet().stream()
                        .filter(e -> e.getValue() >= 10)
                        .map(Entry::getKey)
                        .filter(Product::isNotDeleted)
                        .filter(productIsNotHidden)
                        .collect(toList());

还有一件事我们可以做:我们可以命名被频繁订购的产品的流,并使其成为Stream类型的变量。众所周知,这些Stream项目实际上并未在此时进行计算评估,而是仅在结束时.collect()进行计算评估。但是,Stream<>有时不鼓励使用变量,因为粗心的开发人员可能会尝试重新使用它(重新遍历它),因此在执行此操作之前,请确保您的团队完全了解这种常见情况。

<b>public</b> List<Product> getFrequentOrderedProducts(List<Order> orders) {
        List<Long> hiddenProductIds = productRepo.getHiddenProductIds();
        Predicate<Product> productIsNotHidden = p -> !hiddenProductIds.contains(p.getId());
        Stream<Product> frequentProducts = getProductCountsOverTheLastYear(orders).entrySet().stream()
                        .filter(e -> e.getValue() >= 10)
                        .map(Entry::getKey);
        <b>return</b> frequentProducts
                        .filter(Product::isNotDeleted)
                        .filter(productIsNotHidden)
                        .collect(toList());
}
<p>[...]

这里的想法是通过引入解释变量来避免过多的方法链。这意味着 提取方法甚至使用函数或Stream类型的变量,以使代码尽可能清晰地显示给读者。

原文 

https://www.jdon.com/50826

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java 8函数式编程模式:不要使用巨长的Stream流

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址