代碼重構(gòu)的藝術(shù)
原文出自:https://juejin.cn/post/6903054491273625614
什么是重構(gòu)
所謂重構(gòu)是這樣一個(gè)過(guò)程:在不改變代碼外在行為的前提下,對(duì)源代碼做出修改,以改進(jìn)程序的內(nèi)部結(jié)構(gòu),從而使代碼變得易于理解,可維護(hù)和可擴(kuò)展。本質(zhì)上來(lái)說(shuō)重構(gòu)就是在代碼寫好之后改進(jìn)它的設(shè)計(jì)。
重構(gòu)的目的是什么
首先,重構(gòu)是時(shí)刻保證代碼質(zhì)量的一個(gè)極其有效的手段,不至于讓代碼腐化到無(wú)可救藥的地步。項(xiàng)目在演進(jìn),代碼不停地在堆砌。如果沒(méi)有人為代碼的質(zhì)量負(fù)責(zé)任,代碼總是會(huì)往越來(lái)越混亂的方向演進(jìn)。當(dāng)混亂到一定程度之后,量變引起質(zhì)變,項(xiàng)目的維護(hù)成本已經(jīng)高過(guò)重新開發(fā)一套新代碼的成本,想要再去重構(gòu),已經(jīng)沒(méi)有人能做到了。
? 劣質(zhì)代碼可能會(huì)影響后續(xù)優(yōu)化的效率,從而進(jìn)一步造成代碼劣化;隨著時(shí)間的推移,這種效應(yīng)將會(huì)導(dǎo)致代碼質(zhì)量大幅下降。破窗效應(yīng) (The Broken Windows Theory) ?
其次,優(yōu)秀的代碼或架構(gòu)不是一開始就能完全設(shè)計(jì)好的,就像優(yōu)秀的公司和產(chǎn)品也都是迭代出來(lái)的。我們無(wú)法100%遇見(jiàn)未來(lái)的需求,也沒(méi)有足夠的精力、時(shí)間、資源為遙遠(yuǎn)的未來(lái)買單,所以,隨著系統(tǒng)的演進(jìn),重構(gòu)代碼也是不可避免的。
何時(shí)需要重構(gòu)
「第一次做某件事時(shí)只管去做;第二次做類似的事會(huì)產(chǎn)生反感,但無(wú)論如何還是可以去做;第三次再做類似的事,你就應(yīng)該重構(gòu).」
添加新功能時(shí)重構(gòu)
「種一棵樹最好的時(shí)間是十年前,其次是現(xiàn)在。」 重構(gòu)的最佳時(shí)機(jī)就是在添加新的功能之前。再動(dòng)手添加新功能之前,我們不妨先考慮一下,如果對(duì)現(xiàn)有的代碼結(jié)構(gòu)做些微調(diào),是否會(huì)使加入新的功能變的容易的多。
? 如果你要給程序添加一個(gè)特性,但發(fā)現(xiàn)代碼因缺乏良好的結(jié)構(gòu)而不易于進(jìn)行更改,那就先重構(gòu)那個(gè)程序,使其比較容易添加該特性,然后再添加該特性 ?
修改問(wèn)題時(shí)重構(gòu)
「掃去窗上的塵埃,才可以看到窗外的美景。」 修改一個(gè)問(wèn)題時(shí),我們需要先理解代碼在做什么,然后才可以著手去修改。這段代碼可能是別人寫的,也可能時(shí)自己寫的,但無(wú)論如何,當(dāng)你覺(jué)得這段代碼邏輯糟糕,需要花費(fèi)幾分鐘才能明白其中的含義時(shí),你就要想著如何去重構(gòu)才可以使代碼變的更加簡(jiǎn)潔直觀
有計(jì)劃的對(duì)代碼重構(gòu)
「找尋重構(gòu)和開發(fā)進(jìn)度中適合自己的平衡點(diǎn)」 但有些時(shí)候我們?cè)跍?zhǔn)備重構(gòu)的時(shí)會(huì)發(fā)現(xiàn),之前的代碼結(jié)構(gòu)和依賴關(guān)系錯(cuò)綜復(fù)雜,這時(shí)我們就要考慮當(dāng)前是否有足夠時(shí)間去很好的處理這些混亂的代碼。盡管重構(gòu)的目的是加快開發(fā)速度,但同時(shí)重構(gòu)也會(huì)拖慢軟件的開發(fā)進(jìn)度。如果時(shí)間充足,那么當(dāng)下就是進(jìn)行重構(gòu)最好的時(shí)機(jī)。當(dāng)魚和熊掌不可兼得的時(shí)候,應(yīng)當(dāng)保證軟件的開發(fā)進(jìn)度不受影響,其次才是進(jìn)行重構(gòu)。可以先把需要重構(gòu)地方記錄下來(lái),整理出一個(gè)計(jì)劃,在未來(lái)的一段時(shí)間內(nèi)解決掉。
Code Review時(shí)重構(gòu)
「處明者不見(jiàn)暗中一物,處暗者能見(jiàn)明中區(qū)事。」 Code Review有助于在開發(fā)團(tuán)隊(duì)中傳播知識(shí),也有助于讓較有經(jīng)驗(yàn)的開發(fā)者把知識(shí)傳遞給比較欠缺經(jīng)驗(yàn)的人。Code Review對(duì)于編寫清晰的代碼也很重要,我寫的代碼也許對(duì)于我自己來(lái)說(shuō)很清晰,但對(duì)于別人來(lái)說(shuō)則不然。Code Review讓更多人有機(jī)會(huì)提出有用的建議來(lái)對(duì)代碼進(jìn)行調(diào)整。三人行,則必有我?guī)煛?/p>
何時(shí)不應(yīng)該重構(gòu)
「有所為,有所不為。」 并非所有的糟糕代碼都需要重構(gòu),如果你不需要使用到這段代碼,那么就不必花心思去重構(gòu)它。只有你需要理解其中的工作原理時(shí),對(duì)其重構(gòu)才有價(jià)值。當(dāng)然如果重寫比重構(gòu)更容易,那么就不需要重構(gòu)了。
如何保證重構(gòu)后程序的正確性
保證代碼正確性最好的方法就是進(jìn)行「單元測(cè)試(Unit Testing)」 。當(dāng)重構(gòu)完成之后,如果新的代碼仍然能通過(guò)單元測(cè)試,那就說(shuō)明代碼原有邏輯的正確性未被破壞,原有的外部可見(jiàn)行為未變。
測(cè)試驅(qū)動(dòng)開發(fā)是非常完美的方案。但實(shí)際上大部分IT公司的程序由于種種原因并沒(méi)有單元測(cè)試。這時(shí)需要一些工具用來(lái)幫助我們快速掃描代碼中的問(wèn)題。比如可以給代碼增加Lint語(yǔ)法檢查,使用SonarQube對(duì)代碼進(jìn)行質(zhì)量和漏洞掃描,前端同學(xué)還可以使用TypeScript等等。把這些代碼自動(dòng)掃描工具集成到CI里面,可以大幅度 減少出現(xiàn)bug的情況。目前我所在部門前端組的一系列產(chǎn)品包括項(xiàng)目,已經(jīng)把這些功能集成在CI里面的,每次的代碼更新,都會(huì)觸發(fā)掃描代碼的流程,CI失敗就無(wú)法將代碼合并到開發(fā)分支上面。
有了上述這些還不夠,在重構(gòu)完成之后,還要把改動(dòng)部分的功能完整的自測(cè)一遍,以保證程序無(wú)誤。當(dāng)自測(cè)通過(guò)之后,就可以請(qǐng)測(cè)試同學(xué)來(lái)幫忙進(jìn)行更加完整的測(cè)試流程。
? 為什么要進(jìn)行這么嚴(yán)格的測(cè)試流程,因?yàn)橐WC程序可靠性。如果一件事有可能出錯(cuò),那么它一定會(huì)出錯(cuò)。?
需要重構(gòu)的Bad Code
糟糕的命名

