程序員必備技能:我覺得必須會重構(gòu),哈哈哈
點擊上方[全棧開發(fā)者社區(qū)]→右上角[...]→[設(shè)為星標?]

為什么要重構(gòu)
你可能正在面對一個遺留系統(tǒng),增加一個需求要改動好幾個文件,定位 Bug 經(jīng)常要花掉一整天時間,修復(fù)一個 Bug 可能又制造了 3 個新的 Bug。
你也可能會為了軟件設(shè)計和同事爭得面紅耳赤,討論如何應(yīng)對未來可能出現(xiàn)的需求變化。
為了開發(fā)一個新需求,你打開一份源代碼,完全不知所云嘛,你吐槽著誰能寫出如此不堪入目的代碼,于是決定查看版本記錄,把這個家伙找出來鄙視一下。
然后你在提交歷史里看到了自己的名字... 恭喜你,你進步了。
如果你是一個積極進取的程序員,通常在幾個月甚至幾個星期之后就認不出自己寫的代碼。你總能發(fā)現(xiàn)更好的實現(xiàn)方式,讓代碼更加優(yōu)雅。
隨著增加新特性或需求變更,代碼會變得越來越難以維護。敏捷軟件開發(fā)的十二條原則中有一條是:我們始終擁抱需求變化,哪怕是在軟件開發(fā)的后期。
為了達到這種狀態(tài),我們就要在開發(fā)過程中持續(xù)地優(yōu)化代碼。
而重構(gòu)這項技術(shù),為我們提供了一種更可控的方式來優(yōu)化代碼。
重構(gòu)是什么
重構(gòu),通常指的是「代碼重構(gòu)」,起源于 Smalltalk 圈子。
在日常工作中,我們把重構(gòu)既作為名詞又作為動詞來使用,作為名詞時,它的意思是:
對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。
所以我們會說,「這里需要做一個重構(gòu)」,「這個重構(gòu)有點問題」等。
而在其它時候,我們也會說:「我們來重構(gòu)一下這段代碼吧」,「我正在重構(gòu)一個遺留系統(tǒng)」,這時就是把重構(gòu)當做動詞在用,它的意思是:
使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。
重構(gòu)本質(zhì)上是一種代碼整理技術(shù),這項技術(shù)使得代碼整理的效率更高,風險更小。
如何做
接下來從幾個方面來說說如何做重構(gòu):
什么時候開始
什么時候停止
前提條件
重構(gòu)的過程
什么時候開始
重構(gòu)不應(yīng)該是一個單獨的環(huán)節(jié),應(yīng)該融入到開發(fā)軟件編寫代碼的過程中,就像使用版本控制系統(tǒng)提交代碼一樣,是一個必須做的動作。
你不會跟項目經(jīng)理說,我需要申請一段時間來提交代碼,所以也不用說服項目經(jīng)理給你時間重構(gòu)。
你可以在開發(fā)新功能,修復(fù) Bug 的過程中就把重構(gòu)做了,除了你的程序員同伴,沒有人知道你做了什么。
而他們會認為你做了一件了不起的事情,因為你讓代碼結(jié)構(gòu)更清晰了,以后添加新特性就會更容易,而 Bug 也無處藏身。
如果你采用 TDD 的方式(測試驅(qū)動開發(fā)),那重構(gòu)已完全融入到了開發(fā)過程中。如果沒有采用 TDD,通常有四個時機可以考慮要不要重構(gòu):
事不過三
如果有段代碼讓你修改起來很不舒服,前兩次還可以忍耐,第三次就無需再忍了,果斷操起 IDE 重構(gòu)之。因為出現(xiàn)了三次修改,說明有很大概率以后還會修改,這是一筆劃算的投資。
添加新功能
有時候我們發(fā)現(xiàn)要添加一個新功能很難,我們可以對代碼做一些重構(gòu),讓添加新功能變得容易。
修復(fù)缺陷
在修 Bug 時,我們大部分的時間會花在定位 Bug 上,為什么這么難以找到呢?多半是因為代碼結(jié)構(gòu)不清晰,如果代碼在同一抽象層次上,每個方法都在 10 行以內(nèi),每個方法名和變量名都能清晰地表達意圖,Bug 就再無藏身之處。
所以,通過重構(gòu)代碼,可以讓 Bug 自動浮現(xiàn)出來。
代碼評審
Code Review 已是一個廣泛采用的實踐,在 Code Review 時,其他程序員會提出代碼修改的意見,記錄下來,等 Code Review 結(jié)束之后就可以開始重構(gòu)了。
什么時候停止
重構(gòu)到什么時候,我們就認為可以停止了呢?
有兩個標準可以參考,一個是「簡單設(shè)計」的四條原則:
通過所有測試
沒有重復(fù)
表達意圖
最少化程序元素(類,接口,變量,方法等)
另一個是滿足《Clean Code》(整潔代碼)的要求。
前提條件
現(xiàn)代 IDE,尤其是 JetBrains 公司的一系列產(chǎn)品,支持常用的重構(gòu)手法,極大地降低了重構(gòu)的風險。但為了保證不改變軟件的可觀察行為,還是需要完善的測試。
我也做過一些沒有測試代碼保護的重構(gòu),通常會加一個端到端測試以保證不破壞最重要的功能。實在很難編寫測試代碼,至少也要手工測試來保證重構(gòu)真的沒有改變軟件行為。
另一個重要前提是,使用版本控制系統(tǒng),比如 Git。因為我們的重構(gòu)并不一定總是令人滿意,也有可能出現(xiàn)錯誤,導(dǎo)致軟件變得不可用,所以最好是小步提交,以保證可以隨時放棄變更,回到上一次滿意的狀態(tài)。
重構(gòu)的過程
重構(gòu)的基本步驟是:
測試保護
識別味道
采用手法
運行測試
提交代碼
測試保護
如果沒有測試代碼,就要先添加測試代碼。如果有測試代碼,先運行一下,保證在開始重構(gòu)之前,測試是運行通過的。還要認真審查一下測試代碼,看是否有遺漏一些場景,有遺漏的話要補充遺漏的測試場景。
識別味道
怎么知道哪些代碼需要重構(gòu)呢?首先,代碼是可以工作的,我們并不能說它有問題,但它又不像我們期望的那樣好。受 Kent Beck 剛出生的女兒的使用的尿布的啟發(fā),Martin Fowler 和 Kent Beck 決定用「味道」這個詞來表示需要重構(gòu)的代碼。
他們在《重構(gòu)》一書中列舉了 22 中常見的味道,如果你看《Clean Code》的話,會發(fā)現(xiàn)還有更多。不過,他們并沒有給出一個具體的標準,而是需要我們的直覺來判斷。
比如多大的類算「過大的類」?多少行代碼算「過長的方法」?這些需要自行判斷,而直覺的形成有兩種方法,一是隨著編碼經(jīng)驗的增多自然形成,另一種更快的方式是大量閱讀優(yōu)秀的開源代碼,提高自己的代碼審美。
《重構(gòu)》一書中的味道可以分為五類:
膨脹劑
OO 使用不合理
難以修改
可有可無
耦合
書中都有詳細的解釋,這里不再贅述。



