《重構(gòu)-改善既有代碼的設(shè)計》Martin Fowler

What:何為重構(gòu)

使用一系列重構(gòu)準(zhǔn)則又厉,在不改變「軟件之可察行為」前提下校摩,優(yōu)化代碼結(jié)構(gòu)几睛。

軟件開發(fā)的時間會分配給兩種行為:添加新功能和重構(gòu)败晴。

在添加新功能時浓冒,不應(yīng)該修改既有代碼,直觀添加新功能尖坤,通過測試稳懒。

而重構(gòu)時,你就不能再添加新功能慢味,只管改進(jìn)程序結(jié)構(gòu)场梆,而盡量不修改測試case。

重構(gòu)是這樣一個過程:它在一個目前可運行的程序上進(jìn)行纯路,企圖在「不改變程序行為」的情況下賦予上述美好性質(zhì)或油,使我們能夠繼續(xù)保持高速開發(fā),從而增加程序的價值驰唬。

Why:為何重構(gòu)

缺乏對過程的精心設(shè)計與必要投入顶岸,只抱著對結(jié)果的美好憧憬提刀上陣,遇到困難就靠“奮斗精神”和加班解決定嗓,這種“刀劈斧砍”不止發(fā)生在缺乏審慎的“重構(gòu)”現(xiàn)場蜕琴,又何嘗不是我們這個行業(yè)的縮影?

程序有兩面價值:「今天可以為你做什么」和「明天可以為你做什么」宵溅。大多數(shù)時候凌简,我們都只關(guān)注自己今天想要程序做什么。不論是修復(fù)錯誤或是添加特性恃逻,我們都是為了讓程序力更強雏搂,讓它在今天更有價值。對于今天的工作寇损,我了解得很充分:對于明天的工作凸郑,我了解得不夠充分。但如果我純粹只是為今天工作矛市,明天我將完全無法工作芙沥。好代碼的檢驗標(biāo)準(zhǔn)是人們能否輕而易舉修改它。

「重構(gòu)」改進(jìn)軟件設(shè)計

「重構(gòu)」使軟件更易被理解

「重構(gòu)」助你找到Bug

「重構(gòu)」助你提高編程速度

為什么開發(fā)者不愿意重構(gòu)他們的程序?有幾個可能的原因:

你不知道如何重構(gòu)而昨。

如果這些利益是長遠(yuǎn)才展現(xiàn)的救氯,何必現(xiàn)在付出這些努力呢?長遠(yuǎn)看來歌憨,說不定當(dāng)項目收獲這些利益時着憨,你已經(jīng)不在職位上了。

代碼重構(gòu)是一項額外工作务嫡,老板付錢給你甲抖,主要是讓你編寫新功能。

重構(gòu)可能破壞現(xiàn)有程序心铃。

重構(gòu)的重要性

拿我們都非常熟悉的一件事來做個比喻吧:我們的身體健康狀況准谚。

從很多角度來說,重構(gòu)就好像運動于个、吃適當(dāng)?shù)氖澄锓湛TS多人都知道:我們應(yīng)該多鍛煉身體,應(yīng)該注意均衡飲食厅篓。有些人的生活文化中非常鼓勵這些習(xí)慣秀存,有些人沒有這些好習(xí)慣也可以混過一段時間,甚至看不出有什么影響羽氮。我們可以找各種借口或链,

但如果一直忽視這些好習(xí)慣,那么我們只是在欺騙自己档押。 有些人之運動和均衡飲食澳盐,動機(jī)著眼于短期利益(例如精力更充沛、身體更靈活令宿、

自尊心增強……等等)叼耙。幾乎所有人都知道這些短期利益非常真實。許多人都時斷時續(xù)做過一些努力粒没,另一些人則是不見棺材不掉淚筛婉,不到關(guān)鍵時刻不會有足夠動力去做點什么事。


When:何時重構(gòu)

三次法則:事不過三癞松,三則重構(gòu)

添加功能時一并重構(gòu)

修補錯誤吋一并重構(gòu)

復(fù)審代碼吋一并重構(gòu)

何吋不該重構(gòu)爽撒?

如果還沒有徹底理解既有的代碼,丑陋的代碼能被隱藏在一個API之下响蓉,我就可以容忍它繼續(xù)保持丑陋硕勿。只有當(dāng)我需要理解其工作原理時,對其進(jìn)行重構(gòu)才有價值枫甲。

另一種情況是源武,如果重寫比重構(gòu)還容易扼褪,就別重構(gòu)了。

