成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用了Stream后,代碼反而越寫越丑?

開發 后端
Java8的stream流,加上lambda表達式,可以讓代碼變短變美,已經得到了廣泛的應用。我們在寫一些復雜代碼的時候,也有了更多的選擇。

[[422163]]

本文轉載自微信公眾號「小姐姐味道」,作者小姐姐養的狗。轉載本文請聯系小姐姐味道公眾號。

Java8的stream流,加上lambda表達式,可以讓代碼變短變美,已經得到了廣泛的應用。我們在寫一些復雜代碼的時候,也有了更多的選擇。

代碼首先是給人看的,其次才是給機器執行的。代碼寫的是否簡潔明了,是否寫的漂亮,對后續的bug修復和功能擴展,意義重大。很多時候,是否能寫出優秀的代碼,是和工具沒有關系的。代碼是工程師能力和修養的體現,有的人,即使用了stream,用了lambda,代碼也依然寫的像屎一樣。

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

  1. public List<FeedItemVo> getFeeds(Query query,Page page){ 
  2.     List<String> orgiList = new ArrayList<>(); 
  3.      
  4.     List<FeedItemVo> 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<String> orgiList,FeedItemVo vo){ 
  17.     if(isShow && vo.getOrgIds() != null){ 
  18.         orgiList.add(vo.getOrgiName()); 
  19.     } 
  20.     return true

如果覺得不過癮的話,我們再貼上一小段。

  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. result.addAll(vos.stream().collect(Collectors.toList())); 

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

如何改善?除了技術問題,還是一個意識問題。時刻記得,優秀的代碼,首先是可讀的,然后才是功能完善。

1. 合理的換行

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

當然,我們知道這么做是不對的。在lambda的書寫上,有一些套路可以讓代碼更加規整。

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

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

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

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

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

2. 舍得拆分函數

為什么函數能夠越寫越長?是因為技術水平高,能夠駕馭這種變化么?答案是因為懶!由于開發工期或者意識的問題,遇到有新的需求,直接往老的代碼上添加ifelse,即使遇到相似的功能,也直接選擇將原來的代碼拷貝過去。久而久之,碼將不碼。

首先聊一點性能方面的。在JVM中,JIT編譯器會對調用量大,邏輯簡單的代碼進行方法內聯,以減少棧幀的開銷,并能進行更多的優化。所以,短小精悍的函數,其實是對JVM友好的。

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

我將拿一個經常在代碼中出現的實體轉換示例來說明一下。下面的轉換,創建了一個匿名的函數order->{},它在語義表達上,是非常弱的。

  1. public Stream<OrderDto> 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.     }); 

在實際的業務代碼中,這樣的賦值拷貝還有轉換邏輯通常非常的長,我們可以嘗試把dto的創建過程給獨立開來。因為轉換動作不是主要的業務邏輯,我們通常不會關心其中到底發生了啥。

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

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

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

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

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

registarIsCorrect,就可以當作filter的參數。

3. 合理的使用Optional

在Java代碼里,由于NullPointerException不屬于強制捕捉的異常,它會隱藏在代碼里,造成很多不可預料的bug。所以,我們會在拿到一個參數的時候,都會驗證它的合法性,看一下它到底是不是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.     } 

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

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

  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()); 

當你不確定你提供的東西,是不是為空的時候,一個好的習慣是不要返回null,否則調用者的代碼將充滿了null的判斷。我們要把null消滅在萌芽中。

  1. public Optional<String> getUserName() { 
  2.     return Optional.ofNullable(userName); 

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

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

而應該修改成這樣的方式:

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

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

如果想要普及Optional在項目中的使用,腳手架設計者或者review人,需要多下一點功夫。

4. 返回Stream還是返回List?

很多人在設計接口的時候,會陷入兩難的境地。我返回的數據,是直接返回Stream,還是返回List?

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

但對于一個Stream來說,是不可變的,它不會影響原始的集合。對于這種場景,我們推薦直接返回Stream流,而不是返回集合。這種方式還有一個好處,能夠強烈的暗示API使用者,多多使用Stream相關的函數,以便能夠統一代碼風格。

  1. public Stream<User> getAuthUsers(){ 
  2.     ... 
  3.     return Stream.of(users); 

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

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

5. 少用或者不用并行流

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

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

  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; 

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

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

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

如果有人在并行流上跑了耗時的IO業務,那么你即使執行一個簡單的數學運算,也需要排隊。關鍵是,你是沒辦法阻止項目內的其他同學使用并行流的,也無法知曉他干了什么事情。

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

總結

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

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

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

如果覺得Java提供的特性還是不夠,那我們還有一個開源的類庫vavr,提供了更多的可能性,能夠和Stream以及Lambda結合起來,來增強函數編程的體驗。

  1. <dependency> 
  2.     <groupId>io.vavr</groupId> 
  3.     <artifactId>vavr</artifactId> 
  4.     <version>0.10.3</version> 
  5. </dependency> 

 

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

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

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

代碼,除了工作的意義,不過是我們在世界上表達自己想法的另一種方式罷了。如何寫好代碼,不僅僅是個技術問題,更是一個意識問題。

 

作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高并發世界,給你不一樣的味道。

 

責任編輯:武曉燕 來源: 小姐姐味道
相關推薦

2025-02-06 07:30:32

2021-04-27 22:38:41

代碼開發前端

2022-07-29 08:40:20

設計模式責任鏈場景

2018-05-05 08:54:24

2015-05-13 09:52:29

程序員代碼

2009-11-26 10:15:00

IT職場

2021-12-23 23:04:54

手機蘋果國產

2021-01-18 11:09:42

區塊鏈比特幣工具

2024-07-25 12:35:33

2024-07-29 07:04:00

大模型AI訓AI人工智能

2011-05-17 09:45:28

WDM路由器OTN

2022-05-27 11:44:53

JS代碼

2022-05-17 09:17:45

JS 代碼越來越難讀

2021-11-14 22:04:55

iPhone安卓手機

2012-10-15 09:47:11

JavaiOS

2022-04-27 22:17:51

網絡安全信息通信數據安全

2018-04-03 10:24:13

2022-02-13 00:03:06

AndroidAndroid 13安卓

2021-09-18 10:41:45

手機廠商安全
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 农村妇女毛片精品久久久 | av免费网址 | 国产精品18毛片一区二区 | 国产乱码精品1区2区3区 | 日本精品网站 | 欧美日韩国产精品 | 伊人焦久影院 | 国产欧美一区二区三区日本久久久 | 中文字幕国产一区 | 草樱av| 中文字幕高清av | 中文字幕在线第二页 | 99精品久久 | 精品国产一区二区三区在线观看 | 五月婷婷 六月丁香 | 中文字幕一区二区三区不卡在线 | 亚洲高清在线 | 精品国产1区2区3区 在线国产视频 | 国产高清在线精品一区二区三区 | 人人精品 | 成年人免费网站 | 亚洲国产精品成人综合久久久 | 热re99久久精品国产99热 | xxxxx免费视频 | 亚洲精品国产第一综合99久久 | 91国产在线视频在线 | 国产精品久久久久久久午夜片 | 亚洲视频www | 久久一级大片 | 玖玖视频网 | 一区二区三区四区免费观看 | 色999日韩 | www.蜜桃av| 成人免费在线网 | 亚洲一区中文字幕 | 成人毛片一区二区三区 | 欧美精品影院 | 亚洲国产成人精品女人久久久野战 | 久色网 | 夜夜草| 久草免费在线视频 |