用實(shí)例告訴你如何重構(gòu)帶有壞味道的代碼
如果出現(xiàn)了代碼壞味道,說(shuō)明你的代碼寫(xiě)得不夠好,需要重構(gòu)才能讓它們變成干凈的代碼。在這篇文章中,我將通過(guò) GitHub 上的真實(shí)項(xiàng)目來(lái)解釋代碼壞味道,并向你展示如何重構(gòu)這些帶有壞味道的代碼。
重復(fù)代碼和重復(fù)邏輯
開(kāi)發(fā)人員通常很懶惰,在某種程度上,這不算一件壞事。然而,因?yàn)閼卸瓒呱狭藦?fù)制黏貼代碼的不歸路那就不對(duì)了。這樣可能會(huì)導(dǎo)致最常見(jiàn)的代碼壞味道,即邏輯重復(fù),如下所示。
為了擺脫這種代碼壞味道,我們需要將紅色部分提取到一個(gè)單獨(dú)的方法中,這樣就可以在其他地方重用它們。
長(zhǎng)方法和臃腫的類(lèi)
我們都會(huì)犯這樣的一個(gè)錯(cuò)誤:在現(xiàn)有方法中添加 if() 或 for() 語(yǔ)句來(lái)驗(yàn)證用戶(hù)輸入或檢查用戶(hù)是否已登錄。我們其實(shí)不應(yīng)該這樣做。如果一定要做這些驗(yàn)證,應(yīng)該創(chuàng)建自己的方法。方法長(zhǎng)度應(yīng)該在 4 到 20 行之間,如果超過(guò) 20 行,可以將其中的幾行提取到另一個(gè)方法中。同樣的規(guī)則也適用于類(lèi),根據(jù)單一責(zé)任原則,方法或類(lèi)越小越好。
相同或不同類(lèi)中的重復(fù)方法
另一個(gè)代碼壞味道是多個(gè)方法提供了相同的功能,如下圖所示。
分散式變更(Divergent Change)
如果你了解 SOLID 原則,特別是單一職責(zé)原則,那么你就應(yīng)該知道,修改一個(gè)類(lèi)的理由應(yīng)該是單一的。也就是說(shuō),User 類(lèi)不應(yīng)具有與產(chǎn)品或文件轉(zhuǎn)換相關(guān)的功能。你可以通過(guò)將不相關(guān)的方法提取到 Product 類(lèi)或 FileSystem 類(lèi)來(lái)清除這個(gè)代碼壞味道。
散彈式變更(Shotgun Surgery)
這與發(fā)散變更完全相反。這種代碼壞味道會(huì)讓你因?yàn)橐粋€(gè)需求而去修改多個(gè)類(lèi)。例如,你想要?jiǎng)?chuàng)建一個(gè)新的用戶(hù)規(guī)則(如“Supper-Admin”),然后你發(fā)現(xiàn),為了增加這個(gè)規(guī)則還需要修改 Profile、Products 和 Employees 類(lèi)中的某些方法。在這種情況下,可以考慮將這些方法放在一個(gè)單獨(dú)的類(lèi)中。
依戀情結(jié)(Feature Envy)
有時(shí)候,你會(huì)在類(lèi)中找到一個(gè)大量使用另一個(gè)類(lèi)的方法。在這種情況下,你可以考慮將這個(gè)方法移動(dòng)到它使用的那個(gè)類(lèi)中。如下圖所示。將 getFullAddress() 從 User 類(lèi)移動(dòng)到 ContactInfo 類(lèi)中豈不是更好?因?yàn)樗{(diào)用了 ContactInfo 類(lèi)的很多方法。
數(shù)據(jù)泥團(tuán)(Data Clumps)
有時(shí)候,你會(huì)發(fā)現(xiàn)很多函數(shù)具有相同的參數(shù)列表,這樣會(huì)導(dǎo)致數(shù)泥團(tuán)代碼壞味道。看看下面的例子,你會(huì)發(fā)現(xiàn),幾乎所有類(lèi)型的預(yù)訂都需要護(hù)照信息。
在這種情況下,將護(hù)照信息移到 PassportInfo 類(lèi)中,然后將 PassportInfo 對(duì)象傳給預(yù)訂方法,這樣會(huì)更好。這是一個(gè)很好的重用代碼的例子。請(qǐng)記住,參數(shù)列表太長(zhǎng)可能更容易導(dǎo)致 bug 和代碼沖突,而且難以進(jìn)行單元測(cè)試。
癡迷基本類(lèi)型(Primitive Obsession)
當(dāng)你在應(yīng)用程序的所有地方都使用基本數(shù)據(jù)類(lèi)型時(shí),就會(huì)出現(xiàn)這種代碼壞味道。例如,使用整數(shù)表示電話(huà)號(hào)碼,使用字符串表示貨幣符號(hào)。如果你是這么做的,那么請(qǐng)先看一下下面這個(gè)類(lèi)。
代碼中的地址被定義為數(shù)組,這樣會(huì)導(dǎo)致兩個(gè)問(wèn)題,例如,每次我們需要使用地址時(shí)都要對(duì)其進(jìn)行硬編碼。那么,為什么不創(chuàng)建一個(gè) Address 類(lèi)呢?
現(xiàn)在,每次我們需要添加或編輯地址時(shí),只需要修改 Address 類(lèi)。此外,如果我們需要添加一個(gè)新的“聯(lián)系我們”方法,就增加一個(gè)新的 ContactUs 類(lèi)。這樣,每個(gè)類(lèi)都有自己的單一職責(zé)。
switch 語(yǔ)句
或許你很想知道為什么使用 switch 語(yǔ)句其實(shí)是件很糟糕的事情。雖說(shuō)使用 switch 語(yǔ)句并不一定總是不好的,但在下面的示例中,你可以看到,switch 語(yǔ)句的代碼塊不僅很大而且是不可提取的。當(dāng)代碼塊變得越來(lái)越大時(shí),你將無(wú)法將其拆分成更小的方法。
如果你的 switch 語(yǔ)句代碼塊不是很大,那么你可以繼續(xù)使用它們。例如,工廠模式就使用了 switch 語(yǔ)句。
并行繼承
有時(shí)候我會(huì)想,并行繼承是不是一種不好的做法。先讓我們來(lái)解釋一下并行繼承的概念,然后再討論它是不是代碼壞味道。
從上圖中可以看出,每當(dāng)我們創(chuàng)建一個(gè)新的部門(mén)類(lèi)時(shí),我們還需要?jiǎng)?chuàng)建一個(gè)權(quán)限類(lèi),這將導(dǎo)致之前提到的散彈式變更代碼壞味道。
懶惰類(lèi)
懶惰類(lèi)是指只做很少事情的類(lèi)。還記得這些下面這些代碼嗎?
我們將地址移到一個(gè)單獨(dú)的類(lèi)中,但我們沒(méi)有對(duì)熱線(xiàn)也這么做,因?yàn)樗赡苤挥?3 行代碼。所以,一旦你發(fā)現(xiàn)了這些懶惰類(lèi),應(yīng)該將它們移除。
臨時(shí)字段
當(dāng)一個(gè)類(lèi)實(shí)例的某些變量只是偶爾用到,就出出現(xiàn)臨時(shí)字段代碼壞味道。請(qǐng)看下面的例子,你會(huì)注意到 $name 和 $contactDetails 只在 notify() 方法中用到。
那么為什么不將它們作為方法參數(shù)進(jìn)行傳遞呢。
消息鏈
當(dāng)一個(gè)類(lèi)使用了另一個(gè)類(lèi),而那個(gè)類(lèi)又使用了另外一個(gè)類(lèi),并以此類(lèi)推,那么就會(huì)出現(xiàn)消息鏈代碼壞味道。在下圖中,你可以看到 Employee->EmployeeConfig->Config。
你可以通過(guò)縮短鏈(變成 Employee->Config)讓代碼變得更整潔。
不恰當(dāng)?shù)挠H密關(guān)系
有時(shí)候,一個(gè)類(lèi)的某個(gè)方法需要過(guò)多地了解另一個(gè)類(lèi)的內(nèi)部狀態(tài)或數(shù)據(jù)。正如你在下圖中所看到的,notify() 方法位于 User Class 中,但它卻使用了 UserContactDetails 類(lèi)的很多內(nèi)部方法。
在這種情況下,***可以將這些邏輯從 User 類(lèi)移動(dòng)到 UserContactDetails 類(lèi)中,并新增 getWelcomeMessage($userName) 方法。
中間人
有時(shí)候,你會(huì)發(fā)現(xiàn)一個(gè)類(lèi)的很多方法什么事都不做,只是將調(diào)用委托給另一個(gè)類(lèi)。在這種情況下,這個(gè)類(lèi)被認(rèn)為是中間人,大多數(shù)時(shí)候可以避免使用它。
注意:在 Facade 設(shè)計(jì)模式中,中間人在某些情況下可能會(huì)有所幫助。
接口不同但目的相同的類(lèi)
通常,因?yàn)閳F(tuán)隊(duì)之間缺乏溝通,創(chuàng)建了兩個(gè)不同的類(lèi),但它們的作用卻是一樣的,這意味著出現(xiàn)了重復(fù)代碼。
不完整的庫(kù)
第三方庫(kù)并不總能為你提供應(yīng)用程序中所需的所有功能。在下面的示例中,處理文檔的庫(kù)可以通過(guò) ID 獲取一個(gè)文檔或一次獲取所有的文檔。
如果你需要獲取特定用戶(hù)的所有文檔該怎么辦?在這種情況下,你需要擴(kuò)展 Document 類(lèi)的功能,而不直接修改原始類(lèi)。這個(gè)時(shí)候可以使用裝飾模式,如下圖所示。
現(xiàn)在,你可以使用 DocumentsDecorator 類(lèi)而不是 Documents 類(lèi)。
注釋
你可能會(huì)感到驚訝,但錯(cuò)誤地使用注釋也是一種代碼壞味道。下面是我的一些建議:
- 刪除不必要的注釋。
- 如果代碼很容易理解,請(qǐng)不要添加額外的注釋。
- 不要留下被注釋的舊代碼。
- 刪除用于調(diào)試的注釋?zhuān)?var_dump、echo 等。
夸夸其談未來(lái)性(Speculative Generality)
這個(gè)代碼壞味道與過(guò)早進(jìn)行優(yōu)化有關(guān),很多開(kāi)發(fā)人員都沒(méi)有注意到這一點(diǎn)。在規(guī)劃期間需要考慮的一些注意事項(xiàng):
- 不要過(guò)度計(jì)劃你的代碼。
- 不要試圖涵蓋只有 1%可能性會(huì)在未來(lái)發(fā)生的情況。
- 為了讓算法更簡(jiǎn)單,可以犧牲一些速度,特別是當(dāng)你不需要應(yīng)用程序立即給出結(jié)果的時(shí)候。
- 當(dāng)應(yīng)用程序速度很慢,即使只有 100 個(gè)用戶(hù),也需要進(jìn)行優(yōu)化。
英文原文:https://codeburst.io/write-clean-code-and-get-rid-of-code-smells-aea271f30318
【責(zé)任編輯:龐桂玉 TEL:(010)68476606】