另外粱栖,如果項目已近最后期限迎捺,你也應(yīng)該避免重構(gòu)。在此時機(jī)查排,從重構(gòu)過程贏得的生產(chǎn)力只有在最后期限過后才能體現(xiàn)出來,而那個時候已經(jīng)時不我予抄沮。把未完成的重構(gòu)工作形容為「債務(wù)」跋核。很多公司都需要借債來使自己更有效地運轉(zhuǎn)。但是借債就得付利息叛买,過于復(fù)雜的代碼所造成的「維護(hù)和擴(kuò)展的額外開銷」就是利息砂代。你可以承受一定程度的利息,但如果利息太高你就會被壓垮率挣。把債務(wù)管理好是很重要的刻伊,你應(yīng)該隨時通過重構(gòu)來償還一部分債務(wù)。

如果項目己經(jīng)非常接近最后期限椒功,你不應(yīng)該再分心于重構(gòu)捶箱,因為己經(jīng)沒有時間了。不過多個項目經(jīng)驗顯示:重構(gòu)的確能夠提高生產(chǎn)力动漾。如果最后你沒有足夠時間丁屎,通常就表示你其實早該進(jìn)行重構(gòu)。


HOW:如何重構(gòu)

我們希望程序:

(1)容易理解旱眯;

(2)所有邏輯都只在唯一地點指定晨川;

(3)新的改動不會危及現(xiàn)有行為;

(4)盡可能簡單表達(dá)條件邏輯(conditional logic)删豺。

詳見《重構(gòu)-改善既有代碼的設(shè)計》第一版共虑,第二版。


重構(gòu)中常見的重要問題

「該怎么跟經(jīng)理說重構(gòu)的事呀页?」

如果這位經(jīng)理懂技術(shù)妈拌,那么向他介紹重構(gòu)應(yīng)該不會很困難泛粹。如果這位經(jīng)理只對質(zhì)量感興趣涣达,那么問題就集中到了「質(zhì)量」上面。此時妙啃,在復(fù)審過程中使用重構(gòu)疾党,就是一個不錯的辦法音诫。

大量研究結(jié)果顯示,「技術(shù)復(fù)審」是減少錯誤雪位、提高開發(fā)速度的一條重要途徑竭钝。隨便找一本關(guān)于復(fù)審、審査或軟件開發(fā)程序的書看看,從中找些最新引證香罐,應(yīng)該可以讓大多數(shù)經(jīng)理認(rèn)識復(fù)審的價值卧波。然后你就可以把重構(gòu)當(dāng)作「將復(fù)審意見引入代碼內(nèi)」的方法來使用,這很容易庇茫。

當(dāng)然港粱,很多經(jīng)理嘴巴上說自己「質(zhì)量驅(qū)動」,其實更多是「進(jìn)度驅(qū)動」旦签。這種情況下我會給他們一個較有爭議的建議:不要告訴經(jīng)理查坪!


「接口迭代問題」

原則:不要過早發(fā)布(publish)接口。修改代碼擁有權(quán)政策宁炫,使重構(gòu)更順暢偿曙。

很多重構(gòu)手法不僅會影響一個模塊內(nèi)部,還會影響該模塊與系統(tǒng)其他部分的關(guān)系羔巢。比如我想給一個函數(shù)改名望忆,并且我也能找到該函數(shù)的所有調(diào)用者,那么我只需運用改變函數(shù)聲明竿秆,在一次重構(gòu)中修改函數(shù)聲明和調(diào)用者启摄。但即便這么簡單的一個重構(gòu),有時也無法實施:調(diào)用方代碼可能由另一支團(tuán)隊擁有袍辞,而我沒有權(quán)限寫入他們的代碼庫鞋仍;這個函數(shù)可能是一個提供給客戶的API,這時我根本無法知道是否有人使用它搅吁,至于誰在用威创、用得有多頻繁就更是一無所知。這樣的函數(shù)屬于已發(fā)布接口:接口的使用者(客戶端)與聲明者彼此獨立谎懦,聲明者無權(quán)修改使用者的代碼肚豺。

代碼所有權(quán)的邊界會妨礙重構(gòu),因為一旦我自作主張地修改界拦,就一定會破壞使用者的程序吸申。這不會完全阻止重構(gòu),我仍然可以做很多重構(gòu)享甸,但確實會對重構(gòu)造成約束截碴。為了給一個函數(shù)改名,我需要使用函數(shù)改名蛉威,但同時也得保留原來的函數(shù)聲明日丹,使其把調(diào)用傳遞給新的函數(shù)。這會讓接口變復(fù)雜蚯嫌,但這就是為了避免破壞使用者的系統(tǒng)而不得不付出的代價哲虾。我可以把舊的接口標(biāo)記為“不推薦使用”(deprecated)丙躏,等一段時間之后最終讓其退休;但有些時候束凑,舊的接口必須一直保留下去晒旅。

