重構(gòu)改善既有的代碼設(shè)計(代碼的壞味道)

壞的味道:指的是應(yīng)該被修改,被重構(gòu)的代碼广恢,不具有可讀性凯旋,難理解,冗余代碼袁波。應(yīng)該使用各種重構(gòu)的手法去改變它瓦阐!


[TOC]

Duplicated Code(重復(fù)代碼)

如果你在一個以上的地點(diǎn)看到相同的程序結(jié)構(gòu)蜗侈,那么可以肯定的:設(shè)法將他們合而為一篷牌,程序會變得更好。

  • 同一個類的兩個函數(shù)含有相同的表達(dá)式
  • 兩個互為兄弟子類內(nèi)含相同表達(dá)式
  • 如果兩個毫不相關(guān)的類出現(xiàn)Duplicated Code 應(yīng)該考慮對其中一個將重復(fù)代碼提煉到一個獨(dú)立類種踏幻,然后在另一個類內(nèi)使用這個新類枷颊。

Long Method(過長函數(shù))

我們應(yīng)當(dāng)遵循這樣一條原則:每當(dāng)感覺需要以注釋來說明點(diǎn)什么的時候,我們就需要說明的東西寫進(jìn)一個獨(dú)立的函數(shù)中,并以器用途(而非實現(xiàn)手法)命名夭苗。

  • 我們可以對一組甚至短短一行代碼做這件事信卡。哪怕替換后的函數(shù)調(diào)用動作比函數(shù)自身還長,只要函數(shù)名稱能夠解釋其用途题造,我們也該毫不猶豫得那么做傍菇。關(guān)鍵不在于函數(shù)得長度,而在于函數(shù):“做什么”和“如何做”之間得語義距離界赔。
  • 如何確定該提煉哪一段代碼呢丢习?一個很好得技巧是:尋找注釋。它們通常能指出代碼用途和實現(xiàn)手法之間的語義距離淮悼。如果代碼前方有一行注釋咐低,就是再提醒你L可以將這段代碼替換成一個函數(shù),而且可以在注釋得基礎(chǔ)上給這個函數(shù)命名袜腥。<font color="#dd0000"> 就算只有一行代碼见擦,如果他需要以注釋來說明,那也值得將它提煉導(dǎo)獨(dú)立函數(shù)去羹令。</font>
  • 條件表達(dá)式和循環(huán)常常也是提煉得信號鲤屡。可以使用Decompose Conditional處理條件表達(dá)是特恬。至于循環(huán)执俩,你應(yīng)該將循環(huán)和期內(nèi)的代碼提煉導(dǎo)一個獨(dú)立的函數(shù)中

Large Class(過大的類)

  • 如果類中有太多的實例變量输钩,可以將幾個變量提煉至新類內(nèi)师倔。提煉時應(yīng)該選擇類內(nèi)彼此相關(guān)的變量,將它們放在一起
  • 類內(nèi)如果有太多代碼掺逼,把多余的東西消弭于類內(nèi)部显拜。如果有五個“百行代碼”衡奥,它們之中很多代碼都相同,那么或許你可以把它們變成五個“十行函數(shù)”和十個提煉出的“雙行函數(shù)”远荠。

Long Parameter List(過長的參數(shù)列)

如果已有的對象發(fā)出一條請求就可以取代一個參數(shù)矮固,那么你應(yīng)該激活重構(gòu)手法Replace Paramter with Method。在這里譬淳,“已有的對象”可能時函數(shù)所屬類內(nèi)一個字段档址,也可能是另一個參數(shù)。你也可以運(yùn)用Preserve Whole Object將來自同一個對象的一堆數(shù)據(jù)收集起來邻梆,并以該對象替換它們守伸。如果某些數(shù)據(jù)缺乏合理的對象歸屬,可以使用Introduce Parameter Object 為它們制造出一個“參數(shù)對象”浦妄。

Divergent Change(發(fā)散式變化)

