一携丁、為什么要代碼重構(gòu)(Refactoring)
在不改變系統(tǒng)功能的情況下,改變系統(tǒng)的實(shí)現(xiàn)方式谆甜。為什么要這么做?投入精力不用來滿足客戶關(guān)心的需求集绰,而是僅僅改變了軟件的實(shí)現(xiàn)方式规辱,這是否是在浪費(fèi)客戶的投資呢?
代碼重構(gòu)的重要性要從軟件的生命周期說起栽燕。軟件不同與普通的產(chǎn)品罕袋,他是一種智力產(chǎn)品,沒有具體的物理形態(tài)纫谅。一個(gè)軟件不可能發(fā)生物理損耗炫贤,界面上的按鈕永遠(yuǎn)不會因?yàn)榘磩哟螖?shù)太多而發(fā)生接觸不良。那么為什么一個(gè)軟件制造出來以后付秕,卻不能永遠(yuǎn)使用下去呢兰珍?
對軟件的生命造成威脅的因素只有一個(gè):需求的變更。一個(gè)軟件總是為解決某種特定的需求而產(chǎn)生询吴,時(shí)代在發(fā)展掠河,客戶的業(yè)務(wù)也在發(fā)生變化亮元。有的需求相對穩(wěn)定一些,有的需求變化的比較劇烈唠摹,還有的需求已經(jīng)消失了爆捞,或者轉(zhuǎn)化成了別的需求。在這種情況下勾拉,軟件必須相應(yīng)的改變煮甥。
考慮到成本和時(shí)間等因素,當(dāng)然不是所有的需求變化都要在軟件系統(tǒng)中實(shí)現(xiàn)藕赞。但是總的說來成肘,軟件要適應(yīng)需求的變化,以保持自己的生命力斧蜕。
這就產(chǎn)生了一種糟糕的現(xiàn)象:軟件產(chǎn)品最初制造出來双霍,是經(jīng)過精心的設(shè)計(jì),具有良好架構(gòu)的批销。但是隨著時(shí)間的發(fā)展洒闸、需求的變化,必須不斷的修改原有的功能均芽、追加新的功能丘逸,還免不了有一些缺陷需要修改。為了實(shí)現(xiàn)變更骡技,不可避免的要違反最初的設(shè)計(jì)構(gòu)架鸣个。經(jīng)過一段時(shí)間以后,軟件的架構(gòu)就千瘡百孔了布朦。bug越來越多囤萤,越來越難維護(hù),新的需求越來越難實(shí)現(xiàn)是趴,軟件的構(gòu)架對新的需求漸漸的失去支持能力涛舍,而是成為一種制約。最后新需求的開發(fā)成本會超過開發(fā)一個(gè)新的軟件的成本唆途,這就是這個(gè)軟件系統(tǒng)的生命走到盡頭的時(shí)候富雅。
代碼重構(gòu)就能夠最大限度的避免這樣一種現(xiàn)象。系統(tǒng)發(fā)展到一定階段后肛搬,使用重構(gòu)的方式没佑,不改變系統(tǒng)的外部功能,只對內(nèi)部的結(jié)構(gòu)進(jìn)行重新的整理温赔。通過重構(gòu)蛤奢,不斷的調(diào)整系統(tǒng)的結(jié)構(gòu),使系統(tǒng)對于需求的變更始終具有較強(qiáng)的適應(yīng)能力。
二啤贩、通過代碼重構(gòu)可以達(dá)到以下的目標(biāo)
2.1 持續(xù)偏糾和改進(jìn)軟件設(shè)計(jì)
重構(gòu)和設(shè)計(jì)是相輔相成的待秃,它和設(shè)計(jì)彼此互補(bǔ)。有了重構(gòu)痹屹,你仍然必須做預(yù)先的設(shè)計(jì)章郁,但是不必是最優(yōu)的設(shè)計(jì),只需要一個(gè)合理的解決方案就夠了志衍,如果沒有重構(gòu)暖庄、程序設(shè)計(jì)會逐漸腐敗變質(zhì),愈來愈像斷線的風(fēng)箏足画,脫韁的野馬無法控制雄驹。重構(gòu)其實(shí)就是整理代碼,讓所有帶著發(fā)散傾向的代碼回歸本位淹辞。
2.2 使代碼更易為人所理解
軟件的生命周期往往需要多批程序員來維護(hù),我們往往忽略了這些后來人俘侠。為了使代碼容易被他人理解象缀,需要在實(shí)現(xiàn)軟件功能時(shí)做許多額外的事件,如清晰的排版布局爷速,簡明扼要的注釋央星,其中命名也是一個(gè)重要的方面。一個(gè)很好的辦法就是采用暗喻命名惫东,即以對象實(shí)現(xiàn)的功能的依據(jù)莉给,用形象化或擬人化的手法進(jìn)行命名,一個(gè)很好的態(tài)度就是將每個(gè)代碼元素像新生兒一樣命名廉沮,也許筆者有點(diǎn)命名偏執(zhí)狂的傾向颓遏,如能榮此雅號,將深以此為幸滞时。
對于那些讓人充滿迷茫感甚至誤導(dǎo)性的命名叁幢,需要果決地、大刀闊斧地整容坪稽,永遠(yuǎn)不要手下留情曼玩!
2.3 幫助發(fā)現(xiàn)隱藏的代碼缺陷
重構(gòu)代碼時(shí)逼迫你加深理解原先所寫的代碼。筆者常有寫下程序后窒百,卻發(fā)生對自己的程序邏輯不甚理解的情景酥泛,曾為此驚悚過,后來發(fā)現(xiàn)這種癥狀居然是許多程序員橙鹦牛患的"感冒"逆航。當(dāng)你也發(fā)生這樣的情形時(shí),通過重構(gòu)代碼可以加深對原設(shè)計(jì)的理解,發(fā)現(xiàn)其中的問題和隱患窟她,構(gòu)建出更好的代碼陈症。
2.4 從長遠(yuǎn)來看,有助于提高編程效率
當(dāng)你發(fā)現(xiàn)解決一個(gè)問題變得異常復(fù)雜時(shí)震糖,往往不是問題本身造成的录肯,而是你用錯(cuò)了方法,拙劣的設(shè)計(jì)往往導(dǎo)致臃腫的編碼吊说。
改善設(shè)計(jì)论咏、提高可讀性、減少缺陷都是為了穩(wěn)住陣腳颁井。良好的設(shè)計(jì)是成功的一半厅贪,停下來通過重構(gòu)改進(jìn)設(shè)計(jì),或許會在當(dāng)前減緩速度雅宾,但它帶來的后發(fā)優(yōu)勢卻是不可低估的养涮。
三、哪些情況需要考慮代碼重構(gòu)
3.1 臃腫的類
類之所以會臃腫眉抬,是因?yàn)殚_發(fā)者缺乏對最基本的編碼原則贯吓,即“單一職責(zé)原則”(SRP)的理解。這些類往往會變得很臃腫蜀变,是由于不同的且在功能上缺少關(guān)聯(lián)的方法都放在了相同的類里面悄谐。
3.2 長方法
方法之所以會變得很長主要是有以下幾個(gè)原因:
- 1: 許多沒有關(guān)聯(lián)性的、功能復(fù)雜的模塊的代碼都放在相同的方法內(nèi)库北。這主要是開發(fā)者缺乏SRP的概念
- 2: 多種條件都放在同一個(gè)方法內(nèi)爬舰,這在長方法內(nèi)經(jīng)常會發(fā)生的。這是由于缺乏McCabe代碼復(fù)雜度和SRP的概念的比較寒瓦。
3.3 大量的傳參
我經(jīng)常遇到這幾種情況情屹,一些方法跟另一些方法進(jìn)行交互,或者調(diào)用另一些方法的時(shí)候傳入大量的參數(shù)孵构。這就會出現(xiàn)如果更改了其中一個(gè)參數(shù)屁商,就得在多個(gè)方法內(nèi)進(jìn)行更改。
3.4 常量值無處不在
經(jīng)常會發(fā)現(xiàn)開發(fā)者(尤其是新手)會使用一些具有明確含義的常量值(主要是魔鬼數(shù)字)颈墅,但沒有給它們賦予合適的常量變量蜡镶。這會降低代碼的可讀性和可理解性。
3.5 模糊的方法名
許多時(shí)候恤筛,以下取的方法名會影響代碼的可讀性和可理解性:
- 1: 模糊的不具有任何意義的方法名
- 2: 技術(shù)性的官还,卻沒有提及相關(guān)領(lǐng)域的名稱
四、重構(gòu)的方法
4.1 提取類/抽離方法
正如上面提到的毒坛,像“臃腫的類”(一個(gè)類提供了本該有幾個(gè)類提供的功能)這種代碼異味應(yīng)該將原有類中的方法和屬性移動到適當(dāng)數(shù)目的新類中去望伦。舊類中對應(yīng)新類的方法和屬性應(yīng)該被移除林说。另外,有時(shí)候一些類過于臃腫是因?yàn)樗吮黄渌愂褂帽緫?yīng)該是其他類的成員方法的成員方法屯伞。這些方法也應(yīng)該被遷移到合適的類中腿箩。
4.2 提取方法
像上面提到的“過長的方法”這種代碼異味可以通過從舊方法中提取代碼到一個(gè)或多個(gè)新方法中消除。
4.3 分離條件
許多時(shí)候劣摇,一個(gè)方法很長是因?yàn)榘脦讉€(gè)分支語句(if-else)珠移。這些分支條件可以被提取和移動到幾個(gè)單獨(dú)的方法中。這確實(shí)能大大改善代碼可讀性和可理解性末融。
4.4 引入?yún)?shù)對象/保留全局對象
在我做代碼審查時(shí)發(fā)現(xiàn)另外一個(gè)很常見的情況 - 好幾個(gè)參數(shù)被傳入方法钧惧。問題主要與需要從已有方法中增加或者移除一個(gè)方法參數(shù)有關(guān)。在這種場景勾习,建議將相關(guān)方法參數(shù)組成一個(gè)對象(引入?yún)?shù)對象)浓瞪,讓方法傳遞這些對象而不是每個(gè)單獨(dú)的參數(shù)。
4.5 用符號常量替換魔法數(shù)字
對于有意義的并且到處被使用的字面常量巧婶,應(yīng)該為它們分配一個(gè)命名常量乾颁。這能大大增強(qiáng)代碼可讀性和可理解性。
4.6 重命名方法
正如上面提到的艺栈,模糊不清的方法名會影響代碼的可使用性钮孵。這些模糊不清的名稱應(yīng)該重命名為有意義的可能與業(yè)務(wù)術(shù)語有關(guān)的名稱,來幫助開發(fā)者通過業(yè)務(wù)上下文更好地理解代碼眼滤。這很需要技巧并且要求開發(fā)者與業(yè)務(wù)專家一起協(xié)作來理清代碼需要滿足的業(yè)務(wù)需求。有趣的是历涝,這種重構(gòu)方法看起來似乎非常容易理解诅需,但是常常被許多開發(fā)者忽視,雖然在Eclipse這種IDE的refactor菜單項(xiàng)中經(jīng)常出現(xiàn)這一項(xiàng)荧库。
五堰塌、當(dāng)重構(gòu)沒有現(xiàn)成的明顯的方向時(shí),我們可以遵循下面的原則
1分衫、當(dāng)屬性场刑、方法或類存在任何的需要復(fù)用的意向時(shí),歸納提煉它們蚪战。
2牵现、不要低估小方法對代碼整潔的作用。使用小方法能讓你節(jié)省很多筆墨邀桑。
3瞎疼、用封裝控制可見度。
4壁畸、消除依賴贼急。
5茅茂、簡化構(gòu)造方法——即使這樣做會使代碼變復(fù)雜。
6太抓、不確定時(shí)空闲,將計(jì)算操作移入到這些數(shù)據(jù)的所有者對象里,或?qū)?shù)據(jù)移動到執(zhí)行計(jì)算操作的對象里(也就是迪米特法則(Law of Demeter))走敌。
7碴倾、使用小對象,松耦合悔常,避免大對象影斑,高聚合。
8机打、使用代理對象矫户,模擬對象和輔助對象來隔離網(wǎng)絡(luò),數(shù)據(jù)庫残邀,文件和用戶接口皆辽。
9、不確定時(shí)芥挣,盡量在model里添加代碼驱闷,必要時(shí)才往controler添加代碼。view里添加的都應(yīng)該是便捷功能和簡寫方法空免,但不要局限于此空另。