吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

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

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-08-23 15:08 ? 次閱讀

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

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

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

publicListgetFeeds(Queryquery,Pagepage){
ListorgiList=newArrayList<>();

Listcollect=page.getRecords().stream()
.filter(this::addDetail)
.map(FeedItemVo::convertVo)
.filter(vo->this.addOrgNames(query.getIsSlow(),orgiList,vo))
.collect(Collectors.toList());
//...其他邏輯
returncollect;
}

privatebooleanaddDetail(FeedItemfeed){
vo.setItemCardConf(service.getById(feed.getId()));
returntrue;
}

privatebooleanaddOrgNames(booleanisSlow,ListorgiList,FeedItemVovo){
if(isShow&&vo.getOrgIds()!=null){
orgiList.add(vo.getOrgiName());
}
returntrue;
}

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

if(!CollectionUtils.isEmpty(roleNameStrList)&&roleNameStrList.contains(REGULATORY_ROLE)){
vos=vos.stream().filter(
vo->!CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
&&vo.getTaskName()!=null)
.collect(Collectors.toList());
}else{
vos=vos.stream().filter(vo->vo.getIsSelect()
&&vo.getTaskName()!=null)
.collect(Collectors.toList());
vos=vos.stream().filter(
vo->!CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
&&vo.getTaskName()!=null)
.collect(Collectors.toList());
}
result.addAll(vos.stream().collect(Collectors.toList()));

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

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

1. 合理的換行

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

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

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

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

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

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

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

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

2. 舍得拆分函數

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

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

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

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

publicStreamgetOrderByUser(StringuserId){
returnorderRepo.findOrderByUser().stream()
.map(order->{
OrderDtodto=newOrderDto();
dto.setOrderId(order.getOrderId());
dto.setTitle(order.getTitle().split("#")[0]);
dto.setCreateDate(order.getCreateDate().getTime());
returndto;
});
}

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

publicStreamgetOrderByUser(StringuserId){
returnorderRepo.findOrderByUser().stream()
.map(this::toOrderDto);
}
publicOrderDtotoOrderDto(Orderorder){
OrderDtodto=newOrderDto();
dto.setOrderId(order.getOrderId());
dto.setTitle(order.getTitle().split("#")[0]);
dto.setCreateDate(order.getCreateDate().getTime());
returndto;
}

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

publicStreamgetOrderByUser(StringuserId){
returnorderRepo.findOrderByUser().stream()
.map(OrderDto::new);
}

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

PredicateregistarIsCorrect=reg->
reg.getRegulationId()!=null
&®.getRegulationId()!=0
&®.getType()==0;

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

3. 合理的使用Optional

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

if(null==obj)
if(null==user.getName()||"".equals(user.getName()))

if(order!=null){
Logisticslogistics=order.getLogistics();
if(logistics!=null){
Addressaddress=logistics.getAddress();
if(address!=null){
Countrycountry=address.getCountry();
if(country!=null){
Isocodeisocode=country.getIsocode();
if(isocode!=null){
returnisocode.getNumber();
}
}
}
}
}

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

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

Stringresult=Optional.ofNullable(order)
.flatMap(order->order.getLogistics())
.flatMap(logistics->logistics.getAddress())
.flatMap(address->address.getCountry())
.map(country->country.getIsocode())
.orElse(Isocode.CHINA.getNumber());

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

publicOptionalgetUserName(){
returnOptional.ofNullable(userName);
}

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

OptionaluserName="xjjdog";
StringdefaultEmail=userName.get()==null?"":userName.get()+"@xjjdog.cn";

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

OptionaluserName="xjjdog";
StringdefaultEmail=userName.map(e->e+"@xjjdog.cn").orElse("");

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

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

4. 返回Stream還是返回List?

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

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

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

publicStreamgetAuthUsers(){...returnStream.of(users);}

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

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

5. 少用或者不用并行流

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

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

Listtransform(Listsource){Listdst=newArrayList<>();if(CollectionUtils.isEmpty()){returndst;}source.stream..parallel().map(..).filter(..).foreach(dst::add);returndst;}

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

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

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

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

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

總結

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

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

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

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

<dependency>
<groupId>io.vavrgroupId>
<artifactId>vavrartifactId>
<version>0.10.3version>
dependency>

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

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

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

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


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2974

    瀏覽量

    105142
  • 代碼
    +關注

    關注

    30

    文章

    4825

    瀏覽量

    69049
  • Stream
    +關注

    關注

    0

    文章

    20

    瀏覽量

    8006

