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型別的變數,以使程式碼儘可能清晰地顯示給讀者。