重構(gòu),讓人快樂讓人苦重構(gòu),是編寫代碼必須要面對的一項操作,同時也應(yīng)該是程序員樂于實踐的一項內(nèi)容。不論是邏輯實現(xiàn)還是設(shè)計過程,乃至整個分層結(jié)構(gòu),我們都可能面臨并且實施重構(gòu)。這篇文章不會告訴您什么是重構(gòu),如何去優(yōu)美的重構(gòu)等等的理論,只想和大家分享一些感受,并且探討一些問題。最近的兩周,我一直對我們團隊的一個子業(yè)務(wù)框架做重構(gòu)的工作,很多地方讓我感到很痛苦,于是便有了這篇文章。 牽一發(fā)而動全身的根源在哪里當(dāng)我打開解決方案,查看代碼的時候,我們會發(fā)現(xiàn)很多問題,比如冗余的代碼,性能低下的邏輯實現(xiàn)等等,但是當(dāng)我著手去改造的時候,潛意識告訴我整個似乎不能動,牽扯的面太廣了。更改一個小地方,上下一串都要做相應(yīng)的調(diào)整,這當(dāng)然不是我想要看到的。大范圍的調(diào)整會直接影響系統(tǒng)的穩(wěn)定性,帶來潛在的危險,同時會增加測試團隊的負(fù)擔(dān);在版本控制方面會造成線上和線下版本在同一內(nèi)容的巨大差異,版本更新的時候拿什么來保證一套幾乎全新的代碼替換線上系統(tǒng)是正確的選擇呢?因為很多問題只有在最真實的環(huán)境才能被暴露出來。 這樣的修改,修改成本無疑是巨大的,因為我們期望修改的只是那一小塊代碼而已。大范圍的代碼調(diào)整,同時也伴隨著單元測試代碼的調(diào)整。測試團隊如果因此來重新走測試用例,那么付出的辛苦可想而知。 我要做的是重構(gòu)而不是重寫,造成這種現(xiàn)象的原因在哪里呢? 整個解決方案具有相對完整的分層結(jié)構(gòu),DAO層、實體層、業(yè)務(wù)邏輯層。實體層也對數(shù)據(jù)實體和業(yè)務(wù)實體做了分別定義。但是進行業(yè)務(wù)實現(xiàn)的時候我們并沒有進行有效的隔離和代碼的職責(zé)劃分。 很多代碼在處理業(yè)務(wù)邏輯的時候直接調(diào)用DAO,然后使用返回的數(shù)據(jù)組織業(yè)務(wù)實體。當(dāng)我們的業(yè)務(wù)實體需要按照領(lǐng)域劃分為兩個或者更多的層次的時候,結(jié)果會變得更為糟糕,因為我們需要以底層的業(yè)務(wù)實體為輸入從而輸出上層的實體。當(dāng)你以一個順序工作流的方式完成一整套操作的時候,也許感覺很有成就感,整個過程天衣無縫,完美無缺。但是當(dāng)我們嘗試改變其中的某些內(nèi)容的話,噩夢就開始了,實體的改變勢必會引起邏輯的改變,但是這種改變是有連鎖反應(yīng)的。 業(yè)務(wù)實現(xiàn)的過程很多時候就是不同層次間的實體的轉(zhuǎn)化過程,那么實現(xiàn)過程中單單考慮解除依賴不能收到很好的效果,從業(yè)務(wù)邏輯的職責(zé)出發(fā),劃分出清晰的業(yè)務(wù)層次,再以實體轉(zhuǎn)變的結(jié)合點來考慮分解才能達到良好的效果。 獨立的領(lǐng)域?qū)佑葹橹匾?/h4>各種經(jīng)典的MVC架構(gòu)的實現(xiàn),常常讓人產(chǎn)生誤解,認(rèn)為那樣做就已經(jīng)完美了。事實上,一個業(yè)務(wù)的框架的重點不是增、刪、改、查,我更傾向于將DAO從業(yè)務(wù)框架中分離出去(最后我也是這樣做的),整個系統(tǒng)應(yīng)該提供統(tǒng)一的DAO服務(wù),子業(yè)務(wù)框架要專注于業(yè)務(wù)的實現(xiàn)。 當(dāng)我們嘗試將一整套業(yè)務(wù)實體獨立出來的時候,我們認(rèn)為已經(jīng)做了很好的業(yè)務(wù)理解,但是這是只見樹木不見森林的想法。在某些系統(tǒng)中,領(lǐng)域的實現(xiàn)只占代碼總量的很少一個比例,但是其重要性往往卻是相反的一個比例。當(dāng)我們選擇將領(lǐng)域代碼和其他代碼混合在一起的時候,意味著我們的分層結(jié)構(gòu)也隨之混亂。 "用標(biāo)準(zhǔn)的架構(gòu)模式來完成與上層的松散關(guān)聯(lián)。將所有與領(lǐng)域相關(guān)的代碼都集中在一層,并且將它與用戶界面層、應(yīng)用層和基礎(chǔ)結(jié)構(gòu)層的代碼分離。領(lǐng)域?qū)ο罂梢詫⒅攸c放在表達領(lǐng)域模型上,不需要關(guān)系它們自己的顯示、存儲和管理應(yīng)用任務(wù)等。這樣使模型發(fā)展得足夠豐富和清晰"。在清楚了整個領(lǐng)域模型之后,再考慮選擇合適的模式來解決分層問題,我覺得是合理的做法。 在對業(yè)務(wù)和領(lǐng)域沒有充分理解的時候不要下手在重構(gòu)過程中,發(fā)現(xiàn)很多業(yè)務(wù)實體的定義不著邊際,很多概念只是對數(shù)據(jù)實體的拓展,結(jié)果出來的東西和數(shù)據(jù)實體的邏輯關(guān)系截然不同。對數(shù)據(jù)調(diào)取的邏輯沒有充分理解,那么組織業(yè)務(wù)實體的時候很容易出現(xiàn)不恰當(dāng)?shù)臄?shù)據(jù)訪問方式,比如循環(huán)訪問數(shù)據(jù)庫。 如果整個領(lǐng)域模型的建立和劃分都是錯誤的話,我們?nèi)匀荒軐崿F(xiàn)所需要的功能,但是如果對這樣的代碼進行重構(gòu)無疑等于推倒重來。 現(xiàn)在看來,當(dāng)我們?yōu)榱藢崿F(xiàn)功能而急匆匆的不擇手段的時候,為將來的維護和升級埋下了隱患。當(dāng)我們想要將公用的數(shù)據(jù)調(diào)取和業(yè)務(wù)邏輯實現(xiàn)從各個子項目中抽象出來變成基類、接口、Helper或者Service的時候,我發(fā)現(xiàn)不同子項目的開發(fā)者對業(yè)務(wù)和領(lǐng)域的理解有著很大的差異,因而在實現(xiàn)方式和實體定義上都有很大的不同。這個時候我們又注意到組織實體的邏輯并沒有單獨的分離出來,抽取的工作遇到了難題。也許我們可以分離出代理,然后使用Adapter來適應(yīng)原有的實體組織邏輯,又或許我們應(yīng)該廢除不合理的實體定義,也意味著廢除了相應(yīng)的實現(xiàn)邏輯。 如果我選擇將不合理的實體替換為正確的實體定義將面臨巨大的挑戰(zhàn),也許從數(shù)據(jù)調(diào)取到最終的邏輯都要調(diào)整。當(dāng)然全面調(diào)整的原因是我們沒有實現(xiàn)很好的隔離。 迷茫,面對一個幾千行的Method這是我無法容忍的情況,整個子業(yè)務(wù)的實現(xiàn)幾個大方法全搞定了,每個方法里無數(shù)個"Region"和"End Region"。這樣的情況就別談什么分離和設(shè)計了。最起碼的,當(dāng)你在用"Region"和"End Region"的時候,就應(yīng)該意識到這里可以分離出一個方法來。 大方法帶來很多弊端。它的可維護性差,可閱讀性差,和系統(tǒng)的分層結(jié)構(gòu)不融合,不能進行有效的單元測試。當(dāng)然對大方法的重構(gòu)并不像代碼本身那么發(fā)雜,如果它的邏輯足夠清晰的話。但是如果一個思維足夠清晰的程序員又怎么會寫出這樣的代碼,所以對這樣的代碼進行重構(gòu),面臨一個很大的問題就是那些在不同邏輯里重用的局部變量。當(dāng)然更重要的問題是理不清頭緒。 光去抱怨是沒什么意義的,這樣的方法出現(xiàn)的原因是什么呢?一個是開發(fā)者沒有很好的理解業(yè)務(wù)框架的結(jié)構(gòu)和目的,二是對程序設(shè)計的基本思想理解的不夠好,三是對業(yè)務(wù)邏輯本身理解的不夠清晰。對于這樣的實現(xiàn)去重構(gòu),除了從業(yè)務(wù)角度出發(fā),抽絲剝繭,慢慢的剝離,還有什么好辦法呢?推倒重來嗎? 重構(gòu),要隨時進行當(dāng)有一份代碼覺得不合適,而沒有及時重構(gòu)的話,那么整個解決方案就可能變成垃圾場。后續(xù)的開發(fā)人員會以存在即合理的想法來看待這些垃圾代碼。尤其是新加入的成員,只能模仿別人在怎么做。從測試驅(qū)動的開發(fā)理念看,程序開發(fā)是一個不斷重構(gòu)的迭代過程。很多人將重構(gòu)看成是一件大事,一聽到重構(gòu)就害怕起來,尤其是測試團隊。當(dāng)然這里不能否認(rèn),不恰當(dāng)?shù)闹貥?gòu)會給測試團隊造成很大的麻煩。 集中重構(gòu)是極其錯誤的思想。不要想著等某些開發(fā)任務(wù)結(jié)束了,有時間了再集中精力來重構(gòu)代碼。當(dāng)系統(tǒng)相對穩(wěn)定之后,重構(gòu)要付出的代價可能是整個團隊無法接受的。對分層架構(gòu)的重構(gòu)應(yīng)該是建立架構(gòu)的最初一段時間,不斷的調(diào)整達到最優(yōu)。當(dāng)項目進行一段之后,再來調(diào)整整體結(jié)構(gòu)無疑是讓人無法接受的。 重構(gòu)要避免過度設(shè)計最后要說的是,重構(gòu)要圍繞一個適度的目標(biāo)來進行,要考慮代價,同時不代表模式應(yīng)用的越多越好。相反的,在重構(gòu)過程中,要時時考慮是否把簡答的事情想復(fù)雜了。 目前我重構(gòu)的代碼中,還沒有這樣的問題,這里也就不啰嗦了。 說了這么多,我還是想聽聽各位的看法和感受,如何進行有效的重構(gòu),如何在編程的最開始的階段就避免很多重構(gòu)障礙的產(chǎn)生? |
|