發(fā)散式變化和散彈式修改是比較容易混淆的兩個味道。前者指一個類的職責過多,有很多因素會引起它的變化,具體的表現(xiàn)就是,不同的需求都會修改同一個文件,導(dǎo)致經(jīng)常沖突,不能順利地并行開發(fā)。
后者指的是改一個需求要修改很多個文件,說明沒有把強內(nèi)聚的代碼歸攏到一起。
大部分的注釋都是沒有必要的,注釋應(yīng)該描述「做了什么」和「為什么做」而不是「怎么做」,方法體內(nèi)的注釋基本都可以通過抽取方法并指定一個有意義的名字來解決。
很多為了應(yīng)對未來需求變化而寫的代碼基本永遠不會被執(zhí)行。
你可能發(fā)現(xiàn)了,有些味道是比較容易識別的,比如重復(fù)代碼,注釋等。
而有些就比較高級,比如特性依戀,中間人等,要識別高級味道,需要理解面向?qū)ο蟮奶匦院驮O(shè)計原則。
采用手法
識別到味道之后,就要知道有什么對應(yīng)的手法可以消除這個味道,執(zhí)行完這個手法之后代碼會變成什么樣子。
在《重構(gòu)》一書中,列舉了 66 個常用手法,可以分為六大類:
重組函數(shù)
搬移特性
組織數(shù)據(jù)
簡化條件
簡化調(diào)用
處理概括
這些手法在書中都有詳細的講解,我就不在這里重復(fù)了。只整理出來,給大家一個宏觀的印象:






