<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后,代碼反而越寫越丑?

          共 7746字,需瀏覽 16分鐘

           ·

          2022-04-28 21:57

          往期熱門文章:

          1、一不小心節(jié)約了 591 臺機器!
          2、你見過哪些目瞪口呆的 Java 代碼技巧?
          3、笑死!程序員延壽指南開源了
          4、互聯(lián)網(wǎng)黑話,被上海人翻譯火了
          5、還在用分頁?太Low !試試 MyBatis 流式查詢,真心爽!

          文章來源:【公眾號:小姐姐味道】

          目錄
          • 合理的換行

          • 舍得拆分函數(shù)

          • 合理的使用 Optional

          • 返回 Stream 還是返回 List?

          • 少用或者不用并行流

          • 總結(jié)


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

          代碼首先是給人看的,其次才是給機器執(zhí)行的。代碼寫的是否簡潔明了,是否寫的漂亮,對后續(xù)的 Bug 修復和功能擴展,意義重大。

          很多時候,是否能寫出優(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)秀的代碼,首先是可讀的,然后才是功能完善。

          合理的換行


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

          當然,我們知道這么做是不對的。在 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("?"));

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

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

          舍得拆分函數(shù)


          為什么函數(shù)能夠越寫越長?是因為技術(shù)水平高,能夠駕馭這種變化么?答案是因為懶!

          由于開發(fā)工期或者意識的問題,遇到有新的需求,直接往老的代碼上添加 if else,即使遇到相似的功能,也直接選擇將原來的代碼拷貝過去。久而久之,碼將不碼。

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

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

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

          合理的使用 Optional


          在 Java 代碼里,由于 NullPointerException 不屬于強制捕捉的異常,它會隱藏在代碼里,造成很多不可預料的 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 類,用于解決臭名昭著的空指針問題。實際上,它是一個包裹類,提供了幾個方法可以去判斷自身的空值問題。

          上面比較復雜的代碼示例,就可以替換成下面的代碼:
          ?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());

          當你不確定你提供的東西,是不是為空的時候,一個好的習慣是不要返回 null,否則調(diào)用者的代碼將充滿了 null 的判斷。我們要把 null 消滅在萌芽中。
          public?Optional?getUserName()?{
          ????return?Optional.ofNullable(userName);
          }

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

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

          而應(yīng)該修改成這樣的方式:
          Optional<String>?userName?=?"xjjdog";
          String?defaultEmail?=?userName
          ????.map(e?->?e?+?"@xjjdog.cn")
          ????.orElse("");

          那為什么我們的代碼中,依然充滿了各式各樣的空值判斷?即使在非常專業(yè)和流行的代碼中?

          一個非常重要的原因,就是 Optional 的使用需要保持一致。當其中的一環(huán)出現(xiàn)了斷層,大多數(shù)編碼者都會以模仿的方式去寫一些代碼,以便保持與原代碼風格的一致。

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

          返回 Stream 還是返回 List?


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

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

          但對于一個 Stream 來說,是不可變的,它不會影響原始的集合。對于這種場景,我們推薦直接返回 Stream 流,而不是返回集合。

          這種方式還有一個好處,能夠強烈的暗示 API 使用者,多多使用 Stream 相關(guān)的函數(shù),以便能夠統(tǒng)一代碼風格。
          public?Stream?getAuthUsers(){
          ????...
          ????return?Stream.of(users);
          }

          不可變集合是一個強需求,它能防止外部的函數(shù)對這些集合進行不可預料的修改。在 guava 中,就有大量的 Immutable 類支持這種包裹。

          再舉一個例子,Java 的枚舉,它的 values() 方法,為了防止外面的 api 對枚舉進行修改,就只能拷貝一份數(shù)據(jù)。

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

          少用或者不用并行流


          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。它的大小,默認是 CPU 個數(shù)-1,大多數(shù)情況下,是不夠用的。

          如果有人在并行流上跑了耗時的 IO 業(yè)務(wù),那么你即使執(zhí)行一個簡單的數(shù)學運算,也需要排隊。

          關(guān)鍵是,你是沒辦法阻止項目內(nèi)的其他同學使用并行流的,也無法知曉他干了什么事情。

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

          總結(jié)


          Java8 加入的 Stream 功能非常棒,我們不需要再羨慕其他語言,寫起代碼來也更加行云流水。

          雖然看著很厲害的樣子,但它也只不過是一個語法糖而已,不要寄希望于用了它就獲得了超能力。

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

          總體來說,使用 Stream 和 Lambda,要保證主流程的簡單清晰,風格要統(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 代碼,是虐待同事最好的方式,也是埋坑的不二選擇。

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

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

          往期熱門文章:

          1、笑死!程序員延壽指南開源了
          2、用 Dubbo 傳輸文件?被老板一頓揍!
          3、45 個 Git 經(jīng)典操作場景,專治不會合代碼!
          4、@Transactional 注解失效的3種原因及解決辦法
          5、小學生們在B站講算法,網(wǎng)友:我只會阿巴阿巴
          6、Spring爆出比Log4j2還大的漏洞?
          7、Objects.equals 有坑?。?!
          8、Redis 官方可視化工具,功能真心強大!
          8、xxl-job 和 ElasticJob比,誰牛?
          10、推薦好用的 Spring Boot 內(nèi)置工具類

          瀏覽 23
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  俺来俺去www色官网 | 中文字幕 码精品视频网站 | 国产草草 | 成人免费 做爱视频 | 福利二区视频 |