日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
用了Stream后,代碼反而越寫越丑?

本文轉(zhuǎn)載自微信公眾號(hào)「小姐姐味道」,作者小姐姐養(yǎng)的狗。轉(zhuǎn)載本文請(qǐng)聯(lián)系小姐姐味道公眾號(hào)。

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、儀征網(wǎng)絡(luò)推廣、微信小程序定制開發(fā)、儀征網(wǎng)絡(luò)營(yíng)銷、儀征企業(yè)策劃、儀征品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供儀征建站搭建服務(wù),24小時(shí)服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com

Java8的stream流,加上lambda表達(dá)式,可以讓代碼變短變美,已經(jīng)得到了廣泛的應(yīng)用。我們?cè)趯懸恍?fù)雜代碼的時(shí)候,也有了更多的選擇。

代碼首先是給人看的,其次才是給機(jī)器執(zhí)行的。代碼寫的是否簡(jiǎn)潔明了,是否寫的漂亮,對(duì)后續(xù)的bug修復(fù)和功能擴(kuò)展,意義重大。很多時(shí)候,是否能寫出優(yōu)秀的代碼,是和工具沒有關(guān)系的。代碼是工程師能力和修養(yǎng)的體現(xiàn),有的人,即使用了stream,用了lambda,代碼也依然寫的像屎一樣。

不信,我們來參觀一下一段美妙的代碼。好家伙,filter里面竟然帶著瀟灑的邏輯。

 
 
 
  1. public List getFeeds(Query query,Page page){
  2.     List orgiList = new ArrayList<>();
  3.     
  4.     List collect = page.getRecords().stream()
  5.     .filter(this::addDetail)
  6.     .map(FeedItemVo::convertVo)
  7.     .filter(vo -> this.addOrgNames(query.getIsSlow(),orgiList,vo))
  8.     .collect(Collectors.toList());
  9.     //...其他邏輯
  10.     return collect;
  11. }
  12. private boolean addDetail(FeedItem feed){
  13.     vo.setItemCardConf(service.getById(feed.getId()));
  14.     return true;
  15. }
  16. private boolean addOrgNames(boolean isSlow,List orgiList,FeedItemVo vo){
  17.     if(isShow && vo.getOrgIds() != null){
  18.         orgiList.add(vo.getOrgiName());
  19.     }
  20.     return true;
  21. }

如果覺得不過癮的話,我們?cè)儋N上一小段。

 
 
 
  1. if (!CollectionUtils.isEmpty(roleNameStrList) && roleNameStrList.contains(REGULATORY_ROLE)) {
  2.     vos = vos.stream().filter(
  3.            vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
  4.                     && vo.getTaskName() != null)
  5.            .collect(Collectors.toList());
  6. } else {
  7.     vos = vos.stream().filter(vo -> vo.getIsSelect()
  8.            && vo.getTaskName() != null)
  9.            .collect(Collectors.toList());
  10.     vos = vos.stream().filter(
  11.             vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
  12.                     && vo.getTaskName() != null)
  13.            .collect(Collectors.toList());
  14. }
  15. result.addAll(vos.stream().collect(Collectors.toList()));

代碼能跑,但多畫蛇添足。該縮進(jìn)的不縮進(jìn),該換行的不換行,說什么也算不上好代碼。

如何改善?除了技術(shù)問題,還是一個(gè)意識(shí)問題。時(shí)刻記得,優(yōu)秀的代碼,首先是可讀的,然后才是功能完善。

1. 合理的換行

在Java中,同樣的功能,代碼行數(shù)寫的少了,并不見得你的代碼就好。由于Java使用;作為代碼行的分割,如果你喜歡的話,甚至可以將整個(gè)Java文件搞成一行,就像是混淆后的JavaScript一樣。

當(dāng)然,我們知道這么做是不對(duì)的。在lambda的書寫上,有一些套路可以讓代碼更加規(guī)整。

 
 
 
  1. Stream.of("i", "am", "xjjdog").map(toUpperCase()).map(toBase64()).collect(joining(" "));

上面這種代碼的寫法,就非常的不推薦。除了在閱讀上容易造成障礙,在代碼發(fā)生問題的時(shí)候,比如拋出異常,在異常堆棧中找問題也會(huì)變的困難。所以,我們要將它優(yōu)雅的換行。

 
 
 
  1. Stream.of("i", "am", "xjjdog")
  2.     .map(toUpperCase())
  3.     .map(toBase64())
  4.     .collect(joining(" "));

