Day 10 「如入鲍鱼之肆」从测试闻出 code smell:万恶之源 ---「重复」

Day 10 「如入鲍鱼之肆」从测试闻出 code smell:万恶之源 ---「重复」

好好写测试,轻松闻出 Code Smell

孔子说:「如入鲍鱼之肆,久而不闻其臭,亦与之化矣。」如果你身边的人整天写一些烂 code,看也看不懂,改也改不动,还整天抱怨公司客户这里不好那里不行,也没见他做点什麽改变,这种环境也许一开始会困扰你,但待久了你也就习惯了,不觉得有什麽问题,进而慢慢被他们同化,变成同一种人。

这可不是身为专业人士的我们该有的表现。

古语有云:「万恶淫为首」,在程序的世界,「淫」可不是最糟糕的事。真要讲的话,程序世界的万恶之渊薮,「重复」应是当之无愧。今天我们来看看如何「由测试看出重复」。

用测试抓出重复

大概有 87% 的烂程序,都是从「复制贴上」开始。可以说复制贴上是直接造成重复的主因。然而,开发员会制造重复,却是非常合情合理的事情,因为在一样场景下,我们能想出来的解法就那些,而这些复制的来源,多半是 Stack Overflow 直接抄来的答案,或是过去的专案中,用过的类似解决方法。

这些东西方便有效,重点他能帮助你完成工作,让公司有钱付你薪水。这样看起来,应该是好东西,对吧?

然而,当你遇到一样的事情时,你「从你自己写过的 Code 里,复制一份出来,用到别的地方」,你就马上完成一份「重复」了。2000 年左右,Andrew Hunt 与 David Thomas 在 The Pragmatic Programmer 一书中将此观念命名为「DRY - Don't Repeat Yourself」。

一份程序码如果在别的地方也用得到,与其复制贴上,不如直接重复呼叫,或是透过一些重构手法让他适用在多种不同场景。

重复的程序码有多糟糕,我不太想浪费篇幅解释。笔者从业短短数年,遇到的开发员会犯下重复之罪,大多不是故意的,而是他「没发现、没闻到」这个重复的坏味道。

为什麽?我觉得与大家「很少做单元测试」与「不习惯小规模重构」有关。在笔者自己的经验中,在忙碌的工作与开发的时间压力下,最常帮助我找出重复坏味道的,不是 Code Review,也不是贵松松的 IDE,反而是密集度足够的单元测试。

怎麽说呢?还记得我们前文中讲过的「好用就好测」概念吧?其实一样的,你程序设计上有什麽缺陷,写测试时就能感受到,因为你会逼得你的测试不得不写成同一个样子。当你在写程序时,发现你在重复,不用紧张,这可能代表两件事:

  1. 介面设计得不好,导致使用者要一直做重复的事。
  2. 就是有很多类似的逻辑,测试只是在表现这件事。

不管是哪一种,都代表这是重构或是修改设计的好时机。很抽象吗?来,我们看个例子。

程序重复迫使测试重复:举个例子吧

前面我们聊到测资料的时候,有举一个成绩单的例子。当时我们透过增加学生的种类与对应的奖学金算法,一下子就把复杂度拉高。那时笔者有忍住,完全没有做任何重构的事,而现在要来进行了。我们先来看看不重构的话,程序会长什麽样子:

我这里刻意用贴图的,并且把字缩小,为的就是要请各位不要进去看内容,而只要看这个方法呈现出来的「外观」长什麽样子。笔者经常跟公司同事说:「一段程序码写得好不好,你看『形状』就知道。」从上图可以看出,这个方法很明显拆成三大块,而每一大块都长得很像。其实我们光发现这件事,就应该要闻到一个明显的坏味道了,就是:「肯定有什麽共通的抽象逻辑被重复了」。

如果你在这时就能发现,那就可以马上改了。而如果你没发现,你还有第二次机会,也就是写测试的时候。我们把测试拿出来看一下,一样,只看「形状」就好。

class ScholarshipServiceTest {

    @Test
    void bachelor_full_scholarship() {/*中略*/}

    @Test
    void bachelor_half_scholarship() {/*中略*/}

    @Test
    void bachelor_NO_courses() {/*中略*/}

    @Test
    void bachelor_NO_scholarship() {/*中略*/}


    @Test
    void master_full_scholarship() {/*中略*/}

    @Test
    void master_half_scholarship() {/*中略*/}

    @Test
    void master_NO_courses() {/*中略*/}

    @Test
    void master_NO_scholarship() {/*中略*/}

    @Test
    void PhD_full_scholarship() {/*中略*/}

    @Test
    void PhD_half_scholarship() {/*中略*/}
    
    @Test
    void PhD_NO_courses() {/*中略*/}

    @Test
    void PhD_NO_scholarship() {/*中略*/}
    
    // ...後略
}

我把所有细节全部隐藏起来後,读者应该很容易就看出明显的重复了吧。三种学生,各自有四种场景,所以在写测试时,你应该可以预期,会有非常多的「复制过来改一下」的操作,而觉得麻烦。

觉得麻烦就对了!

这就代表要嘛设计有问题,应该回头修改介面设计;要嘛逻辑就是这麽多,应该回头检查程序有没有找得很像,可以重构一下的地方。这是你闻出重复的第二个机会,错过的话,就要等下次再回头改时,因为看不懂而起心动难想重构了。到了那时,虽然我还是鼓励你重构(再怎麽说也至少有测试保护了),但是你重构花的时间跟心思就会比现在多很多,因为你已经忘记当初在写什麽了。

好我们现在找出程序确实是有重复的地方了,那就来改吧。怎麽改,改成怎样,我们下一篇再介绍。

谜之声:「如果我们没发现,就给程序写一点测试 XD」


图片截自 YouTube

Reference

  1. Andrew Hunt and David Thomas, The Pragmatic Programmer, Addison-Wesley, 2000
tags: ithelp2021

<<:  [Tableau Public] day 10:试试长条图&地图,抓不到地图资讯怎麽办?

>>:  第10天 - PHP新增MySQL资料表内容

[Day5] 假想使用者之情境范例

接续我们在昨天所阐述的设计流程, 接着来看看这些设计流程是如何在实际运作的Action上被实践的。 ...

Day24 动态组件 Dynamic Components

Keep-alive 有时我们会希望资料状态能够保存下来,避免再次载入时资料消失,这时我们的外层就会...

Day 23 AWS的云上排队服务-SQS

想知道如何在云端上传递和处理来自使用者的网路请求讯息吗?AWS的SQS可以帮助我们做到这一点。我们往...

DAY19 MongoDB Oplog 是什麽?迈向高手之路

DAY19 MongoDB Oplog 到底是什麽? oplog 是什麽? 如果你的 MongoDB...

Day25 - this&Object Prototypes Ch3 Objects - Review

Object contents 所有 property 都可以透过 Object.definePr...