一旦需要修改尼摹,我們希望能夠跳到系統(tǒng)的某一點(diǎn)见芹,只在該處做修改。如果不能做到這點(diǎn)蠢涝,你就嗅出兩種相關(guān)的刺鼻味道中的一種了玄呛。

  • 針對某一外界變化的所有相應(yīng)修改,<font color="#dd0000"> 都只應(yīng)發(fā)生在單一類中</font>和二,而這個新類內(nèi)的所有內(nèi)容都應(yīng)該反應(yīng)此變化徘铝,為此,你應(yīng)該找出某特定原因而造成的所有變化惯吕,然后運(yùn)用Extract Class將它們提煉導(dǎo)另一個類中庭砍。

Shotgun Surgery(散彈式修改)

遇到某種變化,你都必須在不同類內(nèi)做出許多小修改混埠,你所面臨的壞味道就是shotgun Surgery怠缸,如果需要修改的代碼散布四處,你不但很難找到它們钳宪,也很容易忘記某個重要的修改

  • 這種情況下應(yīng)該使用Move MethodMove Field把所有需要修改的代碼放進(jìn)同一個類揭北。如果眼下沒有合適的類可以安置這些代碼,就創(chuàng)造一個吏颖。通成μ澹可以運(yùn)用inline class把一系列相關(guān)行為放進(jìn)同一個類。
  • Divergent change 是指<font color="#dd0000">“一個類受多種變化影響”</font>半醉,shotgun surgery則是指<font color="#dd0000">“一種變化引發(fā)多個類相應(yīng)修改”</font>疚俱。者兩種情況下你都會希望整理代碼,使“外界變化”與“需要修改的類”趨于一一對應(yīng)缩多。

Feature Envy(依戀情結(jié))

函數(shù)對某個類的興趣高過對自己所處類的興趣呆奕,通常焦點(diǎn)便是數(shù)據(jù),某個函數(shù)為了計算某個值衬吆,從另一個對象那調(diào)用幾乎半打的額取值函數(shù)梁钾。

  • 療法顯而易見:把這個函數(shù)移至另一個地點(diǎn)。
  • 一個函數(shù)往往會用到幾個類的功能逊抡,那么它究竟該被置于何處呢姆泻?我們的原則是判斷哪個類擁有最多被此函數(shù)使用的數(shù)據(jù),然后把這個函數(shù)和那些數(shù)據(jù)擺在一起冒嫡。

Data Clumps(數(shù)據(jù)泥團(tuán))

  • 兩個類中相同的字段拇勃、許多函數(shù)簽名中相同的參數(shù)。這些總是綁在一起出現(xiàn)的數(shù)據(jù)真應(yīng)該擁有屬于它們自己的對象
  • 一個好的評判方法是:刪掉眾多數(shù)據(jù)中的一項孝凌。這么做方咆,其他數(shù)據(jù)有沒有因而失去意義?如果它們不在有意義胎许,這就是個明確信號:<font color="#dd0000">你應(yīng)該為它們產(chǎn)生一個新對象</font>峻呛。

Primitive Obsession(基本類型偏執(zhí))

對象的一個極大的價值在于:它們模糊(甚至打破)了橫亙于基本類型數(shù)據(jù)和體積較大類之間的界限

  • 可以運(yùn)用Replace Data Vlaue with Object將原本單獨(dú)存在的數(shù)據(jù)值替換為對象
  • 如果想要替換的數(shù)據(jù)值是類型碼,而它并不影響行為辜窑,則可以運(yùn)用Replace type Code with class 將它替換
  • 如果有與類型碼有關(guān)的條件表達(dá)式钩述,可運(yùn)用 Replace Type Code with Subclass 或 Replace Type Code with State/Strategy

Switch Statements(switch 驚悚現(xiàn)身)

大多數(shù)時候,一看到switch語句穆碎,<font color="#dd0000">你就應(yīng)該考慮以多態(tài)來替換它</font>牙勘。問題是多態(tài)應(yīng)該出現(xiàn)在哪兒?switch語句常常根據(jù)類型碼進(jìn)行選擇所禀,你要的是“與該類型碼相關(guān)的函數(shù)或類

Parallel Inheritance Hierarchies(平行繼承體系)

Shotgun Surgery的特殊情況方面,在這種情況下,每當(dāng)你為某個類增加一個子類色徘,也必須為另一個類相應(yīng)的增加一個子類恭金。<font color="#00dd00">如果你發(fā)現(xiàn)某個繼承體系的名稱前綴和另一個繼承體系名稱前綴完全相同</font>,便是聞到了這種壞味道