由于這些復(fù)雜性,我建議不要搞細(xì)粒度的強代碼所有制汪诉。有些組織喜歡給每段代碼都指定唯一的所有者废恋,只有這個人能修改這段代碼。我曾經(jīng)見過一支只有三個人的團(tuán)隊以這種方式運作扒寄,每個程序員都要給另外兩人發(fā)布接口拴签,隨之而來的就是接口維護(hù)的種種麻煩。如果這三個人都直接去代碼庫里做修改旗们,事情會簡單得多。我推薦團(tuán)隊代碼所有制构灸,這樣一支團(tuán)隊里的成員都可以修改這個團(tuán)隊擁有的代碼上渴,即便最初寫代碼的是別人。程序員可能各自分工負(fù)責(zé)系統(tǒng)的不同區(qū)域喜颁,但這種責(zé)任應(yīng)該體現(xiàn)為監(jiān)控自己責(zé)任區(qū)內(nèi)發(fā)生的修改稠氮,而不是簡單粗暴地禁止別人修改。

這種較為寬容的代碼所有制甚至可以應(yīng)用于跨團(tuán)隊的場合半开。有些團(tuán)隊鼓勵類似于開源的模型:B團(tuán)隊的成員也可以在一個分支上修改A團(tuán)隊的代碼隔披,然后把提交發(fā)送給A團(tuán)隊去審核。這樣一來寂拆,如果團(tuán)隊想修改自己的函數(shù)奢米,他們就可以同時修改該函數(shù)的客戶端的代碼;只要客戶端接受了他們的修改纠永,就可以刪掉舊的函數(shù)聲明了鬓长。對于涉及多個團(tuán)隊的大系統(tǒng)開發(fā),在“強代碼所有制”和“混亂修改”兩個極端之間尝江,這種類似開源的模式常常是一個合適的折中涉波。

PS1: 這也是3.0Plus項目初期和中期架構(gòu)組一直堅持源碼依賴的原因,大部分接口變動的情況炭序,是依靠架構(gòu)組同學(xué)直接修改提交使用者的代碼來完成的接口迭代啤覆,保證快速迭代的同時避免編譯報錯。

PS2: 這也是3.0Plus項目后期惭聂,底層庫全部切換為Maven依賴窗声。上ViewFocus功能的時候,被項目要求1天定好ViewFocus的最終接口并宣講彼妻,之后再開發(fā)ViewFocus功能嫌佑。索幸接口之后沒有沒有大的變動豆茫。


「分支問題」

很多團(tuán)隊采用這樣的版本控制實踐:

每個團(tuán)隊成員各自在代碼庫的一條分支上工作,進(jìn)行相當(dāng)大量的開發(fā)之后屋摇,才把各自的修改合并回主線分支(這條分支通常叫master或trunk)揩魂,從而與整個團(tuán)隊分享。常見的做法是在分支上開發(fā)完整的功能炮温,直到功能可以發(fā)布到生產(chǎn)環(huán)境火脉,才把該分支合并回主線。這種做法的擁躉聲稱柒啤,這樣能保持主線不受尚未完成的代碼侵?jǐn)_倦挂,能保留清晰的功能添加的版本記錄,并且在某個功能出問題時能容易地撤銷修改担巩。

這樣的特性分支有其缺點方援。在隔離的分支上工作得越久,將完成的工作集成(integrate)回主線就會越困難涛癌。為了減輕集成的痛苦犯戏,大多數(shù)人的辦法是頻繁地從主線合并(merge)或者變基(rebase)到分支。但如果有幾個人同時在各自的特性分支上工作拳话,這個辦法并不能真正解決問題先匪,因為合并與集成是兩回事。如果我從主線合并到我的分支弃衍,這只是一個單向的代碼移動——我的分支發(fā)生了修改呀非,但主線并沒有。而“集成”是一個雙向的過程:不僅要把主線的修改拉(pull)到我的分支上镜盯,而且要把我這里修改的結(jié)果推(push)回到主線上岸裙,兩邊都會發(fā)生修改。假如另一名程序員正在他的分支上開發(fā)速缆,我是看不見他的修改的哥桥,直到他將自己的修改與主線集成;此時我就必須把她的修改合并到我的特性分支激涤,這可能需要相當(dāng)?shù)墓ぷ髁磕飧狻F渲欣щy的部分是處理語義變化。現(xiàn)代版本控制系統(tǒng)都能很好地合并程序文本的復(fù)雜修改倦踢,但對于代碼的語義它們一無所知送滞。如果我修改了一個函數(shù)的名字,版本控制工具可以很輕松地將我的修改與他的代碼集成辱挥。但如果在集成之前犁嗅,他在自己的分支里新添調(diào)用了這個被我改名的函數(shù),集成之后的代碼就會被破壞晤碘。

