重構(gòu)-靠譜程序員的必備技能

為什么要重構(gòu)

你可能正在面對一個(gè)遺留系統(tǒng)欧瘪,增加一個(gè)需求要改動好幾個(gè)文件腾供,定位 Bug 經(jīng)常要花掉一整天時(shí)間岖沛,修復(fù)一個(gè) Bug 可能又制造了 3 個(gè)新的 Bug。你也可能會為了軟件設(shè)計(jì)和同事爭得面紅耳赤廓握,討論如何應(yīng)對未來可能出現(xiàn)的需求變化搅窿。
為了開發(fā)一個(gè)新需求嘁酿,你打開一份源代碼,完全不知所云嘛男应,你吐槽著誰能寫出如此不堪入目的代碼闹司,于是決定查看版本記錄,把這個(gè)家伙找出來鄙視一下沐飘。然后你在提交歷史里看到了自己的名字... 恭喜你游桩,你進(jìn)步了。如果你是一個(gè)積極進(jìn)取的程序員耐朴,通常在幾個(gè)月甚至幾個(gè)星期之后就認(rèn)不出自己寫的代碼借卧。你總能發(fā)現(xiàn)更好的實(shí)現(xiàn)方式,讓代碼更加優(yōu)雅筛峭。
隨著增加新特性或需求變更铐刘,代碼會變得越來越難以維護(hù)。敏捷軟件開發(fā)的十二條原則中有一條是:我們始終擁抱需求變化影晓,哪怕是在軟件開發(fā)的后期镰吵。為了達(dá)到這種狀態(tài),我們就要在開發(fā)過程中持續(xù)地優(yōu)化代碼挂签。
而重構(gòu)這項(xiàng)技術(shù)疤祭,為我們提供了一種更可控的方式來優(yōu)化代碼。

重構(gòu)是什么

重構(gòu)饵婆,通常指的是「代碼重構(gòu)」勺馆,起源于 Smalltalk 圈子。
在日常工作中侨核,我們把重構(gòu)既作為名詞又作為動詞來使用谓传,作為名詞時(shí),它的意思是:

對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整芹关,目的是在不改變軟件可觀察行為的前提下,提高其可理解性紧卒,降低其修改成本侥衬。

所以我們會說,「這里需要做一個(gè)重構(gòu)」跑芳,「這個(gè)重構(gòu)有點(diǎn)問題」等轴总。
而在其它時(shí)候,我們也會說:「我們來重構(gòu)一下這段代碼吧」博个,「我正在重構(gòu)一個(gè)遺留系統(tǒng)」怀樟,這時(shí)就是把重構(gòu)當(dāng)做動詞在用,它的意思是:

使用一系列重構(gòu)手法盆佣,在不改變軟件可觀察行為的前提下往堡,調(diào)整其結(jié)構(gòu)械荷。

重構(gòu)本質(zhì)上是一種代碼整理技術(shù),這項(xiàng)技術(shù)使得代碼整理的效率更高虑灰,風(fēng)險(xiǎn)更小吨瞎。

如何做

接下來從幾個(gè)方面來說說如何做重構(gòu):

  • 什么時(shí)候開始
  • 什么時(shí)候停止
  • 前提條件
  • 重構(gòu)的過程

什么時(shí)候開始

重構(gòu)不應(yīng)該是一個(gè)單獨(dú)的環(huán)節(jié),應(yīng)該融入到開發(fā)軟件編寫代碼的過程中穆咐,就像使用版本控制系統(tǒng)提交代碼一樣颤诀,是一個(gè)必須做的動作。你不會跟項(xiàng)目經(jīng)理說对湃,我需要申請一段時(shí)間來提交代碼崖叫,所以也不用說服項(xiàng)目經(jīng)理給你時(shí)間重構(gòu)。你可以在開發(fā)新功能拍柒,修復(fù) Bug 的過程中就把重構(gòu)做了心傀,除了你的程序員同伴,沒有人知道你做了什么斤儿。而他們會認(rèn)為你做了一件了不起的事情剧包,因?yàn)槟阕尨a結(jié)構(gòu)更清晰了,以后添加新特性就會更容易往果,而 Bug 也無處藏身疆液。
如果你采用 TDD 的方式(測試驅(qū)動開發(fā)),那重構(gòu)已完全融入到了開發(fā)過程中陕贮。如果沒有采用 TDD堕油,通常有四個(gè)時(shí)機(jī)可以考慮要不要重構(gòu):
事不過三
如果有段代碼讓你修改起來很不舒服,前兩次還可以忍耐肮之,第三次就無需再忍了掉缺,果斷操起 IDE 重構(gòu)之。因?yàn)槌霈F(xiàn)了三次修改戈擒,說明有很大概率以后還會修改眶明,這是一筆劃算的投資。