不要認(rèn)為這種改造很沒有意義,或者認(rèn)為這樣的換行是理所當(dāng)然的。在我平常的代碼review中,這種糅雜在一塊的代碼,真的是數(shù)不勝數(shù),你完全搞不懂寫代碼的人的意圖。

合理的換行是代碼青春永駐的配方。

2. 舍得拆分函數(shù)

為什么函數(shù)能夠越寫越長(zhǎng)?是因?yàn)榧夹g(shù)水平高,能夠駕馭這種變化么?答案是因?yàn)閼?由于開發(fā)工期或者意識(shí)的問題,遇到有新的需求,直接往老的代碼上添加ifelse,即使遇到相似的功能,也直接選擇將原來的代碼拷貝過去。久而久之,碼將不碼。

首先聊一點(diǎn)性能方面的。在JVM中,JIT編譯器會(huì)對(duì)調(diào)用量大,邏輯簡(jiǎn)單的代碼進(jìn)行方法內(nèi)聯(lián),以減少棧幀的開銷,并能進(jìn)行更多的優(yōu)化。所以,短小精悍的函數(shù),其實(shí)是對(duì)JVM友好的。

在可讀性方面,將一大坨代碼,拆分成有意義的函數(shù),是非常有必要的,也是重構(gòu)的精髓所在。在lambda表達(dá)式中,這種拆分更是有必要。

我將拿一個(gè)經(jīng)常在代碼中出現(xiàn)的實(shí)體轉(zhuǎn)換示例來說明一下。下面的轉(zhuǎn)換,創(chuàng)建了一個(gè)匿名的函數(shù)order->{},它在語義表達(dá)上,是非常弱的。

 
 
 
  1. public Stream getOrderByUser(String userId){
  2.     return orderRepo.findOrderByUser().stream()
  3.         .map(order-> {
  4.             OrderDto dto = new OrderDto();
  5.             dto.setOrderId(order.getOrderId());
  6.             dto.setTitle(order.getTitle().split("#")[0]);
  7.             dto.setCreateDate(order.getCreateDate().getTime());
  8.             return dto;
  9.     });
  10. }

在實(shí)際的業(yè)務(wù)代碼中,這樣的賦值拷貝還有轉(zhuǎn)換邏輯通常非常的長(zhǎng),我們可以嘗試把dto的創(chuàng)建過程給獨(dú)立開來。因?yàn)檗D(zhuǎn)換動(dòng)作不是主要的業(yè)務(wù)邏輯,我們通常不會(huì)關(guān)心其中到底發(fā)生了啥。

 
 
 
  1. public Stream getOrderByUser(String userId){
  2.     return orderRepo.findOrderByUser().stream()
  3.         .map(this::toOrderDto);
  4. }
  5. public OrderDto toOrderDto(Order order){
  6.     OrderDto dto = new OrderDto();
  7.             dto.setOrderId(order.getOrderId());
  8.             dto.setTitle(order.getTitle().split("#")[0]);
  9.             dto.setCreateDate(order.getCreateDate().getTime());
  10.     return dto;
  11. }

這樣的轉(zhuǎn)換代碼還是有點(diǎn)丑。但如果OrderDto的構(gòu)造函數(shù),參數(shù)就是Order的話public OrderDto(Order order),那我們就可以把真?zhèn)€轉(zhuǎn)換邏輯從主邏輯中移除出去,整個(gè)代碼就可以非常的清爽。

 
 
 
  1. public Stream getOrderByUser(String userId){
  2.     return orderRepo.findOrderByUser().stream()
  3.         .map(OrderDto::new);
  4. }

除了map和flatMap的函數(shù)可以做語義化,更多的filter可以使用Predicate去代替。比如:

 
 
 
  1. Predicate registarIsCorrect = reg -> 
  2.     reg.getRegulationId() != null 
  3.     && reg.getRegulationId() != 0 
  4.     && reg.getType() == 0;

registarIsCorrect,就可以當(dāng)作filter的參數(shù)。

3. 合理的使用Optional