整潔代碼最重要的一環(huán) 就是好的名字,所以我們要深思熟慮如何給函數(shù)、模塊、變量和類命名,使它們 能清晰地表明自己的功能和用法。
無(wú)意義的注釋

學(xué)會(huì)只編寫夠用的注釋,過(guò)猶不及,應(yīng)當(dāng)重視質(zhì)量而不是數(shù)量
多層的if語(yǔ)句嵌套

if-else在程序設(shè)計(jì)中是不可避免的,作為程序員能做的就是減少嵌套,提升代碼的可閱讀性和質(zhì)量
很酷卻不宜理解代碼

上面這種寫法看起來(lái)是不是很酷,但是過(guò)一段時(shí)間再來(lái)看,你還能一眼看出這部分功能是做什么的嗎?在我剛接觸后端,使用python的時(shí)候?qū)戇^(guò)這樣的代碼,結(jié)果就是在排查問(wèn)題的時(shí)候相當(dāng)頭疼。代碼寫的別人看不懂并不厲害,而是寫的誰(shuí)都看的懂才是厲害。
? 調(diào)試在一開始就比編寫程序困難一倍。因此,按照定義,如果你的代碼寫得非常巧妙,那么你就沒(méi)有足夠的能力來(lái)調(diào)試它。柯林漢定律 (Kernighan's Law) ?
不必要的繼承寫法
繼承雖然是面向?qū)ο蟮乃拇筇匦灾唬褂美^承可以解決代碼復(fù)用的問(wèn)題,但也有其缺點(diǎn): 繼承層次過(guò)深、過(guò)于復(fù)雜會(huì)影響到代碼的可維護(hù)性。如果子類中有方法依賴于父類中的 方法或?qū)傩裕敲串?dāng)父類發(fā)生改變時(shí),子類很可能會(huì)發(fā)生無(wú)法預(yù)知的錯(cuò)誤。
而組合的方式是把類中所有的接口功能單獨(dú)實(shí)現(xiàn),然后使用這些單獨(dú)的對(duì)象組合在一起,完成和目標(biāo)類一致的功能。繼承是用來(lái)表示類之間的 is-a 關(guān)系,而組合是一種 has-a 的關(guān)系。使用組合+接口+委托的方式可以代替大多數(shù)的繼承場(chǎng)景。
重構(gòu)代碼的設(shè)計(jì)原則
開閉原則 (The Open/Closed Principle)
? 實(shí)體應(yīng)開放擴(kuò)展并關(guān)閉修改。?
實(shí)體(可以是類、模塊、函數(shù)等)應(yīng)該能夠使它們的行為易于擴(kuò)展,但是它們的擴(kuò)展行為不應(yīng)該被修改。
里氏替換原則 (The Liskov Substitution Principle)
? 可以在不破壞系統(tǒng)的情況下,用子類型替換類型。?
如果組件依賴于類型,那么它應(yīng)該能夠使用該類型的子類型,而不會(huì)導(dǎo)致系統(tǒng)失敗或者必須知道該子類型的詳細(xì)信息。
依賴反轉(zhuǎn)原則 (The Dependency Inversion Principle)
? 高級(jí)模塊不應(yīng)該依賴于低級(jí)實(shí)現(xiàn)。?
更高級(jí)別的協(xié)調(diào)組件不應(yīng)該知道其依賴項(xiàng)的詳細(xì)信息。
接口隔離原則 (The Interface Segregation Principle)
? 不應(yīng)強(qiáng)制任何客戶端依賴于它不使用的方法。?
組件的消費(fèi)者不應(yīng)該依賴于它實(shí)際上不使用的組件函數(shù)。
單一功能原則 (The Single Responsibility Principle)
? 每個(gè)模塊或者類只應(yīng)該有一項(xiàng)功能。?
模塊或者類只應(yīng)該做一件事。實(shí)際上,這意味著對(duì)程序功能的單個(gè)小更改,應(yīng)該只需要更改一個(gè)組件。例如,更改密碼驗(yàn)證復(fù)雜性的方式應(yīng)該只需要更改程序的一部分。
合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle)
? 量的使用合成和聚合,而不是繼承關(guān)系達(dá)到復(fù)用的目的。?
合成/聚合復(fù)用原則就是指在一個(gè)新的對(duì)象里通過(guò)關(guān)聯(lián)關(guān)系(包括組合關(guān)系和聚合關(guān)系)來(lái)使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新對(duì)象通過(guò)委派調(diào)用已有對(duì)象的方法達(dá)到復(fù)用已有功能的目的。簡(jiǎn)而言之:要盡量使用組合/聚合關(guān)系,少用繼承。
總結(jié)
如果文中有錯(cuò)誤的地方,還望不吝賜教。寫了這么多,其實(shí)是想表達(dá)一個(gè)觀點(diǎn):代碼是寫給人看的,所以要做到良好的編程風(fēng)格,方便其他人閱讀,維護(hù)。 如果你所在的團(tuán)隊(duì)代碼感覺(jué)并不十分優(yōu)雅的話,那就應(yīng)當(dāng)重構(gòu)了。引用前面說(shuō)到的一句話 「種一棵樹最好的時(shí)間是十年前,其次是現(xiàn)在。」
??愛(ài)心三連擊 1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的「點(diǎn)贊,在看」是我創(chuàng)作的動(dòng)力。
2.關(guān)注公眾號(hào)
程序員成長(zhǎng)指北,回復(fù)「1」加入Node進(jìn)階交流群!「在這里有好多 Node 開發(fā)者,會(huì)討論 Node 知識(shí),互相學(xué)習(xí)」!3.也可添加微信【ikoala520】,一起成長(zhǎng)。
“在看轉(zhuǎn)發(fā)”是最大的支持
