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