iT邦幫忙

2021 iThome 鐵人賽

DAY 10
3

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

上一篇
Day 09 「世事難預料」單元測試與例外處理
下一篇
Day 11 「我以火力掩護你」在測試的保護下重構:消除重複
系列文
你就是都不寫測試才會沒時間:Kuma 的 30 天 Unit Test 手把手教學,從理論到實戰 (Java 篇)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Len
iT邦新手 5 級 ‧ 2021-09-10 23:56:20

第十天了,Kuma加油~~~
剛一看圖就覺得可以重構,真的是被你訓練有素XD

Kuma iT邦新手 3 級 ‧ 2021-09-12 20:01:56 檢舉

感謝支持!問題是,看 id 看不出閣下哪位 XD

我要留言

立即登入留言