若干代碼壞味及解法

須要用一種精緻的態度去寫代碼,才能寫出優美而牢固的代碼。java


引子

壞味,一般是那些人們聞起來很是不舒服且避之不及的味道。走進垃圾桶,你就能深入感覺到壞味的存在。編程

代碼壞味,是指那些閱讀和理解起來很是拗口、困難、耗費大量腦力的代碼。代碼壞味更多反映的是風格和態度問題,而與技術沒有太大的關係(也能夠理解爲匱乏編寫可讀代碼的技術能力)。技術還能夠提高,風格和態度壞掉了,整我的的作事就 Low 掉了。這就比如一我的學了不少招式,可是一個馬步都蹲不穩。函數

好的代碼應該是怎樣的 ? 簡潔、天然、清晰。 讀起來,就像感覺到一陣微香的春風,很天然地就理解了,不會感覺到阻力。工具

本文將給出若干代碼壞味,以及如何更好地編寫。

ui

壞味

單詞拼寫錯誤

單測拼寫錯誤,這得多粗心 ?他寫代碼的時候都在想些什麼 ? 真但願這樣的人趕忙轉行,編程行業沒法容忍這種態度作事的人。code

String finasName = fans.getFansNickname();
if (StringUtil.isNotBlank(finasName)) {
    customer = finasName;
}

鏈式寫法

最忍不住衝動的就是鏈式寫法。 以下代碼所示:orm

boolean isPayCard = goods.getGoodsRichInfo().getItemModel().isPayCard();

if (response.getData() != null && CollectionUtils.isNotEmpty(response.getData().getCartList())) {
      // 出參轉換
      cartList = response.getData().getCartList().stream().map(CartResponseBuilder::buildCartList).collect(Collectors.toList());
    }

究其緣由,是這麼寫很爽。不過爽是有代價的: NPE 潛伏其中,並且報 NPE 時,還不能直觀看出是哪個有問題。好比上面的代碼,getGoodsRichInfo() 和 getItemModel() 都有多是 null 而引起 NPE 。blog

有兩種解決技巧:1. 拆解成多個單行調用並作判空(若是變量有複用更好); 2. 用 Optional 避免 NPE。

get

鏈式寫法的一個變種,就是把全部東西都扔到一行,—— 其衝動本質是同樣的。 以下代碼所示:it

List<String> orderNos = new ArrayList<>(messages.stream().map(TcOrder::getOrderNo).collect(Collectors.toSet()));

orderService.queryOrder(Long.parseLong(orderInfoList.get(0).getShopId()), orderNo)

這樣的壞處是什麼呢 ?

  1. 若是我須要把 Set 結果取出來作一點處理,再構造 List , 就不得不本身去拆解這個行。
  2. 單行括號過多,每每容易耗費不少腦力。
  3. orderInfoList.get(0).getShopId() 存在潛在的健壯性和 NPE 。

還有這種:

long discounts =
                    Optional.ofNullable(item).map(Item::getPrice).orElse(0L)
                    - Optional.ofNullable(item).map(OrderItem::getItemPrice)
                        .map(ItemPrice::getUnitPrice).orElse(0L);

明明能夠拆解爲:

Long originPrice = Optional.ofNullable(item).map(Item::getPrice).orElse(0L);
Long unitPrice = Optional.ofNullable(item).map(Item::getItemPrice)
                        .map(ItemPrice::getUnitPrice).orElse(0L);
long discounts = originPrice - unitPrice

更極端的例子:

壞味是什麼?

  1. 看這段邏輯,我就看了老半天,耗時耗神;
  2. 若是要添加關於 refundStatus 的新的邏輯,你讓我寫在哪裏 ? 繼續在裏面加 ?
  3. 裏面複雜的 refundStatus 必定是缺少覆蓋性單測的,代碼不可靠。
  4. 濫用了三目運算符。這些快捷運算符原本是爲了寫成簡潔的單行代碼,但是一旦濫用,就會與其原意背道而馳。

解決方法: 把構建 refundStatus 的部分抽離出來,以下所示。這樣,這個方法就清晰不少,也很容易進行覆蓋性單測,更可靠。

人是很容易效仿的。這樣的代碼看多了,你也會忍不住來上幾行。

大段的if-else

經常能夠看到這樣的代碼:

if (isRetail) {
   // buildLocalDeliveryInfoCodeForRetail  55 lines
}
else {
   //buildLocalDeliveryInfoCodeForNormal  73 lines
}

壞處是什麼:主流程很容易被分支代碼衝散,變成毫無重點的代碼堆砌;若是有多個條件分支,漸漸就會演變成多重 if-else 語句;方法愈來愈長,膨脹很快。第一我的沒作好,後面的人效仿起來,很容易就變成了一堆誰也不肯意碰的爛代碼。

對於這種情形,簡單的方案是,把多個條件分支的語句,分別抽到多個子函數,凸顯主流程;更進一步,採用策略模式,將多個子函數變成多個互不影響的組件,這樣,每一個類都很短小,各司其職,須要修改時也只要改局部便可。

多重if-else語句解耦

我拆解過一個多重 if-else 語句,限於公司代碼規定,這裏不便透露。

多重 if-else 語句,經過一個小技巧就能夠進行「降重」:對於每一個分支,編寫子函數,而後調用它。在每一個子函數裏,能夠經過 if-return 衛述句,快速返回,更容易理解。

若是有多重條件呢 ?好比:

if (orderDetail.getIsVirtual()
        || orderDetail.getIsVT()){
        if (isStockOverSale(orderDetail.getExtra())) {
            return "oversale, wait confirm";
        } else if (isStockDoing(orderDetail.getExtra())) {
            return "wait for stock confirm";
        }
    }

壞處是什麼? 若是我又要新增不一樣維度的條件,這裏很容易就會變成三重乃至更多重 if-else 語句, 你懂的。

解決方法:能夠將變量分離出來,將多重條件打平:

boolean isVirtual = orderDetail.getIsVirtual();
boolean isVirtualTicket = orderDetail.getIsVT();
boolean isVirtualOrder = isVirtual || isVirtualTicket;
boolean isStockOverSale = isStockOverSale(orderDetail.getExtra());
boolean isStockDoing = isStockDeductDoing(orderDetail.getExtra()); 

if (isVirtualOrder && isStockOverSale) {
    return "oversale, wait confirm";
}
if (isVirtualOrder && isStockDeductDoing) {
    return "wait for stock confirm";
}

小結

代碼壞味不少,難以一一列舉,但其本質的特色,就是喜歡將大量邏輯不分層次地堆砌到一塊兒。解決這些代碼壞味的技巧其實很簡單:拆解子函數,調用它。 如何將代碼拆解成不一樣層次的優雅組合,這是一門技藝。

編程,是邏輯與表達並重的活動。我的認爲,重邏輯而不重表達,重技術而不重細節,是國內難以批量生產高質量軟件的重因之一。要作出高質量軟件,從寫好每一行代碼入手,而不能期望工程上有什麼銀彈可讓一堆爛代碼成就一個好軟件。須要用一種精緻的態度去寫代碼,才能寫出優美而牢固的代碼。

對於我的來講,不容忍代碼壞味,孜孜不倦地追求編寫簡潔、天然、清晰的代碼;對於企業來講,須要樹立標杆,有更多關於代碼質量的佈道者,培養編寫好代碼的技術氛圍。不然,代碼就只是賺錢養家的工具,過幾年就要所有扔掉,沒有留下什麼值得借鑑和複用的東西,陷入低水平重複建設的境地。

相關文章
相關標籤/搜索