添加新功能
有時(shí)候我們發(fā)現(xiàn)要添加一個(gè)新功能很難筐高,我們可以對代碼做一些重構(gòu)搜囱,讓添加新功能變得容易。

修復(fù)缺陷
在修 Bug 時(shí)柑土,我們大部分的時(shí)間會花在定位 Bug 上蜀肘,為什么這么難以找到呢?多半是因?yàn)榇a結(jié)構(gòu)不清晰稽屏,如果代碼在同一抽象層次上扮宠,每個(gè)方法都在 10 行以內(nèi),每個(gè)方法名和變量名都能清晰地表達(dá)意圖狐榔,Bug 就再無藏身之處坛增。所以获雕,通過重構(gòu)代碼,可以讓 Bug 自動浮現(xiàn)出來轿偎。

代碼評審
Code Review 已是一個(gè)廣泛采用的實(shí)踐典鸡,在 Code Review 時(shí),其他程序員會提出代碼修改的意見坏晦,記錄下來萝玷,等 Code Review 結(jié)束之后就可以開始重構(gòu)了。

什么時(shí)候停止

重構(gòu)到什么時(shí)候昆婿,我們就認(rèn)為可以停止了呢球碉?
有兩個(gè)標(biāo)準(zhǔn)可以參考,一個(gè)是「簡單設(shè)計(jì)」的四條原則:

  • 通過所有測試
  • 沒有重復(fù)
  • 表達(dá)意圖
  • 最少化程序元素(類仓蛆,接口睁冬,變量,方法等)

另一個(gè)是滿足《Clean Code》(整潔代碼)的要求看疙。

前提條件

現(xiàn)代 IDE豆拨,尤其是 JetBrains 公司的一系列產(chǎn)品,支持常用的重構(gòu)手法能庆,極大地降低了重構(gòu)的風(fēng)險(xiǎn)施禾。但為了保證不改變軟件的可觀察行為,還是需要完善的測試搁胆。我也做過一些沒有測試代碼保護(hù)的重構(gòu)弥搞,通常會加一個(gè)端到端測試以保證不破壞最重要的功能。實(shí)在很難編寫測試代碼渠旁,至少也要手工測試來保證重構(gòu)真的沒有改變軟件行為荤胁。

另一個(gè)重要前提是酌泰,使用版本控制系統(tǒng)摩疑,比如 Git吴侦。因?yàn)槲覀兊闹貥?gòu)并不一定總是令人滿意,也有可能出現(xiàn)錯(cuò)誤杂靶,導(dǎo)致軟件變得不可用承耿,所以最好是小步提交,以保證可以隨時(shí)放棄變更伪煤,回到上一次滿意的狀態(tài)。

重構(gòu)的過程

重構(gòu)的基本步驟是:

  • 測試保護(hù)
  • 識別味道
  • 采用手法
  • 運(yùn)行測試
  • 提交代碼

測試保護(hù)
如果沒有測試代碼凛辣,就要先添加測試代碼抱既。如果有測試代碼,先運(yùn)行一下扁誓,保證在開始重構(gòu)之前防泵,測試是運(yùn)行通過的蚀之。還要認(rèn)真審查一下測試代碼,看是否有遺漏一些場景捷泞,有遺漏的話要補(bǔ)充遺漏的測試場景足删。

識別味道
怎么知道哪些代碼需要重構(gòu)呢?首先锁右,代碼是可以工作的失受,我們并不能說它有問題,但它又不像我們期望的那樣好咏瑟。受 Kent Beck 剛出生的女兒的使用的尿布的啟發(fā)拂到,Martin Fowler 和 Kent Beck 決定用「味道」這個(gè)詞來表示需要重構(gòu)的代碼。他們在《重構(gòu)》一書中列舉了 22 中常見的味道码泞,如果你看《Clean Code》的話兄旬,會發(fā)現(xiàn)還有更多。不過余寥,他們并沒有給出一個(gè)具體的標(biāo)準(zhǔn)领铐,而是需要我們的直覺來判斷。比如多大的類算「過大的類」宋舷?多少行代碼算「過長的方法」绪撵?這些需要自行判斷,而直覺的形成有兩種方法肥缔,一是隨著編碼經(jīng)驗(yàn)的增多自然形成莲兢,另一種更快的方式是大量閱讀優(yōu)秀的開源代碼,提高自己的代碼審美续膳。

《重構(gòu)》一書中的味道可以分為五類:

  • 膨脹劑
  • OO 使用不合理
  • 難以修改
  • 可有可無
  • 耦合

書中都有詳細(xì)的解釋改艇,這里不再贅述。

膨脹劑
濫用 OO
難以修改