在Java代碼里,由于NullPointerException不屬于強(qiáng)制捕捉的異常,它會(huì)隱藏在代碼里,造成很多不可預(yù)料的bug。所以,我們會(huì)在拿到一個(gè)參數(shù)的時(shí)候,都會(huì)驗(yàn)證它的合法性,看一下它到底是不是null,代碼中到處充滿了這樣的代碼。

 
 
 
  1. if(null == obj)
  2. if(null == user.getName() || "".equals(user.getName()))
  3.     
  4. if (order != null) {
  5.     Logistics logistics = order.getLogistics();
  6.     if(logistics != null){
  7.         Address address = logistics.getAddress();
  8.         if (address != null) {
  9.             Country country = address.getCountry();
  10.             if (country != null) {
  11.                 Isocode isocode = country.getIsocode();
  12.                 if (isocode != null) {
  13.                     return isocode.getNumber();
  14.                 }
  15.             }
  16.         }
  17.     }
  18. }

Java8引入了Optional類,用于解決臭名昭著的空指針問題。實(shí)際上,它是一個(gè)包裹類,提供了幾個(gè)方法可以去判斷自身的空值問題。

上面比較復(fù)雜的代碼示例,就可以替換成下面的代碼。

 
 
 
  1. String result = Optional.ofNullable(order)
  2.      .flatMap(order->order.getLogistics())
  3.      .flatMap(logistics -> logistics.getAddress())
  4.      .flatMap(address -> address.getCountry())
  5.      .map(country -> country.getIsocode())
  6.      .orElse(Isocode.CHINA.getNumber());

當(dāng)你不確定你提供的東西,是不是為空的時(shí)候,一個(gè)好的習(xí)慣是不要返回null,否則調(diào)用者的代碼將充滿了null的判斷。我們要把null消滅在萌芽中。

 
 
 
  1. public Optional getUserName() {
  2.     return Optional.ofNullable(userName);
  3. }

另外,我們要盡量的少使用Optional的get方法,它同樣會(huì)讓代碼變丑。比如:

 
 
 
  1. Optional userName = "xjjdog";
  2. String defaultEmail = userName.get() == null ? "":userName.get() + "@xjjdog.cn";

而應(yīng)該修改成這樣的方式:

 
 
 
  1. Optional userName = "xjjdog";
  2. String defaultEmail = userName
  3.     .map(e -> e + "@xjjdog.cn")
  4.     .orElse("");

那為什么我們的代碼中,依然充滿了各式各樣的空值判斷?即使在非常專業(yè)和流行的代碼中?一個(gè)非常重要的原因,就是Optional的使用需要保持一致。當(dāng)其中的一環(huán)出現(xiàn)了斷層,大多數(shù)編碼者都會(huì)以模仿的方式去寫一些代碼,以便保持與原代碼風(fēng)格的一致。

如果想要普及Optional在項(xiàng)目中的使用,腳手架設(shè)計(jì)者或者review人,需要多下一點(diǎn)功夫。

4. 返回Stream還是返回List?

很多人在設(shè)計(jì)接口的時(shí)候,會(huì)陷入兩難的境地。我返回的數(shù)據(jù),是直接返回Stream,還是返回List?

如果你返回的是一個(gè)List,比如ArrayList,那么你去修改這個(gè)List,會(huì)直接影響里面的值,除非你使用不可變的方式對(duì)其進(jìn)行包裹。同樣的,數(shù)組也有這樣的問題。

但對(duì)于一個(gè)Stream來說,是不可變的,它不會(huì)影響原始的集合。對(duì)于這種場(chǎng)景,我們推薦直接返回Stream流,而不是返回集合。這種方式還有一個(gè)好處,能夠強(qiáng)烈的暗示API使用者,多多使用Stream相關(guān)的函數(shù),以便能夠統(tǒng)一代碼風(fēng)格。

 
 
 
  1. public Stream getAuthUsers(){
  2.     ...
  3.     return Stream.of(users);
  4. }

不可變集合是一個(gè)強(qiáng)需求,它能防止外部的函數(shù)對(duì)這些集合進(jìn)行不可預(yù)料的修改。在guava中,就有大量的Immutable類支持這種包裹。再舉一個(gè)例子,Java的枚舉,它的values()方法,為了防止外面的api對(duì)枚舉進(jìn)行修改,就只能拷貝一份數(shù)據(jù)。

但是,如果你的api,面向的是最終的用戶,不需要再做修改,那么直接返回List就是比較好的,比如函數(shù)在Controller中。

5. 少用或者不用并行流