消除這種重復(fù)性的一般策略是:<font color="#00dd00">讓一個繼承體系的實例引用另一個繼承體系的實例</font>褂策。再運(yùn)用Move Method 和Move Field横腿,就可以就將引用端的繼承體系消弭于無形

Lazy Class(冗贅類)

如果一個類的所得不值其身價,它就該消失

如果某些子類沒有做足夠的工作斤寂,試試Collapse Hierarchy耿焊。對于幾乎沒用的組件,你應(yīng)該以Inline Class對付它們

Speculative Generality(夸夸其談的未來性)

<font color="#000066">不要過分對的為未來考慮</font>

如果你的某個抽象類其實沒有太大作用遍搞,請運(yùn)用Collapse Hierarchy罗侯。不必要的委托可運(yùn)用Inline Class除掉。如果函數(shù)的某些參數(shù)違背用上溪猿,可對它實施Remove Parameter钩杰。如果函數(shù)名稱帶有多余的抽象意味,應(yīng)該對它實施rename Method诊县,讓它現(xiàn)實一點(diǎn)

Temporary Field (令人迷惑的暫時字段)

某個實例變量僅為某種特定的情況而設(shè)榜苫。這樣的代碼讓人不易理解,因為你通常認(rèn)為對象在所有時候都需要它的所有變量翎冲,在未被使用的情況下猜測當(dāng)初其設(shè)置目的垂睬,會讓你發(fā)瘋的。<font color="#dd0000">把所有和這個變量相關(guān)的代碼新建一個類放入</font>抗悍。

Message Chains(過度耦合的消息鏈)

如果你看到用戶一個對象請求另一個對象驹饺,然后后者請求另一個對象,然后再請求另一個對象這就是消息鏈

先觀察消息鏈最終得到的對象用來干什么的缴渊,看看能否以Extract Method 把使用該對象的代碼提煉到一個獨(dú)立的函數(shù)中再運(yùn)用Move Method 把這個函數(shù)推入消息鏈

Middle Man(中間人)

人們可能過度運(yùn)用委托赏壹,會有某個接口有一半的函數(shù)都委托給其他類,這樣就是過度運(yùn)用, 使用Remove Middle Man 衔沼,<font color="#dd0000">直接和真正負(fù)責(zé)的對象打交道</font>

Inappropriate Intimacy(狎昵關(guān)系)

  • 過分狎昵的類必須拆散. 你可以采用Move MethodMove Field 幫它們劃分界限, 從而減少狎昵行徑. 你可以可以看看是否可以運(yùn)用Change Bidirectional Association to Unidirectional (將雙向關(guān)聯(lián)改為單向關(guān)聯(lián))讓其中一個類對另一個斬斷情絲.
  • 如果兩個類實在是情投意合, 可以運(yùn)用Extract Class 把兩者的共同點(diǎn)提煉到一個安全的地點(diǎn),讓它們坦蕩地使用這個新類. 或者也可以嘗試運(yùn)用Hide Delegate讓另一個類來為它們傳遞相思情.
  • 繼承往往造成過度親密, 因為子類對超類的了解總是超過超類的主管愿望. 如果你覺的該讓這個孩子獨(dú)立生活了, 請運(yùn)用Replace Inheritance with Delegation 讓它離開繼承體系.

Alternative Class with Different Interfaces(異曲同工的類)

如果兩個函數(shù)做同一件事蝌借,卻有著不同的簽名昔瞧,請運(yùn)用Rename Method根據(jù)它們的用途重新命名。但這往往不夠菩佑,請反復(fù)運(yùn)用 Move Method將某些行為移入類自晰,直到2者的協(xié)議一致為止。如果你必須反復(fù)而贅余的移入代碼才能完成這些稍坯,或許可運(yùn)用Extract SuperClass

Incomplete Library Class(不完美的類庫)

如果只想修改類庫的一兩個函數(shù)酬荞,可以運(yùn)用Introduce Foreign Method 如果想要添加一大堆額外行為,就得運(yùn)用Introduce Local Extension

Data Class(純稚的數(shù)據(jù)類)

