zoukankan      html  css  js  c++  java
  • 若干代码坏味及解法

    需要用一种精致的态度去写代码,才能写出优美而牢固的代码。


    引子

    坏味,通常是那些人们闻起来非常不舒服且避之不及的味道。走进垃圾桶,你就能深刻感受到坏味的存在。

    代码坏味,是指那些阅读和理解起来非常拗口、困难、耗费大量脑力的代码。代码坏味更多反映的是风格和态度问题,而与技术没有太大的关系(也可以理解为匮乏编写可读代码的技术能力)。技术还可以提升,风格和态度坏掉了,整个人的做事就 Low 掉了。这就好比一个人学了很多招式,但是一个马步都蹲不稳。

    好的代码应该是怎样的 ? 简洁、自然、清晰。 读起来,就像感受到一阵微香的春风,很自然地就理解了,不会感受到阻力。

    本文将给出若干代码坏味,以及如何更好地编写。

    坏味

    单词拼写错误

    单测拼写错误,这得多粗心 ?他写代码的时候都在想些什么 ? 真希望这样的人赶紧转行,编程行业无法容忍这种态度做事的人。

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

    链式写法

    最忍不住冲动的就是链式写法。 如下代码所示:

    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 。

    有两种解决技巧:1. 拆解成多个单行调用并做判空(如果变量有复用更好); 2. 用 Optional 避免 NPE。

    链式写法的一个变种,就是把所有东西都扔到一行,—— 其冲动本质是一样的。 如下代码所示:

    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";
    }
    

    小结

    代码坏味很多,难以一一列举,但其本质的特点,就是喜欢将大量逻辑不分层次地堆砌到一起。解决这些代码坏味的技巧其实很简单:拆解子函数,调用它。 如何将代码拆解成不同层次的优雅组合,这是一门技艺。

    编程,是逻辑与表达并重的活动。个人认为,重逻辑而不重表达,重技术而不重细节,是国内难以批量生产高质量软件的重因之一。要做出高质量软件,从写好每一行代码入手,而不能指望工程上有什么银弹可以让一堆烂代码成就一个好软件。需要用一种精致的态度去写代码,才能写出优美而牢固的代码。

    对于个人来说,不容忍代码坏味,孜孜不倦地追求编写简洁、自然、清晰的代码;对于企业来说,需要树立标杆,有更多关于代码质量的布道者,培养编写好代码的技术氛围。否则,代码就只是赚钱养家的工具,过几年就要全部扔掉,没有留下什么值得借鉴和复用的东西,陷入低水平重复建设的境地。

  • 相关阅读:
    2019牛客暑期多校训练营(第六场)
    2019牛客暑期多校训练营(第五场)
    CCPC-Wannafly Summer Camp 2019 全记录
    2018CCPC吉林赛区 | 部分题解 (HDU6555 HDU6556 HDU6559 HDU6561)
    2006-2007 ACM-ICPC | POJ3380 POJ3384 POJ3385 水题题解
    数论专场 Day9 部分题解
    Catenyms (POJ2337) 字典序最小欧拉路
    2019暑训第一场训练赛 |(2016-icpc区域赛)部分题解
    哈尔滨理工大学软件与微电子学院第八届程序设计竞赛同步赛(高年级)B 小乐乐搭积木 (状态压缩)
    牛客练习赛32B Xor Path (树形dp)
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/13270339.html
Copyright © 2011-2022 走看看