發(fā)散式變化和散彈式修改是比較容易混淆的兩個(gè)味道坟岔。前者指一個(gè)類的職責(zé)過多谒兄,有很多因素會引起它的變化,具體的表現(xiàn)就是社付,不同的需求都會修改同一個(gè)文件承疲,導(dǎo)致經(jīng)常沖突,不能順利地并行開發(fā)鸥咖。后者指的是改一個(gè)需求要修改很多個(gè)文件燕鸽,說明沒有把強(qiáng)內(nèi)聚的代碼歸攏到一起。

可有可無

大部分的注釋都是沒有必要的啼辣,注釋應(yīng)該描述「做了什么」和「為什么做」而不是「怎么做」啊研,方法體內(nèi)的注釋基本都可以通過抽取方法并指定一個(gè)有意義的名字來解決。很多為了應(yīng)對未來需求變化而寫的代碼基本永遠(yuǎn)不會被執(zhí)行。

耦合

你可能發(fā)現(xiàn)了党远,有些味道是比較容易識別的削解,比如重復(fù)代碼,注釋等沟娱。而有些就比較高級氛驮,比如特性依戀,中間人等济似,要識別高級味道矫废,需要理解面向?qū)ο蟮奶匦院驮O(shè)計(jì)原則。

采用手法
識別到味道之后碱屁,就要知道有什么對應(yīng)的手法可以消除這個(gè)味道磷脯,執(zhí)行完這個(gè)手法之后代碼會變成什么樣子。
在《重構(gòu)》一書中娩脾,列舉了 66 個(gè)常用手法赵誓,可以分為六大類:

  • 重組函數(shù)
  • 搬移特性
  • 組織數(shù)據(jù)
  • 簡化條件
  • 簡化調(diào)用
  • 處理概括

這些手法在書中都有詳細(xì)的講解,我就不在這里重復(fù)了柿赊。只整理出來俩功,給大家一個(gè)宏觀的印象:

重組函數(shù)
搬移特性
組織數(shù)據(jù)
簡化條件
簡化函數(shù)調(diào)用
處理概括關(guān)系

運(yùn)行測試
在采用了手法修改代碼之后,就要執(zhí)行測試以確保真的沒有改變軟件的行為碰声」铗眩可能有時(shí)會發(fā)現(xiàn),做了重構(gòu)之后測試會失敗胰挑,但實(shí)現(xiàn)并沒有問題蔓罚,我們需要修改測試代碼讓它成功。這就說明測試寫的不合理瞻颂,給重構(gòu)帶來了負(fù)擔(dān)豺谈,所以我們測試的粒度要把握好,太細(xì)的粒度就會增加維護(hù)成本贡这。比如茬末,有些人會給每個(gè)私有方法都寫單元測試,那有可能采用「內(nèi)聯(lián)函數(shù)」這個(gè)手法之后這個(gè)方法就不存在了盖矫,就需要修改測試丽惭。這里說起來話就長了,以后再寫一篇如何寫有效的測試的文章吧辈双。重點(diǎn)是重構(gòu)之后责掏,一定要執(zhí)行測試,不管是手工測試或自動化測試湃望。

提交代碼
最后换衬,如果你采用了一個(gè)比較復(fù)雜的手法局义,或者即將采用一個(gè)復(fù)雜的手法,最好先提交一下代碼冗疮,以保證出現(xiàn)意外后能快速回滾,避免浪費(fèi)時(shí)間檩帐。

重構(gòu)要采取「小步快跑」的原則术幔,盡量采用安全的手法,讓測試一直處于通過的狀態(tài)湃密。
從低級的壞味道開始诅挑,消除低級味道之后,高級味道才會浮現(xiàn)出來泛源。

進(jìn)階

重構(gòu)與設(shè)計(jì)的關(guān)系

在沒有重構(gòu)這個(gè)技術(shù)之前拔妥,廣泛采用的是 Big Front Design,在開始編碼之前要進(jìn)行非常詳細(xì)的設(shè)計(jì)达箍,考慮應(yīng)對未來出現(xiàn)的各種變化没龙。而有了重構(gòu)技術(shù)之后,前期設(shè)計(jì)的壓力就小了缎玫,畢竟可以隨時(shí)通過重構(gòu)來改善設(shè)計(jì)硬纤,應(yīng)對變化。所以你大可不必一上來就應(yīng)用《設(shè)計(jì)模式》把代碼搞復(fù)雜赃磨,先用簡單的實(shí)現(xiàn)滿足當(dāng)前需求即可筝家。等變化真正來臨時(shí),再通過重構(gòu)技術(shù)調(diào)整設(shè)計(jì)邻辉,模式給我們提供了一個(gè)方向溪王,但并不是最終目標(biāo)。還記得簡單設(shè)計(jì)的四條原則嗎值骇?通過測試莹菱,沒有重復(fù),表達(dá)意圖雷客,最少元素芒珠。除了這四條原則,還有 SOLID搅裙,DRY皱卓,KISS 等設(shè)計(jì)原則。只要最終的代碼符合好的原則部逮,干凈整潔沒有壞味道娜汁,管它符不符合某個(gè)模式呢?兄朋!

