天天看點

代碼的壞味道2  構築測試體系

《重構》第三章學習筆記

我們必須培養自己的判斷力,來決定在什麼時候進行重構。

1.1  Duplicate Code(重複代碼)

如果你在一個以上地點看到相同的程式結構,那麼将他們合而為一會更好。

1.2  Long Method(過長函數)

擁有短函數的對象會活得比較好,比較長。

間接層所能帶來的全部益處:解釋能力(可讀性),共享能力(重用性),選擇能力(?)。

現在OO 語言基本解決了函數調用所産生的開銷。

“ 你應該更積極進去的分解函數。我們遵循這樣一條原則:每當感覺需要以注釋來說明點什麼的時候,我們就把需要說明的東西寫進一個函數中,并以其用途(而非實作手法)命名。我們可以對一組甚至短短一行代碼(擁有複雜邏輯,難以了解)做這件事。哪怕替換後的函數調用動作比函數自身還長,隻要函數名稱能夠解釋其用途,我們也該毫不猶豫的這麼做。關鍵不在于函數的長度,而在于“做什麼”和“如何做”之間的語義距離。 ”

“如何确定該提煉哪一段代碼?一個很好的技巧是:尋找注釋。它們通常是指出“代碼用途和實作手法間的語義距離”的信号。如果代碼需要用注釋來說明其用途,那麼就要考慮把這段代碼提煉成獨立的函數,并且用注釋來為此函數命名。”

複雜條件式和循環液常常是提煉的信号。

1.3  Large Class(過大類)

如果想利用單一的class 做太多的事情,其内往往會出現太多的 instance 變量。

如果class 中擁有太多的代碼,也是“代碼重複、混亂、死亡”的絕佳滋生點。

1.4  Long Parameter List(過長的參數清單)

過長的産生導緻程式難以了解。

1.5  Divergent Change(發散式變化)

“ 一個class 受多個外界變化的影響 ”,則把這多個變化封裝成一個新的類。即“ 将總是一起變化的東西放在一起 ”

針對外界某一變化所有相應的修改,都應該隻發生在單一的class 中,而這個 class 的所有内容都應該反映該外界變化。總的思想就是,封裝變化。這個地方和設計模式的想法是一緻的。

1.6  Shotgun Surgery(散彈式修改)

和發散式變化不同,每次遇到變化,都要在多個class 中進行小的修改以響應之。他們分散在多處,很容易出錯。

這裡的主要思想是集中變化。

散彈式修改指的是,“ 一種變化引發多個class 的修改 ”,發散式變化指的是“ 一個class 受多個外界變化的影響 ”。

這兩種情況下,通過重構, 使“外界變化”和“待修改類”呈一對一關系 的理想境地。

1.7  Feature Envy(依戀情節)

某個函數對其他類的資料的興趣,高過對host class 的興趣。即對其他的類的資料的依賴十分大。

1.8  Data Clumps(資料泥團)

資料泥團指的是總是綁定在一起出現的資料。

一個好的評斷方法:删除衆多資料中的一項資料,其他資料是否是因而失去了意義?如果他們不再有意義:你應該為他們産生一個新的對象。

形成新的對象後,可以根據Feature Envy 将一些操作移至此對象中。

1.9  Primitive Obsession(基本型别偏執)

建立多個很小,但是很靈活的對象。

1.10  Switch Statements( switch 驚悚現身)

使用面向對象程式設計,要少用switch 和 case 語句。而是用多态來替換它。

1.11  Parallel Inheritance Hierarchies(平行繼承體系)

每當你為一個class 增加一個 subclass 的時候,必須為另一個 class 增加一個 subclass 。一般這兩個 class 的字首相同。

1.12  Lazy Class(冗贅類)

類顯得多餘,沒有價值。

1.13  Speculative Generality(誇誇其談未來性)

這個往往是過度設計的結果:對某種變化的應對,而這種變化沒有發生。

1.14  Temporary Field(令人迷惑的暫時值域)

變量隻在特定的情形下有效,而并不是所有的情況下有效。很多情況下,這些值域應該不屬于此class ,而應該單獨的提取成新的類。

1.15  Message Chains(過度耦合的消息鍊)

使用者向一個對象索取另一個對象,然後在向後者索求另一個對象,然後在索求另一個對象——客戶與查找過程的航行結構緊密耦合。

1.16  Middle Man(中間轉手人)

對象的基本特征之一就是封裝——對外部世界隐藏實作細節——封裝往往伴随委托。委托的過度運作,就導緻了Middle Man 。

1.17  Inappropriate Intimacy (親密關系)

兩個class 之間的關系過于親密。比如,花大量的時間探究彼此的 private 成分。

1.18  Alternative Classes with Different Interface(異曲同工的類)

類名不同,但是功能相似。

1.19  Incomplete Library Class(不完美的程式類庫)

基礎類庫無法滿足實際的需求。

1.20  Data Class(純稚的資料類)

它們擁有一些值域,以及用于通路(讀寫)這些值域的函數,除此之外一無長物。

1.21  Refused Bequest(被拒絕的遺贈)

子類不像繼承父類的函數和資料,這往往是繼承體系的錯誤。

如果子類複用父類的行為,但又不願支援父類的接口,這種情況下Refused Bequest 的壞味道會很強烈。

1.22  Comments(過多的注釋)

注釋其實是一種香味,更多的情況下它被用作除臭劑:即代碼中出現壞味道(設計糟糕的代碼),然後用注釋“除臭”。這個時候我們應該對這些壞味道的代碼進行重構,然後,你會發現注釋變成了多餘的。

當你感覺需要注釋,請先嘗試重構,試着讓所有的注釋都變得多餘——代碼本身就是自注釋的。

注釋可以用來記述“為什麼做某事”、“打算做某事”、“無十足把握的區域”,而不必記錄“怎麼做”。

2  構築測試體系

如果你想進行重構,首要前提就是要擁有一個可靠的測試環境。

“編寫優良的測試程式,可以極大的提高我的程式設計速度,即使不進行重構也是如此。”

2.1  自我測試代碼(Self-testing Code )的價值

“Class 應該包含他們自己的測試代碼。”

“每個Class 都有一個測試函數,并用它測試自己這個 Class 。”

確定所有的測試都完全自動化,讓它們檢查自己的測試結果。

隻要寫好一點功能,就立即添加測試。

一整組(a suite of )測試就是一個強大的“臭蟲”偵測器,能夠大大縮減查找“臭蟲”所需要的時間。

“實際上,編寫測試代碼的最有用時機是在開始程式設計之前。當你需要添加特性的時候,先寫相應的測試代碼。聽起來離經叛道,其實不然。填寫測試代碼其實就是問自己:添加這個功能需要做什麼。編寫測試代碼還能使你把注意力集中于接口而非實作上頭(永遠是件好事)。預先寫好的測試代碼也為你的工作按上一個明确的結束标志:一旦測試代碼運作正常,工作就可以結束了。”