運行測試
在采用了手法修改代碼之后,就要執(zhí)行測試以確保真的沒有改變軟件的行為??赡苡袝r會發(fā)現(xiàn),做了重構(gòu)之后測試會失敗,但實現(xiàn)并沒有問題,我們需要修改測試代碼讓它成功。
這就說明測試寫的不合理,給重構(gòu)帶來了負擔,所以我們測試的粒度要把握好,太細的粒度就會增加維護成本。
比如,有些人會給每個私有方法都寫單元測試,那有可能采用「內(nèi)聯(lián)函數(shù)」這個手法之后這個方法就不存在了,就需要修改測試。
這里說起來話就長了,以后再寫一篇如何寫有效的測試的文章吧。重點是重構(gòu)之后,一定要執(zhí)行測試,不管是手工測試或自動化測試。
提交代碼
最后,如果你采用了一個比較復(fù)雜的手法,或者即將采用一個復(fù)雜的手法,最好先提交一下代碼,以保證出現(xiàn)意外后能快速回滾,避免浪費時間。
重構(gòu)要采取「小步快跑」的原則,盡量采用安全的手法,讓測試一直處于通過的狀態(tài)。從低級的壞味道開始,消除低級味道之后,高級味道才會浮現(xiàn)出來。
進階
重構(gòu)與設(shè)計的關(guān)系
在沒有重構(gòu)這個技術(shù)之前,廣泛采用的是 Big Front Design,在開始編碼之前要進行非常詳細的設(shè)計,考慮應(yīng)對未來出現(xiàn)的各種變化。
而有了重構(gòu)技術(shù)之后,前期設(shè)計的壓力就小了,畢竟可以隨時通過重構(gòu)來改善設(shè)計,應(yīng)對變化。
所以你大可不必一上來就應(yīng)用《設(shè)計模式》把代碼搞復(fù)雜,先用簡單的實現(xiàn)滿足當前需求即可。等變化真正來臨時,再通過重構(gòu)技術(shù)調(diào)整設(shè)計,模式給我們提供了一個方向,但并不是最終目標。
還記得簡單設(shè)計的四條原則嗎?通過測試,沒有重復(fù),表達意圖,最少元素。除了這四條原則,還有 SOLID,DRY,KISS 等設(shè)計原則。只要最終的代碼符合好的原則,干凈整潔沒有壞味道,管它符不符合某個模式呢?!
大型遺留系統(tǒng)的重構(gòu)
對于代碼上百萬,千萬行的遺留系統(tǒng),怎么重構(gòu)呢?滿地都是壞味道,一點點去重構(gòu),什么時候是個頭?
這時,選擇哪些代碼來重構(gòu)就非常重要,影響到投資回報。如果對代碼進行分類,將會得出幾種類型:
不會被執(zhí)行的爛代碼
運行穩(wěn)定,基本不會改動的爛代碼
經(jīng)常發(fā)現(xiàn) Bug 的爛代碼
經(jīng)常需要變更的爛代碼
不會被執(zhí)行的代碼,直接刪除就好了。運行穩(wěn)定的又不需要改動的,動它反而可能引入風險,當然,在時間充裕的情況下,還是可以重構(gòu)的。
真正有價值,值得重構(gòu)的,投入產(chǎn)出比最高的,是經(jīng)常出問題和經(jīng)常會有需求變更的爛代碼。優(yōu)化了這部分代碼,可以減少 Bug 和進行需求變更的時間。
總結(jié)
好了。關(guān)于重構(gòu)我想分享的就是這些,我們來回顧一下:
為什么要重構(gòu)?
為了讓軟件始終可以維護,保證開發(fā)效率。
什么是重構(gòu)?
一種以可控的方式整理代碼的技術(shù),在不改變軟件可觀察行為的前提下改善其內(nèi)部結(jié)構(gòu)。
什么時候開始?
事不過三,添加功能,修復(fù) Bug,代碼評審時。
什么時候停止?
重構(gòu)到符合簡單設(shè)計四條原則的 Clean Code。
前提條件
測試保護,版本控制。
重構(gòu)的過程
運行測試,識別味道(常見的 22 種),采用手法(66 個),運行測試,提交代碼。
重構(gòu)與設(shè)計的關(guān)系
有了重構(gòu)技術(shù),我們不用在前期做非常詳細的設(shè)計,做適當?shù)脑O(shè)計,然后通過重構(gòu)讓設(shè)計浮現(xiàn)出來。不用在乎軟件是否符合模式,只要符合原則即可。
大型遺留系統(tǒng)的重構(gòu)
在經(jīng)常需要修改的爛代碼上做重構(gòu)才有最大收益。
作者:Seaborn Lee
https://gitbook.cn/books/591837cbc9b8f67d6a6a94df/index.html
覺得本文對你有幫助?請分享給更多人
關(guān)注「全棧開發(fā)者社區(qū)」加星標,提升全棧技能
本公眾號會不定期給大家發(fā)福利,包括送書、學習資源等,敬請期待吧!
如果感覺推送內(nèi)容不錯,不妨右下角點個在看轉(zhuǎn)發(fā)朋友圈或收藏,感謝支持。
好文章,留言、點贊、在看和分享一條龍吧??
