第1章 重構(gòu),第一個案例
- 代碼塊俞小纱注,代碼的功能就俞容易管理畏浆,代碼的處理和移動也就俞輕松。(功能也就越單一)
- 任何不會被修改的變量都可以被當(dāng)成參數(shù)傳入新的函數(shù)狞贱,至于會被修改的變量需要慎重刻获。如果只有一個變量會被修改,可以把它當(dāng)做返回值瞎嬉。
- 絕大多數(shù)情況下蝎毡,函數(shù)應(yīng)該放在它所使用的數(shù)據(jù)的所屬對象內(nèi)。
- 最好不要在另一個對象的屬性基礎(chǔ)上運用switch語句氧枣。如果不得不使用沐兵,也應(yīng)該在對象自己的數(shù)據(jù)上使用,而不是在別人的數(shù)據(jù)上使用便监。
- 使用繼承來適當(dāng)組織類關(guān)系后扎谎,可以用多態(tài)取代switch語句。
- 一部影片可以在生命周期內(nèi)修改自己的分類烧董,一個對象卻不能在生命周期內(nèi)修改自己所屬的類毁靶。
第2章 重構(gòu)原則
- 三次法則:第一次做某件事時只管去做;第二次做類似的事會產(chǎn)生反感逊移,但無論如何還是可以去做预吆;第三次在做類似的是,你就應(yīng)該重構(gòu)胳泉。事不過三拐叉,三則重構(gòu)岩遗。
- 在重構(gòu)中引入間接層的某些價值:
- 允許邏輯共享
- 分開解釋意圖和實現(xiàn)
- 隔離變化
- 封裝條件邏輯
- 封裝條件邏輯。對象有一種奇妙的機制:多態(tài)消息凤瘦,可以靈活而清晰地表達條件邏輯宿礁。將條件邏輯轉(zhuǎn)化為消息形式,往往能降低代碼的重復(fù)廷粒、增加清晰度并提高彈性窘拯。
第3章 代碼的壞味道
- 重復(fù)代碼:設(shè)法合二為一
- 同一個類的兩個函數(shù)還有相同的表達式,這時需要提煉出重復(fù)代碼坝茎。
- 兩個互為兄弟的子類內(nèi)含有相同的表達式涤姊,可以提煉相同代碼,并放到父類中嗤放。如果只是代碼間相似思喊,并非完全相同,那么可以將相似部分和差異部分拆開次酌,構(gòu)成單獨的函數(shù)恨课。然后你可以使用模板方法的設(shè)計模式。
- 如果兩個毫不相關(guān)的類中出現(xiàn)重復(fù)代碼岳服,則可以將重復(fù)代碼提煉成一個函數(shù)放到一個獨立類中或者只放在某一個類中(總之要放在合適的地方)剂公,然后其他類都去調(diào)用這個函數(shù)。
- 過長函數(shù)
一條原則:每當(dāng)感覺需要以注釋來說明點什么的時候吊宋,我們就把需要說明的東西寫進一個獨立函數(shù)中纲辽,并以其用途(而非實現(xiàn)手法)命名。哪怕替換后的函數(shù)調(diào)用動作比函數(shù)自身還長璃搜,只要函數(shù)名稱能夠解釋其用途拖吼,我們也該毫不猶豫的那么做。關(guān)鍵不在于函數(shù)的長度这吻,而在于函數(shù)“做什么”和“如何做”之間的語義距離吊档。 - 過大的類
類內(nèi)如果有太多代碼,也是代碼重復(fù)唾糯、混亂并最終走向死亡的源頭怠硼。最簡單的解決方案是把多余的東西消弭于類內(nèi)部。如果有五個“百行函數(shù)”移怯,它們之間很多代碼都相同拒名,那么或許你可以把它們編程五個“十行函數(shù)”和十個提煉出來的“雙行函數(shù)”。 - 過長參數(shù)列:用對象做參數(shù)來減少參數(shù)個數(shù)
- 有了對象芋酌,就不必把函數(shù)需要的所有東西都以參數(shù)傳遞給它了,只需傳給它足夠的雁佳、讓函數(shù)能從中獲得自己需要的東西就行了脐帝。函數(shù)需要的東西多半可以在函數(shù)宿主類中找到同云。如果將對象傳遞給函數(shù),大多數(shù)修改都將沒有必要堵腹,因為你很可能只需(在函數(shù)內(nèi))增加一兩條請求炸站,就能得到更多的數(shù)據(jù)。
- 這里有一個重要的例外:有時候你明顯不希望造成“被調(diào)用對象”與“較大對象”間的某種依賴關(guān)系疚顷。這時候?qū)?shù)據(jù)從對象中拆解出來單獨作為參數(shù)旱易,也很合情合理。
- 發(fā)散式變化:一個類受多種變化的影響
如果某個類經(jīng)常因為不同的原因在不同的方向上發(fā)生變化腿堤,發(fā)散式變化的壞味道就出現(xiàn)了阀坏。針對某一外界變化的所有相應(yīng)修改,都只應(yīng)該發(fā)生在單一類中笆檀,而這個新類內(nèi)的所有內(nèi)容都應(yīng)該反應(yīng)次變化忌堂。 - 霰彈式修改:一種變化引發(fā)多個類相應(yīng)修改
如果每遇到某種變化,你都必須在許多不同的類內(nèi)做出許多小修改酗洒,你所面臨的壞味道就是霰彈式修改士修。這種情況下,可以把所有需要修改的代碼放進同一個類樱衷,如果沒有合適的類可放棋嘲,就創(chuàng)造一個。 - 依戀情節(jié)
- 對象技術(shù)的全部要點在于:這是一種“將數(shù)據(jù)和對數(shù)據(jù)的操作行為包裝在一起”的技術(shù)矩桂。
- 有一種經(jīng)典的氣味是:函數(shù)對某個類的興趣高過對自己所處類的興趣沸移。這種孺慕之情最通常的焦點便是數(shù)據(jù)。
- 處理這種壞味道的原則是:判斷哪個類擁有最多被此函數(shù)實用的數(shù)據(jù)耍鬓,然后就把這個函數(shù)和那些數(shù)據(jù)擺在一起阔籽。
- 最根本的原則是:將總是一起變化的東西放在一塊。
- 數(shù)據(jù)泥團:總是綁在一起的數(shù)據(jù)應(yīng)該擁有屬于它們自己的對象
一個好的評判辦法是:刪掉眾多數(shù)據(jù)中的一項牲蜀,其他數(shù)據(jù)有沒有因此而失去意義笆制?如果他們不再有意義,這就是一個明確信號:你應(yīng)該為它們產(chǎn)生一個新對象涣达。 - 基本類型偏執(zhí):將數(shù)據(jù)組織成有意義的形式在辆,不偏執(zhí)于基本類型
- 對象技術(shù)的新手通常不愿意在小任務(wù)上運用小對象。
- 如果你有一組應(yīng)該總是被放在一起的字段(基本類型的數(shù)據(jù))度苔,那么可以嘗試將這組數(shù)據(jù)放到一個單獨類中變成結(jié)構(gòu)類型的數(shù)據(jù)
- switch語句
面向?qū)ο蟪绦虻囊粋€最明顯特征就是:少用switch(或case)語句匆篓。從本質(zhì)上說,switch語句的問題在于重復(fù)寇窑。面向?qū)ο笾械亩鄳B(tài)概念可為此帶來優(yōu)雅的解決辦法鸦概。 - 平行繼承體系
- 如果你發(fā)現(xiàn)某個繼承體系的類名稱前綴和另一個繼承體系的類名稱前綴完全相同,便是問到了這種壞味道忧便。
- 消除這種重復(fù)性的一般策略是:讓一個繼承體系的實例引用另一個繼承體系的實例幕帆。
- 冗贅類
- 夸夸其談未來性
當(dāng)有人說“噢唱逢, 我想我們總有一天需要做這件事”奈搜,并因而企圖以各式各樣的鉤子和特殊情況來處理一些非必要的事情懊悯,這種壞味道就出現(xiàn)了港粱。 - 令人迷惑的臨時字段
- 過度耦合的消息鏈
- 中間人
- 狎昵關(guān)系
- 異曲同工的類
- 不完美的類庫
- 純稚的數(shù)據(jù)類
- 純稚的數(shù)據(jù)類是指:它們擁有一些字段富俄,以及用于訪問(讀寫)這些字段的函數(shù)眼刃,除此之外一無長物摄狱。
- 這種類如果get/set方法均是public的脓诡,則需要引起注意,應(yīng)該進行適當(dāng)?shù)姆庋b媒役,而不是全部公有化祝谚。
- 被拒絕的遺贈
子類應(yīng)該繼承超類的函數(shù)和數(shù)據(jù)。但如果他們不想或不需要繼承所有的函數(shù)和數(shù)據(jù)刊愚,則應(yīng)該為這個子類新建一個兄弟類踊跟,把所有用不到的函數(shù)和數(shù)據(jù)放到兄弟類中,他們共享的數(shù)據(jù)和函數(shù)則放到共同的超類中鸥诽。 - 過多的注釋
- 當(dāng)你感覺需要撰寫注釋時商玫,請先嘗試重構(gòu),試著讓所有注釋都變得多余
- 如果你不知道該做什么的牡借,這才是注釋的良好運用時機拳昌。
第4章 構(gòu)筑測試體系
確保所有測試都完全自動化,讓他們檢查自己的測試結(jié)果钠龙。
第5章 重構(gòu)列表
第6章 重新組織函數(shù)
- 提煉函數(shù)
- 動機:首先炬藤,如果每個函數(shù)的粒度都很小,那么函數(shù)被服用的機會就更大碴里;其次沈矿,這回是高層函數(shù)讀起來就像一系列注釋;再次咬腋,如果函數(shù)都是細粒度羹膳,那么函數(shù)的覆寫也會更容易些。函數(shù)的長度不是問題根竿,關(guān)鍵在于函數(shù)名稱和函數(shù)本體之間的語義距離陵像。如果提煉可以強化代碼的清晰度,那么就去做寇壳,就算函數(shù)名稱比提煉出來的代碼還長也無所謂醒颖。
- 做法:創(chuàng)造一個新函數(shù),根據(jù)這個函數(shù)的意圖來對它命名(以它“做什么”來命名壳炎,而不是以它“怎樣做”命名)泞歉。
- 內(nèi)聯(lián)函數(shù)
- 內(nèi)聯(lián)臨時變量
- 引入解釋性變量
將復(fù)雜表達式(或其中一部分)的結(jié)果放進一個臨時變量,以此變量名稱來解釋表達式用途。 - 分解臨時變量
- 移除對參數(shù)的賦值
- 以函數(shù)對象取代函數(shù)
- 替換算法
第7章 在對象之間搬移特性
- 在對象的設(shè)計過程中腰耙,“決定把責(zé)任放在哪兒”即使不是最重要的事偿洁,也是最重要的事之一。
- 搬移函數(shù)
- 搬移字段
- 提煉類
- 將類內(nèi)聯(lián)化
- 隱藏委托關(guān)系
- 移除中間人
- 引入外加函數(shù)
- 引入本地擴展
第8章 重新組織數(shù)據(jù)
- 自封裝字段
- 以對象取代數(shù)據(jù)值
- 將值對象改為引用對象
- 將引用對象改為值對象
- 值對象有一個非常重要的特性:它們應(yīng)該是不可變的沟优。
- 定義了equals(),就必須同時定義hashCode()睬辐。實現(xiàn)hashCode()有個簡單的辦法:讀取equals()使用的所有字段的hash碼挠阁,然后對它們進行按位異或(^)操作。
- 以對象取代數(shù)組
小結(jié):用對象替代數(shù)組的好處就是就對數(shù)組的各種操作都可以封裝起來溯饵。因此侵俗,對于那些需要各種操作的數(shù)組,最好封裝起來丰刊。
- 復(fù)制“被監(jiān)視數(shù)據(jù)”
- 將單向關(guān)聯(lián)改為雙向關(guān)聯(lián)
- 將雙向關(guān)聯(lián)改為單向關(guān)聯(lián)
- 以字面常量取代魔法數(shù)
- 封裝字段
- 封裝集合
- 以數(shù)據(jù)類取代記錄
- 以類取代類型碼
- 以子類取代類型碼
- 以State/Strategy取代類型碼
- 以字段取代子類
第9章 簡化條件表達式
- 分解條件表達式
如果條件表達式中的條件太多太長隘谣,則可以把條件抽取成一個函數(shù),增加代碼可讀性啄巧。
- 合并條件表達式
- 如果檢查條件各不相同寻歧,最終行為卻一致,就應(yīng)該使用“邏輯或”和“邏輯與”將他們合并為一個條件表達式秩仆。
- 如果你認(rèn)為這些檢查的確彼此獨立码泛,的確不應(yīng)該被視為同一次檢查,那么就不要使用本項重構(gòu)澄耍。
- 合并重復(fù)的條件片段
- 移除控制標(biāo)記
適當(dāng)?shù)倪\用break語句和continue語句可以取代標(biāo)志位的使用 - 以衛(wèi)語句取代嵌套條件表達式
- 衛(wèi)語句噪珊,是指在方法最終返回語句前,加入其它返回語句(return語句)
- 拆分條件齐莲,然后加入適當(dāng)?shù)男l(wèi)語句痢站,以減少條件的嵌套層數(shù)
- 將條件反轉(zhuǎn),然后加入適當(dāng)?shù)男l(wèi)語句选酗,以減少條件的嵌套層數(shù)
- 以多態(tài)取代條件表達式
- 引入
Null
對象 - 引入斷言
第10章 簡化函數(shù)調(diào)用
- 函數(shù)改名
- 添加參數(shù)
- 移除參數(shù)
- 將查詢函數(shù)和修改函數(shù)分離
- 令函數(shù)攜帶參數(shù)
- 以明確函數(shù)取代參數(shù)
- 保持對象完整
- 有時候阵难,你會將來自同一對象的若干項數(shù)據(jù)作為參數(shù),傳遞給某個函數(shù)星掰。這樣做的問題在于:萬一將來被調(diào)用的函數(shù)需要新的數(shù)據(jù)項多望,你就必須查找并修改對此函數(shù)的所有調(diào)用。如果你把這些數(shù)據(jù)所屬的整個對象傳給函數(shù)氢烘,可以避免這種尷尬的處境怀偷,因為被調(diào)用函數(shù)可以向那個參數(shù)對象請求任何它想要的信息。
- 如果被調(diào)用函數(shù)使用了來自另一個對象的很多項數(shù)據(jù)播玖,這可能意味該函數(shù)實際上應(yīng)該被定義在那些數(shù)據(jù)所屬的對象中椎工。
- 以函數(shù)取代參數(shù)
- 如果函數(shù)可以通過其他途徑獲得參數(shù)值,那么它就不應(yīng)該通過參數(shù)取得該值。
- 過長的參數(shù)列會增加程序閱讀者的理解難度维蒙,因此我們應(yīng)該盡可能縮短參數(shù)列的長度掰吕。
- 引入?yún)?shù)對象
某些參數(shù)總是很自然地同時出現(xiàn),這時颅痊,可以用一個對象取代這些參數(shù)殖熟。 - 移除設(shè)值函數(shù)
- 類中的某個字段應(yīng)該在對象創(chuàng)建時被設(shè)值,然后就不再改變斑响。這樣的字段應(yīng)該去掉其對應(yīng)的設(shè)值函數(shù)菱属。
- 如果你為某個字段提供了設(shè)值函數(shù),這就暗示這個字段值可以被改變舰罚。如果你不希望在對象創(chuàng)建之后此字段還有機會被改變纽门,那就不要為它提供設(shè)值函數(shù)(同時該字段設(shè)為
final
)。這樣你的意圖會更加清晰营罢,并且可以排除其值被修改的可能性——這種可能性往往是非常大的赏陵。
- 隱藏函數(shù)
- 不會被其他任何類用到的函數(shù)的訪問類型應(yīng)該為
private
。 - 盡可能降低所有函數(shù)的可見度饲漾。
- 不會被其他任何類用到的函數(shù)的訪問類型應(yīng)該為
- 以工廠函數(shù)取代構(gòu)造函數(shù)
- 在派生子類的過程中以工廠函數(shù)取代類型碼蝙搔。
- 如果要根據(jù)類型來創(chuàng)建不同的對象,這些對象有共同的父類能颁,則可以在父類中添加一個工廠函數(shù)來創(chuàng)建不同的對象杂瘸。
- 封裝向下轉(zhuǎn)型
如果某個函數(shù)返回的對象,需要由函數(shù)調(diào)用者執(zhí)行向下轉(zhuǎn)型伙菊,則可以將向下轉(zhuǎn)型動作移到函數(shù)中败玉。 - 以異常取代錯誤碼
- 以測試取代異常
第11章 處理概括關(guān)系
-
字段上移
- 如果兩個子類擁有相同的字段,則將該字段移至超類镜硕。
- 如果這些字段是
private
的运翼,你必須將超類的字段聲明為protected
,這樣子類才能引用它兴枯。
-
將函數(shù)上移
- 有些函數(shù)在各個子類中產(chǎn)生完全相同的結(jié)果血淌,則將該函數(shù)移至超類。
- 如果你使用的是一種強類型語言财剖,而待提升函數(shù)又調(diào)用了一個只出現(xiàn)于子類而未出現(xiàn)于超類的函數(shù)悠夯,你可以在超類中為被調(diào)用函數(shù)聲明一個抽象函數(shù)。
構(gòu)造函數(shù)本體上移
如果在各個子類中擁有一些構(gòu)造函數(shù)躺坟,并且它們的本體幾乎完全一致沦补。那么可以在超類中新建一個構(gòu)造函數(shù),并在子類構(gòu)造函數(shù)中調(diào)用它咪橙。函數(shù)下移
如果超類中的某個函數(shù)只與部分(而非全部)子類有關(guān)夕膀,則將這個函數(shù)移到相關(guān)的那些子類中去虚倒。字段下移
超類中的某個字段只被部分(而非全部)子類用到,則將這個字段移到需要它的那些子類去产舞。提煉子類
如果類中的某些特性只被某些(而非全部)實例用到魂奥,則新建一個子類,將上面所說的那一部分特性移到子類中易猫。提煉超類
如果兩個類有相似特性耻煤,則為這兩個類建立一個超類,將相同特性移至超類准颓。提煉接口
如果某各類在不同環(huán)境下扮演截然不同的角色违霞,使用接口就是個好主意。折疊繼承體系
如果超類和子類之間無太大區(qū)別瞬场,則將它們合為一體。塑造模板函數(shù)
你有一些子類涧郊,其中相應(yīng)的某些函數(shù)以相同的順序執(zhí)行類似的操作贯被,但各個操作的細節(jié)上有所不同。將這些操作分別放進獨立函數(shù)中妆艘,并保持它們都有相同的簽名彤灶,于是原函數(shù)也就變得相同了。然后將原函數(shù)上移至超類批旺。以委托取代繼承
以繼承取代委托
第12章 大型重構(gòu)
- 梳理并分解繼承體系
- 如果某個繼承體系同時承擔(dān)兩項責(zé)任幌陕,則可以建立兩個繼承體系,并通過委托關(guān)系讓其中一個可以調(diào)用另一個汽煮。
- 要指出繼承體系是否承擔(dān)了兩項不同的責(zé)任并不困難:如果繼承體系中的某一特定層級上的所有類搏熄,其子類名稱都以相同的形容詞開始,那么這個體系可能就是承擔(dān)著兩項不同的責(zé)任暇赤。
- 將過程化設(shè)計轉(zhuǎn)化為對象設(shè)計
- 將領(lǐng)域和表述/顯示分離
某些GUI類之中包含了領(lǐng)域邏輯心例,將領(lǐng)域邏輯分離出來,為它們建立獨立的領(lǐng)域類鞋囊。 - 提煉繼承體系
如果你有某個類做了太多工作止后,其中一部分工作是以大量條件表達式完成的,那么可以建立繼承體系溜腐,以一個子類表示一種特殊情況译株。
第13章 重構(gòu),復(fù)用與現(xiàn)實
- 現(xiàn)實的檢驗
- 為什么開發(fā)者不愿意重構(gòu)他們的程序
- 再論現(xiàn)實檢驗
- 重構(gòu)的資源和參考資料
- 從重構(gòu)聯(lián)想到軟件復(fù)用和技術(shù)傳播
第14章 重構(gòu)工具
- 使用工具進行重構(gòu)
- 重構(gòu)工具的技術(shù)標(biāo)準(zhǔn)
- 重構(gòu)工具的實用標(biāo)準(zhǔn)
第15章 總結(jié)
要點列表
- 如果你發(fā)現(xiàn)自己需要為程序添加一個特性挺益,而代碼結(jié)構(gòu)使你無法很方便地達成目的歉糜,那就先重構(gòu)那個程序,使特性的添加比較容易進行矩肩,然后再添加新特性现恼。
- 重構(gòu)前肃续,先檢查自己是否有一套可靠的測試機制。這些測試必須有自我檢驗?zāi)芰Α?/li>
- 重構(gòu)技術(shù)就是以微小的步伐修改程序叉袍。如果你犯下錯誤始锚,很容易便可發(fā)現(xiàn)它。
- 任何一個傻瓜都能寫出計算機可以理解的代碼喳逛。唯有寫出人類容易理解的代碼瞧捌,才是優(yōu)秀程序員。
- 重構(gòu)(名詞):對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整润文,目的是在不改變軟件可觀察行為的前提下姐呐,提高其可理解性,降低其修改成本典蝌。
- 重構(gòu)(動詞):使用一系列重構(gòu)手法曙砂,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)骏掀。
- 事不過三鸠澈,三則重構(gòu)
- 不要過早發(fā)布接口。請修改你的代碼所有權(quán)政策截驮,使重構(gòu)更順暢笑陈。
- 當(dāng)你感覺需要撰寫注釋時,請先嘗試重構(gòu)葵袭,試著讓所有注釋都變得多余涵妥。
- 確保所有測試都完全自動化,讓它們檢查自己的測試結(jié)果坡锡。
- 一套測試就是一個強大的
bug
偵測器蓬网,能夠大大縮減查找bug
所需要的時間。 - 頻繁地運行測試鹉勒。每次編譯請把測試也考慮進去——每天至少執(zhí)行每個測試一次拳缠。
- 每當(dāng)你收到
bug
報告,請先寫一個單元測試來暴露這只bug
贸弥。 - 編寫未臻完善的測試并實際運行窟坐,好過對完美測試的無盡等待。
- 考慮可能出錯的邊界條件绵疲,把測試火力集中在那兒哲鸳。
- 當(dāng)事情被大家認(rèn)為應(yīng)該會出錯時,別忘了檢查是否拋出了預(yù)期的異常盔憨。
- 不要因為測試無法捕捉所有
bug
就不寫測試徙菠,因為測試的確可以捕捉到大多數(shù)bug
。
對于比較簡單或者筆者暫時還沒有很好理解的部分郁岩,只是一筆帶過婿奔;而對于筆者在工作中遇到過相似問題的地方缺狠,則摘錄地比較詳細∑继《重構(gòu):改善代碼的既有設(shè)計》一書非常經(jīng)典挤茄,需要閱讀、實踐冰木、再閱讀穷劈、再實踐。如果你也是一個注重代碼簡潔踊沸、美觀和高效的程序員歇终,那么這本書一定不能錯過,深入閱讀一定能提升代碼的質(zhì)量逼龟。本文會在后續(xù)繼續(xù)更新完善评凝,剔除簡單和不常用的部分,完善和保留實用的部分腺律。
本文作者: Sheh偉偉
本文鏈接: http://davidsheh.github.io/2016/03/03/「重構(gòu):改善代碼的既有設(shè)計」讀書筆記/
版權(quán)聲明: 本博客所有文章除特別聲明外肥哎,均采用 CC BY-NC-SA 3.0 許可協(xié)議。轉(zhuǎn)載請注明出處疾渣!