Java的并行流有很多問題,這些問題對(duì)并發(fā)編程不熟悉的人高頻率踩坑。不是說并行流不好,但如果你發(fā)現(xiàn)你的團(tuán)隊(duì),老在這上面栽跟頭,那你也會(huì)毫不猶豫的降低推薦的頻率。

并行流一個(gè)老生常談的問題,就是線程安全問題。在迭代的過程中,如果使用了線程不安全的類,那么就容易出現(xiàn)問題。比如下面這段代碼,大多數(shù)情況下運(yùn)行都是錯(cuò)誤的。

 
 
 
  1. List transform(List source){
  2.  List dst = new ArrayList<>();
  3.  if(CollectionUtils.isEmpty()){
  4.   return dst;
  5.  }
  6.  source.stream.
  7.   .parallel()
  8.   .map(..)
  9.   .filter(..)
  10.   .foreach(dst::add);
  11.  return dst;
  12. }

你可能會(huì)說,我把foreach改成collect就行了。但是注意,很多開發(fā)人員是沒有這樣的意識(shí)的。既然api提供了這樣的函數(shù),它在邏輯上又講得通,那你是阻擋不住別人這么用的。

并行流還有一個(gè)濫用問題,就是在迭代中執(zhí)行了耗時(shí)非常長(zhǎng)的IO任務(wù)。在用并行流之前,你有沒有一個(gè)疑問?既然是并行,那它的線程池是怎么配置的?

很不幸,所有的并行流,共用了一個(gè)ForkJoinPool。它的大小,默認(rèn)是CPU個(gè)數(shù)-1,大多數(shù)情況下,是不夠用的。

如果有人在并行流上跑了耗時(shí)的IO業(yè)務(wù),那么你即使執(zhí)行一個(gè)簡(jiǎn)單的數(shù)學(xué)運(yùn)算,也需要排隊(duì)。關(guān)鍵是,你是沒辦法阻止項(xiàng)目?jī)?nèi)的其他同學(xué)使用并行流的,也無法知曉他干了什么事情。

那怎么辦?我的做法是一刀切,直接禁止。雖然殘忍了一些,但它避免了問題。

總結(jié)

Java8加入的Stream功能非常棒,我們不需要再羨慕其他語言,寫起代碼來也更加行云流水。雖然看著很厲害的樣子,但它也只不過是一個(gè)語法糖而已,不要寄希望于用了它就獲得了超能力。

隨著Stream的流行,我們的代碼里這樣的代碼也越來越多。但現(xiàn)在很多代碼,使用了Stream和Lambda以后,代碼反而越寫越糟,又臭又長(zhǎng)以至于不能閱讀。沒其他原因,濫用了!

總體來說,使用Stream和Lambda,要保證主流程的簡(jiǎn)單清晰,風(fēng)格要統(tǒng)一,合理的換行,舍得加函數(shù),正確的使用Optional等特性,而且不要在filter這樣的函數(shù)里加代碼邏輯。在寫代碼的時(shí)候,要有意識(shí)的遵循這些小tips,簡(jiǎn)潔優(yōu)雅就是生產(chǎn)力。

如果覺得Java提供的特性還是不夠,那我們還有一個(gè)開源的類庫(kù)vavr,提供了更多的可能性,能夠和Stream以及Lambda結(jié)合起來,來增強(qiáng)函數(shù)編程的體驗(yàn)。

 
 
 
  1.     io.vavr
  2.     vavr
  3.     0.10.3

但無論提供了如何強(qiáng)大的api和編程方式,都扛不住小伙伴的濫用。這些代碼,在邏輯上完全是說的通的,但就是看起來別扭,維護(hù)起來費(fèi)勁。

寫一堆垃圾lambda代碼,是虐待同事最好的方式,也是埋坑的不二選擇。

寫代碼嘛,就如同說話、聊天一樣。大家干著同樣的工作,有的人說話好聽顏值又高,大家都喜歡和他聊天;有的人不好好說話,哪里痛戳哪里,雖然他存在著但大家都討厭。

代碼,除了工作的意義,不過是我們?cè)谑澜缟媳磉_(dá)自己想法的另一種方式罷了。如何寫好代碼,不僅僅是個(gè)技術(shù)問題,更是一個(gè)意識(shí)問題。

作者簡(jiǎn)介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號(hào)。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。


標(biāo)題名稱:用了Stream后,代碼反而越寫越丑?
當(dāng)前URL:http://www.dlmjj.cn/article/djgsdcg.html