大型遺留系統(tǒng)的重構(gòu)

對于代碼上百萬掐禁,千萬行的遺留系統(tǒng),怎么重構(gòu)呢?滿地都是壞味道傅事,一點(diǎn)點(diǎn)去重構(gòu)缕允,什么時(shí)候是個(gè)頭?
這時(shí)蹭越,選擇哪些代碼來重構(gòu)就非常重要障本,影響到投資回報(bào)。如果對代碼進(jìn)行分類响鹃,將會得出幾種類型:

  • 不會被執(zhí)行的爛代碼
  • 運(yùn)行穩(wěn)定驾霜,基本不會改動的爛代碼
  • 經(jīng)常發(fā)現(xiàn) Bug 的爛代碼
  • 經(jīng)常需要變更的爛代碼

不會被執(zhí)行的代碼,直接刪除就好了买置。運(yùn)行穩(wěn)定的又不需要改動的粪糙,動它反而可能引入風(fēng)險(xiǎn),當(dāng)然忿项,在時(shí)間充裕的情況下蓉冈,還是可以重構(gòu)的。真正有價(jià)值倦卖,值得重構(gòu)的洒擦,投入產(chǎn)出比最高的,是經(jīng)常出問題和經(jīng)常會有需求變更的爛代碼怕膛。優(yōu)化了這部分代碼熟嫩,可以減少 Bug 和進(jìn)行需求變更的時(shí)間。


好了褐捻。關(guān)于重構(gòu)我想分享的就是這些掸茅,我們來回顧一下:
為什么要重構(gòu)?
為了讓軟件始終可以維護(hù)柠逞,保證開發(fā)效率昧狮。

什么是重構(gòu)?
一種以可控的方式整理代碼的技術(shù)板壮,在不改變軟件可觀察行為的前提下改善其內(nèi)部結(jié)構(gòu)逗鸣。

什么時(shí)候開始?
事不過三绰精,添加功能撒璧,修復(fù) Bug,代碼評審時(shí)笨使。

什么時(shí)候停止卿樱?
重構(gòu)到符合簡單設(shè)計(jì)四條原則的 Clean Code。

前提條件
測試保護(hù)硫椰,版本控制繁调。

重構(gòu)的過程
運(yùn)行測試萨蚕,識別味道(常見的 22 種),采用手法(66 個(gè))蹄胰,運(yùn)行測試岳遥,提交代碼。

重構(gòu)與設(shè)計(jì)的關(guān)系
有了重構(gòu)技術(shù)裕寨,我們不用在前期做非常詳細(xì)的設(shè)計(jì)寒随,做適當(dāng)?shù)脑O(shè)計(jì),然后通過重構(gòu)讓設(shè)計(jì)浮現(xiàn)出來帮坚。不用在乎軟件是否符合模式,只要符合原則即可互艾。

大型遺留系統(tǒng)的重構(gòu)
在經(jīng)常需要修改的爛代碼上做重構(gòu)才有最大收益试和。


最后推薦一些學(xué)習(xí)資源:

本文最初發(fā)布于 GitChat,歡迎閱讀答疑實(shí)錄纫普。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅悍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昨稼,更是在濱河造成了極大的恐慌节视,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件假栓,死亡現(xiàn)場離奇詭異寻行,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匾荆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門拌蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牙丽,你說我怎么就攤上這事简卧。” “怎么了烤芦?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵举娩,是天一觀的道長。 經(jīng)常有香客問我构罗,道長铜涉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任绰播,我火速辦了婚禮骄噪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蠢箩。我一直安慰自己链蕊,他們只是感情好事甜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滔韵,像睡著了一般逻谦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陪蜻,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天邦马,我揣著相機(jī)與錄音,去河邊找鬼宴卖。 笑死滋将,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的症昏。 我是一名探鬼主播随闽,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肝谭!你這毒婦竟也來了掘宪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤攘烛,失蹤者是張志新(化名)和其女友劉穎魏滚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坟漱,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鼠次,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芋齿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片须眷。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沟突,靈堂內(nèi)的尸體忽然破棺而出花颗,到底是詐尸還是另有隱情,我是刑警寧澤惠拭,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布扩劝,位于F島的核電站,受9級特大地震影響职辅,放射性物質(zhì)發(fā)生泄漏棒呛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一域携、第九天 我趴在偏房一處隱蔽的房頂上張望簇秒。 院中可真熱鬧,春花似錦秀鞭、人聲如沸趋观。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皱坛。三九已至编曼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剩辟,已是汗流浹背掐场。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贩猎,地道東北人熊户。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像吭服,于是被迫代替她去往敵國和親敏弃。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

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