|
重構(gòu)最大的敵人不是技巧與能力,而是懶惰,或者說是態(tài)度。許多細(xì)小的重構(gòu)看似無足輕重,例如方法重命名、提取方法。即使重構(gòu)了,似乎對代碼的結(jié)構(gòu)也沒有太大的影響,于是就決定淡然處之,心里想“事情還未到不可挽回的地步,實現(xiàn)功能要緊,至于重構(gòu),還是以后再做吧!”這樣一想,于是就會滋生得過且過的想法,等到代碼開始變得一團(tuán)糟時,重構(gòu)已經(jīng)變得無比困難了。此時需要的重構(gòu)技巧,將百倍難于發(fā)現(xiàn)壞味道之初。
在我參加的前一個項目中,我們定義了一個處理OrderSet的Controller。剛剛開始開發(fā)時,對于OrderSet的操作并不多,主要是Search與Count操作。OrderSet分為WithDetails與WithoutDetail兩種類型。為了實現(xiàn)的簡單,我們將這兩種類型的操作都放在一個Controller中。隨著操作的逐漸增多,這個Controller就變得越來越龐大,逐漸變得臃腫起來。每當(dāng)我需要調(diào)用或者修改Controller時,我都在想:“嗯,這個代碼太糟糕了,什么時候給它重構(gòu)一下。”想是這么想,卻一直扮演著說話的巨人,行動的矮子。即使興起這樣的念頭,又因為其他的工作將此念頭澆滅。直到有一天,這個Controller的代碼已經(jīng)到了忍無可忍的地步,我和我的Pair終于達(dá)成一致意見,決定對此代碼進(jìn)行手術(shù)。我們花費了近一天的時間對Controller以及相關(guān)的Repository進(jìn)行了徹底的重構(gòu)。重構(gòu)前后的代碼天差地別,我終于可以不用像吃了蒼蠅那般看著代碼惡心了。這種重構(gòu)后體驗到的愉悅簡直無與倫比,最關(guān)鍵是結(jié)果令人滿意,重構(gòu)后的代碼變得更可讀,更簡單,也更容易增加新的功能。
如今工作的項目,需要對遺留系統(tǒng)進(jìn)行遷移。首要的任務(wù)是為此系統(tǒng)編寫B(tài)DD測試,以期搭建遷移的測試保護(hù)網(wǎng),并能夠形成可讀與可工作的測試用例文檔。在開始接觸這個任務(wù)時,客戶方的開發(fā)人員已經(jīng)基本搭建好了初步的框架。當(dāng)我們在面對不良代碼時,第一個浮現(xiàn)在腦海中的念頭是“重構(gòu)”,然而考慮到時間因素,隨之又強迫自己滅了這個念頭,因為我們需要考慮項目的進(jìn)度。我們總是在這樣的取舍之中艱難前進(jìn),終于,在系統(tǒng)需要增加一個新消息的測試時,我強烈地感受到重構(gòu)的迫在眉睫。即使代碼有諸多破窗,修補了一扇,總強過聽之任之。經(jīng)過接近一天多的重構(gòu)(當(dāng)然還包括run tests以及build花費的時間),結(jié)果令人滿意。回顧這個過程,我發(fā)現(xiàn)在發(fā)現(xiàn)壞味道時,如果能及時地對代碼進(jìn)行重構(gòu),并保證重構(gòu)的小步前進(jìn),并不會阻礙開發(fā)進(jìn)度,相反還能夠在一定程度改善代碼質(zhì)量,提高代碼的可讀性、可重用性以及可擴展性。所謂“勿以善小而不為”,千萬不要因為小重構(gòu)對代碼質(zhì)量的影響微乎其微而輕視她,或者忽略她,又或者推遲到忍無可忍再想到重構(gòu)。重構(gòu)并非最后的救命稻草,而是隨時保持我們正確前進(jìn)的一把尺子。
說完了重構(gòu)的重要性,讓我再來粗略地介紹這個重構(gòu)過程。
我們的測試程序主要針對Message的發(fā)送、接收與驗證。業(yè)務(wù)的處理則由部署在JBoss上的應(yīng)用來處理。我們需要設(shè)定期望的Message,在發(fā)送請求到遠(yuǎn)程系統(tǒng)后,接收返回的消息,并驗證消息以及數(shù)據(jù)庫是否符合我們的期望。重構(gòu)的起因在于我們需要編寫新的測試覆蓋之前從未測試過的消息,其類型為SO08。如果沿用之前的實現(xiàn),我們就需要在測試步驟中增加MessageType的分支,根據(jù)消息類型對返回的消息進(jìn)行檢查。
檢查的邏輯事實上已經(jīng)被抽象為MessageChecker接口,并為各種類型的消息建立了不同的MessageChecker子類。MessageCheckFactory則這些子類的工廠,負(fù)責(zé)根據(jù)類型創(chuàng)建對應(yīng)的子類對象。這樣的設(shè)計是完全合理的。然而,問題出現(xiàn)在MessageReceiver,它提供了接收消息的方法,通過傳入的消息類型、隊列名稱等參數(shù),返回消息。這個返回值被定義為MessageReader。
MessageReader正是問題的癥結(jié)。我一直強調(diào)的面向?qū)ο笤O(shè)計中一個重要概念就是所謂”對象的自治“,即對象的職責(zé)是自我完備的,它能夠?qū)ψ约簱碛械臄?shù)據(jù)負(fù)責(zé),具備了“智能”處理的行為特征。MessageReader違背了這一原則,它是愚笨的對象,仿佛“坐擁寶山而不知”的笨伯,雖然擁有消息的值,卻不知道該如何處理這些消息。簡而言之,它提供的方法只能對XML格式的消息進(jìn)行讀取,卻不具有真正的業(yè)務(wù)行為。于是在測試步驟中,就產(chǎn)生了這樣的代碼(省略了部分實現(xiàn)代碼):
private void checkPropagationQueueByName(String name, Queue queue, MessageType messageType) { MessageReader reader = messageReceiver.getMessageFor(messageType, identifier, queue); String messageText = reader.toString(); if (messageType == SO05) { messageCheckFactory.checkerFor(messageType, getExpectedSO05ResponseFor(name), messageText); } if (messageType == SO07) { checkSO07Response(name, messageType, messageText) } if (messageType == SO08) { messageCheckFactory.checkerFor(messageType, getExpectedSO08ResponseFor(name), messageText) .checkResponse(); }}
it知識庫:重構(gòu) — 勿以善小而不為,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。