所謂Data Class是指:它們擁有一些值域(fields)瞧哟,以及用于訪問(讀寫〕這些值域的函數(shù)混巧,除此之外一無長物。

  • 這樣的classes只是一種「不會說話的數(shù)據(jù)容器」勤揩,它們幾乎一定被其他classes過份細(xì)瑣地操控著咧党。這些classes早期可能擁有public值域,果真如此你應(yīng)該在別人注意到它們之前陨亡,立刻運(yùn)用Encapsulate Field (封裝值域)將它們封裝起來凿傅。如果這些classes內(nèi)含容器類的值域(collection fields),你應(yīng)該 檢査它們是不是得到了恰當(dāng)?shù)姆庋b数苫;如果沒有聪舒,就運(yùn)用 Encapsulate Collection(封裝群集) 把它們封裝起來。對于那些不該被其他classes修改的值域虐急,請運(yùn)用 Remove Setting Method(移除設(shè)置函數(shù))箱残。
  • 然后,找出這些「取值/設(shè)值」函數(shù)(getting and setting methods)被其他classes運(yùn)用的地點(diǎn)止吁。嘗試以Move Method(搬移函數(shù)) 把那些調(diào)用行為搬移到Data Class來被辑。如果無法搬移整個函數(shù),就運(yùn)用 Extract Method(提煉函數(shù)) 產(chǎn)生一個可被搬移的函數(shù)敬惦。不久之后你就可以運(yùn)用Hide Method (隱藏某個函數(shù))把這些「取值/設(shè)值」函數(shù)隱藏起來了盼理。

Refused Bequest(被拒絕的遺贈)

指的是一個子類,不需要父類中的過多方法

這樣我們可以為這個子類創(chuàng)建一個兄弟類俄删,把父類中不需要的方法下移到兄弟類中去宏怔。 如果子類復(fù)用了超類的行為(實現(xiàn)),卻又不愿意支持超類的接口畴椰。不過即使不愿意繼承接口臊诊,也不要胡亂修改繼承體系,應(yīng)該運(yùn)用Replace Inheritance with Delegation 來達(dá)到目的

Comments(過多的注釋)

引用作者的一句話<font color="#dd0000">"當(dāng)你感覺需要撰寫注釋斜脂,請先嘗試重構(gòu)抓艳,試著讓所有注釋都變得多余。"</font>

并不是說寫注釋不好帚戳,而是當(dāng)你寫一段很長的注釋來說明代碼邏輯的時候玷或,說明這段代碼真的很糟糕儡首,你就要考慮重構(gòu)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末偏友,一起剝皮案震驚了整個濱河市蔬胯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌约谈,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犁钟,死亡現(xiàn)場離奇詭異棱诱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涝动,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門迈勋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人醋粟,你說我怎么就攤上這事靡菇。” “怎么了米愿?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵厦凤,是天一觀的道長。 經(jīng)常有香客問我育苟,道長较鼓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任违柏,我火速辦了婚禮博烂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漱竖。我一直安慰自己禽篱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布馍惹。 她就那樣靜靜地躺著躺率,像睡著了一般。 火紅的嫁衣襯著肌膚如雪万矾。 梳的紋絲不亂的頭發(fā)上肥照,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天,我揣著相機(jī)與錄音勤众,去河邊找鬼舆绎。 笑死,一個胖子當(dāng)著我的面吹牛们颜,可吹牛的內(nèi)容都是我干的吕朵。 我是一名探鬼主播猎醇,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼努溃!你這毒婦竟也來了硫嘶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤梧税,失蹤者是張志新(化名)和其女友劉穎沦疾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第队,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哮塞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凳谦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忆畅。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尸执,靈堂內(nèi)的尸體忽然破棺而出家凯,到底是詐尸還是另有隱情,我是刑警寧澤如失,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布绊诲,位于F島的核電站,受9級特大地震影響褪贵,放射性物質(zhì)發(fā)生泄漏驯镊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一竭鞍、第九天 我趴在偏房一處隱蔽的房頂上張望板惑。 院中可真熱鬧,春花似錦偎快、人聲如沸冯乘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裆馒。三九已至,卻和暖如春丐怯,著一層夾襖步出監(jiān)牢的瞬間喷好,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工读跷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梗搅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像无切,于是被迫代替她去往敵國和親荡短。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容