分支合并本來就是一個復(fù)雜的問題褂微,隨著特性分支存在的時間加長功蜓,合并的難度會指數(shù)上升。集成一個已經(jīng)存在了4個星期的分支宠蚂,較之集成存在了2個星期的分支式撼,難度可不止翻倍。

所以很多人認(rèn)為求厕,應(yīng)該盡量縮短特性分支的生存周期著隆,比如只有一兩天。還有一些人(比如我本人)認(rèn)為特性分支的生命還應(yīng)該更短呀癣,我們采用的方法叫作持續(xù)集成(Continuous Integration美浦,CI),也叫“基于主干開發(fā)”(Trunk-Based Development)项栏。在使用CI時浦辨,每個團(tuán)隊成員每天至少向主線集成一次。這個實踐避免了任何分支彼此差異太大沼沈,從而極大地降低了合并的難度荤牍。不過CI也有其代價:你必須使用相關(guān)的實踐以確保主線隨時處于健康狀態(tài),必須學(xué)會將大功能拆分成小塊庆冕,還必須使用特性開關(guān)(feature toggle,也叫特性旗標(biāo)劈榨,feature flag)將尚未完成又無法拆小的功能隱藏掉访递。 CI的粉絲之所以喜歡這種工作方式,部分原因是它降低了分支合并的難度同辣,不過最重要的原因還是CI與重構(gòu)能良好配合拷姿。重構(gòu)經(jīng)常需要對代碼庫中的很多地方做很小的修改(例如給一個廣泛使用的函數(shù)改名),這樣的修改尤其容易造成合并時的語義沖突旱函。采用特性分支的團(tuán)隊常會發(fā)現(xiàn)重構(gòu)加劇了分支合并的困難响巢,并因此放棄了重構(gòu),這種情況我們曾經(jīng)見過多次棒妨。CI和重構(gòu)能夠良好配合踪古,所以Kent Beck在極限編程中同時包含了這兩個實踐。 我并不是在說絕不應(yīng)該使用特性分支券腔。如果特性分支存在的時間足夠短伏穆,它們就不會造成大問題。(實際上纷纫,使用CI的團(tuán)隊往往同時也使用分支枕扫,但他們會每天將分支與主線合并。)對于開源項目辱魁,特性分支可能是合適的做法烟瞧,因為不時會有你不熟悉(因此也不信任)的程序員偶爾提交修改诗鸭。但對全職的開發(fā)團(tuán)隊而言,特性分支對重構(gòu)的阻礙太嚴(yán)重了参滴。即便你沒有完全采用CI强岸,我也一定會催促你盡可能頻繁地集成。而且卵洗,用上CI的團(tuán)隊在軟件交付上更加高效请唱,我真心希望你認(rèn)真考慮這個客觀事實。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末过蹂,一起剝皮案震驚了整個濱河市十绑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酷勺,老刑警劉巖本橙,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脆诉,居然都是意外死亡甚亭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門击胜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亏狰,“玉大人,你說我怎么就攤上這事偶摔∠就伲” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵辰斋,是天一觀的道長策州。 經(jīng)常有香客問我,道長宫仗,這世上最難降的妖魔是什么够挂? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮藕夫,結(jié)果婚禮上孽糖,老公的妹妹穿的比我還像新娘。我一直安慰自己毅贮,他們只是感情好梭姓,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫩码,像睡著了一般誉尖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铸题,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天铡恕,我揣著相機(jī)與錄音琢感,去河邊找鬼。 笑死探熔,一個胖子當(dāng)著我的面吹牛驹针,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诀艰,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼柬甥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了其垄?” 一聲冷哼從身側(cè)響起苛蒲,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绿满,沒想到半個月后臂外,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡喇颁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年漏健,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橘霎。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔫浆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姐叁,到底是詐尸還是另有隱情瓦盛,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布七蜘,位于F島的核電站,受9級特大地震影響墙懂,放射性物質(zhì)發(fā)生泄漏橡卤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一损搬、第九天 我趴在偏房一處隱蔽的房頂上張望碧库。 院中可真熱鬧,春花似錦巧勤、人聲如沸嵌灰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沽瞭。三九已至,卻和暖如春剩瓶,著一層夾襖步出監(jiān)牢的瞬間驹溃,已是汗流浹背城丧。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留豌鹤,地道東北人亡哄。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像布疙,于是被迫代替她去往敵國和親蚊惯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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