原文標題:用了Stream后,代碼反而越寫越丑?

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    鋒能否取代Protel?

    ,PCB部分也符合用戶的基本要求,高端功能,也處于后期的研發階段。目前就功能而言,用戶完全可以運用青鋒進行PCB板的設計。不過考慮過往用戶對Protel的依賴、用戶的個人喜好,及部分用戶對國產技術產品的偏見,青鋒能否取代Protel的問題,就需要用戶自己詳細體驗
    發表于 02-23 17:22

    請教:距離近報警聲急促是什么原理

    各位高人。哪位知道倒車雷達或防丟器,距離近報警聲急促是啥原理。有沒有可以用WIFI標識卡實現這些功能的設備。謝謝
    發表于 04-12 06:47

    社區之星:努力,幸運的24不可說

    ,平時論壇的活動也是積極參與,所以小編在此深深鞠躬,非常感謝24不可說對論壇的付出。帖子前,小編和24不可說交流了一下,小編問他最喜歡的一句話是什么,他說是“努力,幸運。”,所以希望他是——
    發表于 10-14 17:03

    保險絲“小”吃香

    至今,保險絲已經朝著越來“小”的方向發展了,看來這年頭還真的是“小”吃香這是為何呢?眾所周知,現如今的電腦已經慢慢走向了微型時代,保險絲也不例外,隨著經濟科技的不斷發展和進步,保險絲也變得
    發表于 05-31 10:11

    程序員優秀嗎?

    注重自己的身體,但仍然還不足夠。留言:Frank Silbermann在Pertinent 文章留下的評論:速度快的程序員能通過他們高人一等的短期記憶來編出雜亂無章的代碼、迅速的完成任務。這些程序員當
    發表于 10-25 10:04

    失真,什么是交失真,交失真的原理

    失真,什么叫交失真 在分析時,是把三級管的門限電壓看作為零,但實際中,門限電壓不能為零,且電壓和電流的關系不是線性
    發表于 09-17 08:14 ?3.1w次閱讀
    交<b class='flag-5'>越</b>失真,什么是交<b class='flag-5'>越</b>失真,交<b class='flag-5'>越</b>失真的原理

    新凱安裝教程

    新凱安裝教,使用方便,簡潔,新凱安裝教程
    發表于 11-18 16:27 ?11次下載

    怎么使用SSD才能避免越慢?

    江湖傳言:SSD越慢。沒錯,其實這是有科學依據的:可用閃存空間富裕時,SSD是無需做GC的,因為總有空閑的空間可寫。SSD使用早期,由于沒有觸發GC,無需額外的讀寫,所以速度很快。慢慢的會發現SSD變慢了,主要原因是SSD需要做GC。
    的頭像 發表于 06-02 10:58 ?5341次閱讀
    怎么使用SSD才能避免<b class='flag-5'>越</b><b class='flag-5'>寫</b>越慢?

    Android手機為什么

    智趣狗在今年1月曾簡單介紹過手機為啥卡的原因,只是當時是以蘋果降速門為切入點,并沒有說透。今天,就讓咱們聚焦Android手機領域,從硬件、系統和軟件三個方面分析安卓手機為啥容易出現“用越慢”的尷尬問題吧。
    的頭像 發表于 05-17 11:22 ?3611次閱讀

    用越快的華為,榮耀手機

    華為、榮耀手機使用時間久,反而速度越快,究竟為何?
    的頭像 發表于 08-20 14:41 ?3112次閱讀

    智能手機的Android系統為什么卡?

    Android系統卡?今天小米手機部總裁曾學忠科普為何Android系統卡,原因如下:
    的頭像 發表于 10-30 10:44 ?2242次閱讀

    為何Android系統卡背后的原因竟是?

    今天小米手機部總裁曾學忠科普為何Android系統卡,原因如下:
    的頭像 發表于 10-30 10:48 ?2763次閱讀

    AXI-Stream代碼

    AXI-Stream代碼詳解 AXI4-Stream跟AXI4的區別在于AXI4-Stream沒有ADDR接口,這樣就不涉及讀寫數據的概念了,只有簡單的發送與接收說法,減少了延時,允許
    的頭像 發表于 11-05 17:40 ?3649次閱讀
    AXI-<b class='flag-5'>Stream</b><b class='flag-5'>代碼</b>

    鴻蒙系統會不會

    鴻蒙系統會不會卡?在6 月 2 日,華為在開發者大會上發布了全新的鴻蒙系統,對于這個全新的鴻蒙系統,已經有小伙伴升級使用了,也有小伙伴還在觀望,那么鴻蒙系統會不會
    的頭像 發表于 06-29 09:21 ?4.4w次閱讀

    英偉達為什么便宜?英偉達的市盈率一直在下降?

    便宜?有點繞,但是他們是專業人士,這個肯定是說便宜就有便宜的道理。從估值角度來看英偉達不貴。 英偉達當前的估值(遠期PE)算下來比ChatGPT面世之前反而更便宜了,所以說英偉達便宜了。為什么?不能只看英偉達的股價漲了6-
    的頭像 發表于 03-12 18:02 ?1947次閱讀
    在线百家乐官网作| 顶级赌场官网| 百家乐桌颜色可定制| 百家乐官网园云鼎赌场娱乐网规则| 百家乐官网规则澳门| 鸟巢百家乐的玩法技巧和规则| 百家乐官网娱乐送白菜| 丹棱县| 百家乐送彩金网络| 百家乐最佳投注法下载| 百家乐里和的作用| 百家乐官网网上真钱娱乐场开户注册| 百家乐官网庄闲几率| 侯马市| 易发国际| 博彩套利| 最好的棋牌游戏平台| 财神百家乐的玩法技巧和规则 | 百家乐官网tt赌场娱乐网规则| 极速百家乐官网真人视讯| 百家乐官网赌场国际| 总统线上娱乐城| 大发888网页登陆| 大发888娱乐场下载英皇国际| 水果老虎机游戏| 百家乐论坛bocaila| 网上百家乐骗人的| 真人百家乐代理合作| 百家乐娱乐城送分| 百家乐开庄几率| 金沙百家乐娱乐城场| 百家乐官网韩泰阁| 百家乐官网软件l柳州| 太阳城百家乐官网红利| 百家乐官网庄比闲多多少| 百家乐官网游戏程序出售| 缙云县| 黑龙江省| 百家乐官网棋牌辅助| 赌博百家乐官网趋势把握| 百家乐官网庄闲必胜规|