Legacy Code 重構實戰練習 I(下) — DDD 讀書會筆記 Ch01 — Ch05

Nick
7 min readMar 7, 2020

前言

今天要延續上篇重構實戰案例,在書中稱「沒有測試覆蓋的程式碼即是 Legacy Code」的規則下,我們先透過書中解耦合(coupling)的方法先釐清對象的三項功能,並補上測試,接著進入到討論如何重構讓程式碼變「乾淨」,因此此次讀書會時間不夠沒有深入討論這塊,所以以下是我個人的想法,歡迎大家留言討論任何偶疑惑的點。

已經將測試對象進行覆蓋

回顧程式碼

以下將會有三個重構大點進行詳細步驟說明,閱讀時間約 5 ~ 10 分鐘,如果身邊有機會可以開啟編輯器一起來做做看,實際做過一遍大概花費 15 ~ 30 分鐘,專案在這裡,請切到 dab464 這個 hash 的 commit 然後開始閱讀吧。

開始之前先說得打臉的,專案一打開發現前幾天寫的測試 Code 馬上就看不懂了,還好讀書會有其他人分享自己的測試程式,才發現自己上次寫的盲點有以下。

  1. 問題一:過多自定義的變數值,如 userId、trip1、trip2,不容易理解。
  2. 問題二:TripServiceUnderTest 做了什麼,這是一個要覆寫一些會有連接 DB 的 method 而實作的 class,但反而增加了測試程式的複雜度。
前幾天寫的單元測試,有點不好懂

Beyond Yesterday 先重構一下測試程式

  1. 條動一:可以透過一些常數變數定義好複用在每個 testcase 中。
  2. 條動二:移除取代 TripServiceUnderTest 的實作,改為之前單元測試工具知名的 Mockery 套件直接複寫原本 class 的 method。
  3. 再把單元測試四步驟 Setup、Exercise、Verify、Teardown 標上。

調整完以上三點就變成以下程式了

重構後的測試程式碼

進入真的回顧程式碼

以下是目前可以測試的主程式碼 TripService,目前有了測試覆蓋,我們可以點開以下圖片,好好思考一下「如果是你會想調整哪裡,想個一兩點」,好了就一起往下討論吧。

小備註:UserSession 、 TripDAO 為對 DB 撈取資料的功能物件

目前的主程式碼,一起來想怎麼重構吧

重構思考點一:減少大括號數,思考 if else 是否可以減少

抱歉我記得導讀者有講到一個好的名詞,但我沒記下來 Orz ,我先誤稱它為「防禦性程式」好了,以下看看實作。

「防禦型程式」實作
記得要看到測試都通過才可以 commit

重構思考點二:*減少全域變數的使用

全域變數一直是 Legacy Code 的大惡夢,因為它常就是牽扯到許多耦合(coupling)的關鍵原因,要撰寫單元測試前要先把他們 mock 掉,就如以下我們在撰寫測試時條動程式碼產生的這兩個「物件接縫」的「制動點」。

在這個程式碼,就是當時我們解 coupling 的這兩個函數

以下是我思考最好的方法,這步真的很歡迎討論 > <

我將全域變數透過 Service 建構時傳入,

將全域變數改為 constructor 傳入
測試過了,但也是最可怕的是我們調整的地方是被 mock 掉的地方

小討論:這調動是否危險,會有什麼 bug

讓這個「制動點」改在 constructor 決定,但也代表所有 call TripService 實作的地方都需要調整參數。

接下來把「物件接縫」的「制能點」從 method 改在 constructor 實作,直接看以下內容吧。

將原本因為需要解耦合 (coupling) 的程式碼移除
測試出問題了,因為原本是在 method 做制動去 mock 這兩個與 DB 串接的程式

再修正一下測試程式,以其中一個 testcase 來象徵就好
原本是透過將 service 的 method 進行 mock,這邊改成再 construct 時替換傳入的兩個物件 UserSession 及 tripDAO,改成使用 Mock Object 來指定回傳內容。

將 service 需要 mock 的函數改成 mock 在傳入的物件中
測試被修正了

小討論:是否是一個危險的調動?
這個重構同時動到了 主程式碼 及 測試程式!

我的回答是是的,這真的很危險,通常我面對這種程式是不敢去調動的,除非有補些微整合測試在外層,會稍微有勇氣一點。

重構思考點三:將程式碼放置到屬於他的模組

這點感謝 Fong 大大在自己的重構範例中寫到,提醒了我這步可以調整,就是以下反白的這段亂亂的程式碼,如果你一開始就有注意到的點,那代表你比我還敏感 ~

有點冗長的程式碼

這段程式碼是判斷 loggedUser 是否為傳入的 user 的朋友,但這段邏輯卻是在 service 實作,或許我們可以將它移入 user 模組中,之後可以複用。

整理判斷是否是 friend 的方法
User 新增 isFriendWith 的 method
記得一樣要測試通過才可以 commit

調整後才可以再,簡化 isFriend 這個變數,或許可以用一個三元運算子就可以把 default 回傳值一起涵蓋了。

這個位置好像可以簡化成一行
條整後的程式碼
記得一樣要測試通過才可以 commit

最後來個,重構前後比較吧

這是重構前,發現的問題統整是

  • if else 可以改成「防禦型程式」,減少大括號的使用數
  • 全域變數在 method 中到處使用
  • 判斷 isFriend 邏輯可以整理至 User 物件中在不同 service 複用,也減少 service 變數數及邏輯
重構前
重構後

小結

這算是我第一次針對重構及「我覺得好的程式碼」進行文章發表,希望你還喜歡,也希望能多多與大大們交流,尤其是重構二我一直覺得是一個問題點,我每次都很想改,但又改不太動的點。

也希望之後的讀書會能有更多火花,並堅持繼續以文章形式記錄下來。

如果喜歡我這篇文,可以幫我拍手 1-10 下
如果覺得這文章對你有幫助,可以幫我拍手 10–30
如果覺得想支持讀書會重構這主題文章,可以幫我拍手 30–50 讓我知道
也記得 Follow我 陳建宇
更歡迎你在下方留言,我很樂意與你討論聊天或回答問題!

參考資料

--

--

Nick

嗨 我是 Nick,目前在 ShopBack 擔任軟體工程師,喜歡用科技解決問題,偶爾會整理一些工作上的發現分享在這,歡迎大家追蹤及交流