<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7471字,需瀏覽 15分鐘

           ·

          2022-05-10 02:54


          • 1. 合理的換行
          • 2. 舍得拆分函數(shù)
          • 3. 合理的使用Optional
          • 4. 返回Stream還是返回List?
          • 5. 少用或者不用并行流
          • 總結(jié)

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

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

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

          public?List?getFeeds(Query?query,Page?page){
          ????List?orgiList?=?new?ArrayList<>();
          ????
          ????List?collect?=?page.getRecords().stream()
          ????.filter(this::addDetail)
          ????.map(FeedItemVo::convertVo)
          ????.filter(vo?->?this.addOrgNames(query.getIsSlow(),orgiList,vo))
          ????.collect(Collectors.toList());
          ????//...其他邏輯
          ????return?collect;
          }

          private?boolean?addDetail(FeedItem?feed){
          ????vo.setItemCardConf(service.getById(feed.getId()));
          ????return?true;
          }

          private?boolean?addOrgNames(boolean?isSlow,List?orgiList,FeedItemVo?vo){
          ????if(isShow?&&?vo.getOrgIds()?!=?null){
          ????????orgiList.add(vo.getOrgiName());
          ????}
          ????return?true;
          }

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

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

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

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

          1. 合理的換行

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

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

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

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

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

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

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

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

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

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

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

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

          public?Stream?getOrderByUser(String?userId){
          ????return?orderRepo.findOrderByUser().stream()
          ????????.map(order->?{
          ????????????OrderDto?dto?=?new?OrderDto();
          ????????????dto.setOrderId(order.getOrderId());
          ????????????dto.setTitle(order.getTitle().split("#")[0]);
          ????????????dto.setCreateDate(order.getCreateDate().getTime());
          ????????????return?dto;
          ????});
          }

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

          public?Stream?getOrderByUser(String?userId){
          ????return?orderRepo.findOrderByUser().stream()
          ????????.map(this::toOrderDto);
          }
          public?OrderDto?toOrderDto(Order?order){
          ????OrderDto?dto?=?new?OrderDto();
          ????????????dto.setOrderId(order.getOrderId());
          ????????????dto.setTitle(order.getTitle().split("#")[0]);
          ????????????dto.setCreateDate(order.getCreateDate().getTime());
          ????return?dto;
          }

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

          public?Stream?getOrderByUser(String?userId){
          ????return?orderRepo.findOrderByUser().stream()
          ????????.map(OrderDto::new);
          }

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

          Predicate?registarIsCorrect?=?reg?->?
          ????reg.getRegulationId()?!=?null?
          ????&&?reg.getRegulationId()?!=?0?
          ????&&?reg.getType()?==?0;

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

          3. 合理的使用Optional

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

          if(null?==?obj)
          if(null?==?user.getName()?||?"".equals(user.getName()))
          ????
          if?(order?!=?null)?{
          ????Logistics?logistics?=?order.getLogistics();
          ????if(logistics?!=?null){
          ????????Address?address?=?logistics.getAddress();
          ????????if?(address?!=?null)?{
          ????????????Country?country?=?address.getCountry();
          ????????????if?(country?!=?null)?{
          ????????????????Isocode?isocode?=?country.getIsocode();
          ????????????????if?(isocode?!=?null)?{
          ????????????????????return?isocode.getNumber();
          ????????????????}
          ????????????}
          ????????}
          ????}
          }

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

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

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

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

          public?Optional?getUserName()?{
          ????return?Optional.ofNullable(userName);
          }

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

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

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

          Optional?userName?=?"xjjdog";
          String?defaultEmail?=?userName????.map(e?->?e?+?"@xjjdog.cn")????.orElse("");

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

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

          4. 返回Stream還是返回List?

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

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

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

          public?Stream?getAuthUsers(){????...????return?Stream.of(users);}

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

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

          5. 少用或者不用并行流

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

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

          List?transform(List?source){?List?dst?=?new?ArrayList<>();?if(CollectionUtils.isEmpty()){??return?dst;?}?source.stream.??.parallel()??.map(..)??.filter(..)??.foreach(dst::add);?return?dst;}

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

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

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

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

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

          總結(jié)

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

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

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

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

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

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

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

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

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


          程序汪資料鏈接

          程序汪接的7個私活都在這里,經(jīng)驗整理

          Java項目分享 ?最新整理全集,找項目不累啦 07版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!


          歡迎添加程序汪個人微信 itwang009? 進粉絲群或圍觀朋友圈

          瀏覽 14
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  超碰福利导航 | 日韩视频高清无码 | 国产高清无码黄 | 色婷婷一区二区三区久久午夜成人 | 一级日韩 |