中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

整潔代碼之道——重構(gòu)

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

寫在前面

現(xiàn)在的軟件系統(tǒng)開發(fā)難度主要在于其復(fù)雜度和規(guī)模,客戶需求也不再像Winston Royce瀑布模型期望那樣在系統(tǒng)編碼前完成所有的設(shè)計(jì)滿足用戶軟件需求。在這個信息爆炸技術(shù)日新月異的時代,需求總是在不斷的變化,隨之在2001年業(yè)界17位大牛聚集在美國猶他州的滑雪勝地雪鳥(Snowbird)雪場,提出了“Agile”(敏捷)軟件開發(fā)價值觀,并在他們的努力推動下,開始在業(yè)界流行起來。在《代碼整潔之道》一書中提出:一種軟件質(zhì)量,可持續(xù)開發(fā)不僅在于項(xiàng)目架構(gòu)設(shè)計(jì),還與代碼質(zhì)量密切相關(guān),代碼的整潔度和質(zhì)量成正比,一份整潔的代碼在質(zhì)量上是可靠的,為團(tuán)隊(duì)開發(fā),后期維護(hù),重構(gòu)奠定了良好的基礎(chǔ)。

接下來筆者將結(jié)合自己之前的重構(gòu)實(shí)踐經(jīng)驗(yàn),來探討平時實(shí)際開發(fā)過程中我們注重代碼優(yōu)化實(shí)踐細(xì)節(jié)之道,而不是站在純空洞的理論來談?wù)摯a整潔之道。

在具體探討如何進(jìn)行代碼優(yōu)化之前,我們首先需要去探討和明確下何謂是“代碼的壞味道”,何謂是“整潔優(yōu)秀代碼”。因?yàn)橐磺袃?yōu)化的根源都是來自于我們平時開發(fā)過程中而且是開發(fā)人員自己產(chǎn)生的“代碼壞味道”。

代碼的壞味道

“如果尿布臭了,就換掉它!保Z出Beck奶奶,論撫養(yǎng)小孩的哲學(xué)。同樣,代碼如果有壞味道了,那么我們就需要去重構(gòu)它使其成為優(yōu)秀的整潔代碼。

談?wù)摰胶沃^代碼的壞味道,重復(fù)代碼(Duplicated Code)首當(dāng)其沖。重復(fù)在軟件系統(tǒng)是萬惡的,我們熟悉的分離關(guān)注點(diǎn),面向?qū)ο笤O(shè)計(jì)原則等都是為了減少重復(fù)提高重用,Don’t repeat yourself(DRY)。關(guān)于DRY原則,我們在平時開發(fā)過程中必須要嚴(yán)格遵守。

其次還有其他壞味道:過長函數(shù)(Long Method)、過大的類(Large Class)、過長參數(shù)列表(Long Parameter List)、冗余類(Lazy Class)、冗余函數(shù)(Lazy Function)無用函數(shù)參數(shù)(Unused Function Parameter)、函數(shù)圈復(fù)雜度超過10(The Complexity is over 10)、依戀情結(jié)(Feature Envy)、Switch過多使用(Switch Abuse)、過度擴(kuò)展設(shè)計(jì)(Over-extend design)、不可讀或者可讀性差的變量名和函數(shù)名(unread variable or function name)、異曲同工類(Alternative Classes with Different Interfaces)、過度耦合的消息鏈(Message Chains)、令人迷惑的臨時字段(Temporary Field)、過多注釋(Too Many Comments)等壞味道。

整潔代碼

什么是整潔代碼?不同的人會站在不同的角度闡述不同的說法。而我最喜歡的是Grady Booch(《面向?qū)ο蠓治雠c設(shè)計(jì)》作者)闡述:

“整潔的代碼簡單直接。整潔的代碼如同優(yōu)美的散文。整潔的代碼從不隱藏設(shè)計(jì)者的意圖,充滿了干凈利落的抽象和直截了當(dāng)?shù)目刂普Z句!

整潔的代碼就是一種簡約(簡單而不過于太簡單)的設(shè)計(jì),閱讀代碼的人能很清晰的明白這里在干什么,而不是隱澀難懂,整潔的代碼讀起來讓人感覺到就像閱讀散文-藝術(shù)的沉淀,作者是精心在意締造出來。

整潔代碼是相對于代碼壞味道的,如何將壞味道代碼優(yōu)化成整潔代碼,正是筆者本文所探討的重點(diǎn)內(nèi)容:整潔代碼之道-重構(gòu),接下來筆者將從幾個角度重點(diǎn)描述如何對軟件進(jìn)行有效有技巧的重構(gòu)。

重構(gòu) — Why

在軟件開發(fā)過程中往往開發(fā)者不經(jīng)意間就能產(chǎn)生代碼的壞味道,特別是團(tuán)隊(duì)人員水平參差不齊每個人的經(jīng)驗(yàn)和技術(shù)能力不同的情況下更容易產(chǎn)生不同階段的代碼壞味道。并且隨著需求的迭代和時間推移,代碼的壞味道越來越嚴(yán)重,甚至影響到團(tuán)隊(duì)的開發(fā)效率,那么遇到這個問題該如何去解決。

在軟件開發(fā)Coding之前我們不可能事先了解所有的需求,軟件設(shè)計(jì)肯定會有考慮不周到不全面的地方,而且隨著項(xiàng)目需求的Change,很有可能原來的代碼設(shè)計(jì)結(jié)構(gòu)已經(jīng)不能滿足當(dāng)前需求。

更何況,我們很少有機(jī)會從頭到尾參與并且最終完成一個項(xiàng)目,基本上都是接手別人的代碼,即使這個項(xiàng)目是從頭參與的,也有可能接手團(tuán)隊(duì)其他成員的代碼。我們都有過這樣的類似的抱怨經(jīng)歷,看到別人的代碼時感覺就像垃圾一樣特別差勁,有一種強(qiáng)烈的完全想重寫的沖動,但一定要壓制住這種沖動,你完全重寫,可能比原來的好一點(diǎn),但浪費(fèi)時間不說,還有可能引入原來不存在的Bug,而且,你不一定比原來設(shè)計(jì)得好,也許原來的設(shè)計(jì)考慮到了一些你沒考慮到的分支或者異常情況。

我們寫的代碼,終有一天也會被別人接手,很可能到時別人會有和我們現(xiàn)在一樣的沖動,所以開發(fā)者在看別人代碼時候,要懷著一顆學(xué)習(xí)和敬畏之心,去發(fā)現(xiàn)別人的代碼之美,在這個過程中挑出寫的比較好的優(yōu)秀代碼,吸取精華,去其糟粕,在這個基礎(chǔ)上,我們再去談重構(gòu),那么你的重構(gòu)會是一個好的開端。

總之,我們要做的是重構(gòu)不是重寫,要先從小范圍的局部重構(gòu)開始,然后逐步擴(kuò)展到整個模塊。

重構(gòu) — 作用

重構(gòu),絕對是軟件開發(fā)寫程序過程中最重要的事之一。那么什么是重構(gòu),如何解釋重構(gòu)。名詞:對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可理解性,降低其修改成本。 動詞 :使用一系列重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。

重構(gòu)不只可以改善既有的設(shè)計(jì)結(jié)構(gòu),還可以幫助我們理解原來很難理解的流程。比如一個復(fù)雜的條件表達(dá)式,我們可能需要很久才能看明白這個表達(dá)式的作用,還可能看了好久終于看明白了,過了沒多長時間又忘了,現(xiàn)在還要從頭看,如果我們把這個表達(dá)式運(yùn)用Extract Method抽象出來,并起一個易于理解的名字,如果函數(shù)名字起得好,下次當(dāng)我們再看到這段代碼時,不用看邏輯我們就知道這個函數(shù)是做什么的。

如果對這個函數(shù)內(nèi)所有難于理解的地方我們做了適當(dāng)?shù)闹貥?gòu),把每個細(xì)小的邏輯抽象成一個小函數(shù)并起一個容易理解的名字,當(dāng)我們看代碼時就有可能像看注釋一樣,不用再像以前一樣通過看代碼的實(shí)現(xiàn)來猜測這段代碼到底是做什么的,我一直堅(jiān)持和秉持這個觀點(diǎn):好的代碼勝過注釋,畢竟注釋還是有可能更新不及時的,不及時最新的注釋容易更其他人帶來更多的理解上的困惑。

此外重構(gòu)可以使我們增加對代碼和業(yè)務(wù)邏輯功能的理解,從而幫助我們找到Bug;重構(gòu)可以幫助我們提高編程速度,即重構(gòu)改善了程序結(jié)構(gòu)設(shè)計(jì),并且因?yàn)橹貥?gòu)的可擴(kuò)展性使添加新功能變得更快更容易。

重構(gòu) — 時機(jī)

理解了重構(gòu)的意義和作用,那么我們何時開始重構(gòu)呢?筆者一直堅(jiān)持這種觀點(diǎn):重構(gòu)是一個持續(xù)的系統(tǒng)性的工程,它是貫穿于整個軟件開發(fā)過程中,我們無需專門的挑出時間進(jìn)行重構(gòu),重構(gòu)應(yīng)該隨時隨地的進(jìn)行,即遵循三次法則:事不過三,三則重構(gòu)。這個準(zhǔn)則表達(dá)的意思是:第一次去實(shí)現(xiàn)一個功能盡管去做,但是第二次做類似的功能設(shè)計(jì)時會產(chǎn)生反感,但是還是會去做,第三次還是實(shí)現(xiàn)類似的功能做同樣的事情,那你就應(yīng)該去重構(gòu)。三次準(zhǔn)則比較抽象,那么對應(yīng)到我們具體的軟件開發(fā)流程中,一般可以在這三個時機(jī)去進(jìn)行:

(1) 當(dāng)添加新功能時如果不是特別容易,可以通過重構(gòu)使添加特性和新功能變得更容易。在添加新功能的時候,我們就先清理這個功能所需要的代碼;ㄒ稽c(diǎn)時間,用滴水穿石的方法逐漸清理代碼,隨著時間的推移,我們的代碼就會越來越干凈,開發(fā)速度也會越來越快。

(2) 修改Bug的時候去重構(gòu),比如你在查找定位Bug的過程中,發(fā)現(xiàn)以前自己的代碼或者別人的代碼因?yàn)樵O(shè)計(jì)缺陷比如可擴(kuò)展性、健壯性比較差造成的,那么此時就是一個比較好的重構(gòu)時機(jī)。可能這個時候很多同學(xué)就有疑問了,認(rèn)為我開發(fā)要趕進(jìn)度,沒有時間去重構(gòu),或者認(rèn)為我打過補(bǔ)丁把Bug解決不就行了,不需要去重構(gòu)。根據(jù)筆者之前多年的經(jīng)驗(yàn)得出的結(jié)論:遇到即要解決即那就是每遇到一個問題,就馬上解決它,而不是選擇繞過它。完善當(dāng)前正在使用的代碼,那些還沒有遇到的問題,就先不要理它。在當(dāng)前前進(jìn)的道路上,清除所有障礙,以后你肯定還會再一次走這條路,下次來到這里的時候你會發(fā)現(xiàn)路上不再有障礙。

軟件開發(fā)就是這樣;蛟S解決這個問題需要你多花一點(diǎn)時間。但是從長遠(yuǎn)來看,它會幫你節(jié)省下更多的時間。也就是重構(gòu)是個循序漸進(jìn)的過程,經(jīng)過一段時間之后,你會發(fā)現(xiàn)之前所有的技術(shù)債務(wù)會逐步都不見了,所有的坑相繼都被填平了。這種循序漸進(jìn)的代碼重構(gòu)的好處開始顯現(xiàn),編程的速度明顯會加快。

(3)Code Review時去重構(gòu),很多公司研發(fā)團(tuán)隊(duì)都會有定期的Code Review,這種活動的好處多多,比如有助于在開發(fā)團(tuán)隊(duì)中傳播知識進(jìn)行技術(shù)分享,有助于讓較有經(jīng)驗(yàn)的開發(fā)者把知識傳遞給欠缺經(jīng)驗(yàn)的人,并幫助更多的人對軟件的其他業(yè)務(wù)模塊更加熟悉從而實(shí)現(xiàn)跨模塊的迭代開發(fā)。Code Review可以讓更多的人有機(jī)會對自己提出更多優(yōu)秀好的建議。同時重構(gòu)可以幫助審查別人的代碼,因?yàn)樵谥貥?gòu)前,你需要先閱讀代碼得到一定程度的理解和熟悉,從而提出一些建議和好的idea,并考慮是否可以通過重構(gòu)快速實(shí)現(xiàn)自己的好想法,最終通過重構(gòu)實(shí)踐你會得到更多的成就感滿足感。為了使審查代碼的工作變得高效有作用,據(jù)我以前的經(jīng)驗(yàn),我建議一個審查者和一個原作者進(jìn)行合作,審查者提出修改建議,然后兩人共同判斷這些修改是否能夠通過重構(gòu)輕松實(shí)現(xiàn),如果修改成本比較低,就在Review的過程中一起著手修改。

如果是比較大型比較復(fù)雜的設(shè)計(jì)復(fù)查審核工作,建議原作者使用UML類序列圖、時間序列圖、流程圖去向?qū)彶檎哒宫F(xiàn)設(shè)計(jì)的具體實(shí)現(xiàn)細(xì)節(jié),在整個Code Review中,審查者可以提出自己的建議或者修改意見。在這種情景下,審查者一般由團(tuán)隊(duì)里面比較資深的工程師、架構(gòu)師、技術(shù)專家等成員組成。

關(guān)于Code Review的形式,還可以采取極限編程中的“結(jié)對編程”形式。這種形式可以采取兩個人位置坐在一起去審查代碼,可以采取兩個平臺比如IOS 和android 的開發(fā)人員一起去審查,或者經(jīng)驗(yàn)資深的和經(jīng)驗(yàn)不資深的人員一起搭配去審查。

重構(gòu)的這三個時機(jī)要把握好原則,即什么時候不應(yīng)該重構(gòu),比如有時候既有代碼實(shí)現(xiàn)太混亂啦,重構(gòu)它還不如重新寫一個來得簡;此外,如果你的項(xiàng)目已經(jīng)進(jìn)入了尾期,此時也應(yīng)該避免重構(gòu),這時機(jī)應(yīng)該盡可能以保持軟件的穩(wěn)定性為主。

理解了重構(gòu)是做什么,重構(gòu)的作用,為什么要重構(gòu),以及重構(gòu)的時機(jī),我們對重構(gòu)有了初步認(rèn)識,接下來筆者重點(diǎn)篇幅來講解如何使用重構(gòu)技巧去優(yōu)化代碼質(zhì)量達(dá)成Clean Code .

重構(gòu)技巧 — 函數(shù)重構(gòu)

重構(gòu)的源頭一切從重構(gòu)函數(shù)開始,掌握函數(shù)重構(gòu)技巧是重構(gòu)過程中很關(guān)鍵的一步,接下來我們來探討下函數(shù)重構(gòu)有那些實(shí)用技巧。

  • 重命名函數(shù)(Rename Function Name) : Clean Code要求定義的變量和函數(shù)名可讀性要強(qiáng),從名字就可以知道這個變量和函數(shù)去做什么事情,所以好的可讀性強(qiáng)的函數(shù)名稱很重要,特別是有助于理解比較復(fù)雜的業(yè)務(wù)邏輯。

  • 移除參數(shù)(Remove Parameter): 當(dāng)函數(shù)不再需要某個參數(shù)時,要果斷移除,不要為了某個未知需求預(yù)留參數(shù),過多的參數(shù)會給使用者帶來參數(shù)困擾。

  • 將查詢函數(shù)和修改函數(shù)分離:如果某個函數(shù)既返回對象值,又修改對象狀態(tài)。這時候應(yīng)該建立兩個不同的函數(shù),其中一個負(fù)責(zé)查詢,另一個負(fù)責(zé)修改。如果查詢函數(shù)只是簡單的返回一個值而沒有副作用,就可以無限次的調(diào)用查詢函數(shù)。對于復(fù)雜的計(jì)算也可以緩存結(jié)果。

  • 令函數(shù)攜帶參數(shù):如果若干函數(shù)做了類似的工作,只是少數(shù)幾個值不同導(dǎo)致行為略有不同,合并這些函數(shù),以參數(shù)來表達(dá)不同的值。

  • 以明確函數(shù)取代參數(shù):有一個函數(shù)其中的邏輯完全取決于參數(shù)值而采取不同行為,針對該參數(shù)的每一個可能值建立一個單獨(dú)的函數(shù)。

  • 保持對象完整性:如果你需要從某個對象取若干值,作為函數(shù)的多個參數(shù)傳進(jìn)去,特別是需要傳入較多參數(shù)比如5個參數(shù)或者更多參數(shù)時,這種情況建議直接將這個對象直接傳入作為函數(shù)參數(shù),這樣既可以減少參數(shù)的個數(shù),增加了對象間的信賴性,而且這樣被調(diào)用者需要這個對象的其他屬性時可以不用人為的再去修改函數(shù)參數(shù)。

  • 以函數(shù)取代參數(shù):對象調(diào)用某個函數(shù),并將所得結(jié)果作為參數(shù)傳遞給另外一個函數(shù),而那個函數(shù)本身也能夠調(diào)用前一個函數(shù),直接讓那個函數(shù)調(diào)用就行,可以直接去除那個參數(shù),從而減少參數(shù)個數(shù)。

  • 引入?yún)?shù)對象:某些參數(shù)總是同時出現(xiàn),新建一個對象取代這些參數(shù),不但可以減少參數(shù)個數(shù),而且也許還有一些參數(shù)可以遷移到新建的參數(shù)類中,增加類的參數(shù)擴(kuò)展性。

  • 移除設(shè)值函數(shù)(Setting Method):如果類中的某個字段應(yīng)該在對象創(chuàng)建時賦值,此后就不再改變,這種情景下就不需要添加Setting method。

  • 隱藏函數(shù):如果有一個函數(shù)從來沒有被其他類有用到,或者是本來被用到,但隨著類動態(tài)添加接口或者需求變更,之后就使用不到了,那么需要隱藏這個函數(shù),也就是減小作用域。

  • 以工廠函數(shù)取代構(gòu)造函數(shù):如果你希望創(chuàng)建對象時候不僅僅做簡單的構(gòu)建動作,最顯而易見的動機(jī)就是派生子類時根據(jù)類型碼創(chuàng)建不同的子類,或者控制類的實(shí)例個數(shù)。

重構(gòu)技巧 — 條件表達(dá)式

  • 分解條件表達(dá)式:如果有一個復(fù)雜的條件語句,if/else語句的段落邏輯提取成一個函數(shù)。

  • 合并條件表達(dá)式:一系列條件測試,都得到相同的測試結(jié)果,可以將這些測試表達(dá)式合并成成一個,并將合并后的表達(dá)式提煉成一個獨(dú)立函數(shù),如果這些條件測試是相互獨(dú)立不相關(guān)的,就不要合并。

  • 合并重復(fù)的條件片段:在條件表達(dá)式的每個分支上有著相同的一段代碼,把這段代碼遷移到表達(dá)式之外。

  • 移除控制標(biāo)記:不必遵循單一出口的原則,不用通過控制標(biāo)記來決定是否退出循環(huán)或者跳過函數(shù)剩下的操作,直接break或者return。

  • 以衛(wèi)語句替代嵌套條件表達(dá)式:條件表達(dá)式通常有兩種表現(xiàn)形式,一:所有分支都屬于正常行為;二:只有一種是正常行為,其他都是不常見的情況。對于一的情況,應(yīng)該使用if/else條件表達(dá)式;對于二這種情況,如果某個條件不常見,應(yīng)該單獨(dú)檢查條件并在該條件為真時立即從函數(shù)返回,這樣的單獨(dú)檢查常常被稱為衛(wèi)語句。

  • 以多態(tài)取代條件表達(dá)式:如果有個條件表達(dá)式根據(jù)對象類型的不同選擇而選擇不同的行為,將條件表達(dá)式的每個分支放進(jìn)一個子類內(nèi)的覆寫函數(shù)中,將原始函數(shù)聲明為抽象函數(shù)。

  • 引入Null對象:當(dāng)執(zhí)行一些操作時,需要再三檢查某對象是否為NULL,可以專門新建一個NULL對象,讓相應(yīng)函數(shù)執(zhí)行原來檢查條件為NULL時要執(zhí)行的動作,除NULL對象外,對特殊情況還可以有Special對象,這類對象一般是Singleton.

  • 引入斷言:程序狀態(tài)的一種假設(shè)

  • 以MAP取代條件表達(dá)式:通過HashMap的Key-Value鍵值對優(yōu)化條件表達(dá)式,條件表達(dá)式的判斷條件作為key值,value值存儲條件表達(dá)式的返回值。

  • 通過反射取代條件表達(dá)式:通過動態(tài)反射原理

重構(gòu)技巧 — 案例

前面這多章節(jié)內(nèi)容主要都是理論內(nèi)容,接下來我們來看看具體的重構(gòu)案例。

Map去除if條件表達(dá)式

關(guān)于該技巧的實(shí)現(xiàn)方法,上章節(jié)有講述,我們直接看代碼案例如下代碼所示:

原始的條件表達(dá)式代碼如下圖1所示:

public static int getServiceCode(String str){
     int code = 0;
     if(str.equals("Age")){
         code = 1;
     }else if(str.equals("Address")){
         code = 2;
     }else  if(str.equals("Name")){
         code = 3;
     }else if(str.equals("No")){
         code = 4;
     }
     return  code;
 }

重構(gòu)后的代碼如下所示:

public static void initialMap(){
     map.put("Age",1);
     map.put("Address",2);
     map.put("Name",3);
     map.put("No",4);
 }

上述代碼是直接通過Map結(jié)構(gòu),將條件表達(dá)式分解, Key 是條件變量,Value是條件表達(dá)式返回值。取值很方便,顯然高效率O(1)時間復(fù)雜度取值。這種重構(gòu)技巧適合于比較簡單的條件表達(dá)式場景,下面是比較復(fù)雜的沒有返回值的條件表達(dá)式場景,我們?nèi)タ纯慈绾翁幚怼?/p>

反射去除分支結(jié)構(gòu)

原始的條件表達(dá)式代碼如下圖1所示:

(點(diǎn)擊放大圖像)

圖1 條件表達(dá)式示范

(點(diǎn)擊放大圖像)

圖2 通過Map和反射重構(gòu)示范

如上圖2所示,通過Map和反射去分解條件表達(dá)式,將條件表達(dá)式分支的邏輯抽取到子類中的覆寫函數(shù)中,提取了共同的抽象類,里面包含抽象接口 handleBusinessData,子類繼承實(shí)現(xiàn)它。

多態(tài)取代條件表達(dá)式

(點(diǎn)擊放大圖像)

圖3 重構(gòu)后的案例結(jié)果圖

(點(diǎn)擊放大圖像)

圖4 重構(gòu)后的案例-多態(tài)如何使用

(點(diǎn)擊放大圖像)

圖5 重構(gòu)后的代碼結(jié)構(gòu)圖

(點(diǎn)擊放大圖像)

圖6 重構(gòu)-抽象類、簡單工廠模式思想去實(shí)現(xiàn)條件表達(dá)式的分解

如上圖6所示,在原始的條件表達(dá)式中,有兩個條件表達(dá)式分支(分支邏輯):

  • 中文入住人操作HotelCNPasserngerOperaton類

  • 英文入住人操作HotelEnPassengerOperation 類

共同抽取了基類抽象類:AbstractPassengerOperation,其兩個分支子類去繼承抽象類。

為了分解條件表達(dá)式,筆者采取了多態(tài)的重構(gòu)技巧去實(shí)現(xiàn),具體有兩種實(shí)現(xiàn)方式,第一種實(shí)現(xiàn)方式是采用抽象類去實(shí)現(xiàn)多態(tài),代碼結(jié)構(gòu)圖如圖5 passenger文件夾,UML類圖如上圖6所示。第二種實(shí)現(xiàn)方式是采用接口去實(shí)現(xiàn)多態(tài),代碼結(jié)構(gòu)如圖5 passenger2 文件夾,UML類圖如上圖7所示。

(點(diǎn)擊放大圖像)

圖7重構(gòu)-接口狀態(tài)者模式思想去實(shí)現(xiàn)條件表達(dá)式的分解

如上圖7所示,在原始的條件表達(dá)式中,有兩個條件表達(dá)式分支(分支邏輯),其分支邏輯分別放在了子類HotelCNPassengerState 和 HotelENPassengerState中,統(tǒng)一提取了接口類 PassengerState類,里面包含子類都需要實(shí)現(xiàn)的兩個基礎(chǔ)接口。從圖7,可以看出,是使用了狀態(tài)者模式。

經(jīng)過了上述重構(gòu)之后,我們達(dá)成了什么效果:

  • 邏輯清晰

  • 主邏輯代碼行數(shù)減少

  • 業(yè)務(wù)邏輯,更好的封裝解藕,無需關(guān)注具體的業(yè)務(wù)細(xì)節(jié)

  • 采用了多態(tài)、抽象、狀態(tài)模式、工廠模式、Build模式的等不同的思想和方法,很多不同的重構(gòu)技巧去重構(gòu)一個功能,值得推廣和借簽;

重構(gòu)技巧-移動平臺Android實(shí)戰(zhàn)篇

前面筆者從理論和實(shí)際案例的角度對重構(gòu)進(jìn)行了分析,包括為什么需要重構(gòu)、重構(gòu)的作用、重構(gòu)的時機(jī)、如何進(jìn)行重構(gòu)等內(nèi)容,推薦提前閱讀。

接下來筆者將從實(shí)踐的角度去分享,即在平時開發(fā)Android工程中,我們?nèi)绾胃咝プ鲋貥?gòu),重構(gòu)和開發(fā)怎么比較好的有效結(jié)合起來。

Long Method 實(shí)戰(zhàn)

Long Method 是筆者前面提到的“代碼壞味道”之一,這也是開發(fā)者一般經(jīng)常容易犯的典型錯誤。

接下來筆者介紹在Android平臺中如何去解決這個“bad taste”,實(shí)際上我們可以通過計(jì)算函數(shù)的圈復(fù)雜度(cyclomatic complexity)來判斷函數(shù)是否過長,一般cyclomatic complexity > 11 ,就可以認(rèn)為函數(shù)過長,需要進(jìn)行重構(gòu)優(yōu)化,那么關(guān)于函數(shù)重構(gòu)的優(yōu)化技巧在前面幾章我也有重點(diǎn)提到。

在解決圈復(fù)雜度過大這個問題,首先我們要去發(fā)現(xiàn)你的工程哪里存在問題,這一步我們可以通過工具或者第三方插件幫我們?nèi)ソ鉀Q,比如打開Android studio 工具欄 Analyze –> Run inspection by name,如下圖8所:

(點(diǎn)擊放大圖像)

圖8 Analyze工具示意圖

如圖8所示,選擇Run inspection by name ,打開如下圖9所示界面:

(點(diǎn)擊放大圖像)

圖9 Overly long method 界面示意圖

如圖9所示,輸入long 出現(xiàn)Overly long method ,選擇如上圖所示,點(diǎn)擊會打開一個新的界面如圖10所示:

(點(diǎn)擊放大圖像)

圖10 Run ‘Overly long method’

如圖10所示,可以選擇對當(dāng)前工程,當(dāng)前File,當(dāng)前Module 或者其他Module進(jìn)行分析,等待運(yùn)行一段時間分析結(jié)果如下圖11所示:

(點(diǎn)擊放大圖像)

圖11 Run ‘Overly long method’ 結(jié)果

得到圖11分析結(jié)果之后,我們就可以有針對性的去進(jìn)行優(yōu)化重構(gòu)了,知道哪些類哪些函數(shù)需要去優(yōu)化,具體重構(gòu)優(yōu)化是一般可以將過長的函數(shù)拆分成幾個不同的小函數(shù),拆分原則:一個函數(shù)的功能要保持職責(zé)單一,查詢和修改職責(zé)分開;所以可以通過不同類型的功能業(yè)務(wù)邏輯處理或者查詢、修改功能去拆分大函數(shù)。

Too many parameters 實(shí)戰(zhàn)

函數(shù)參數(shù)過多,也是典型的“代碼壞味道”之一,同理打開如上圖8所示的界面,然后輸入 too many pa 關(guān)鍵字打開如下圖12所示的界面:

(點(diǎn)擊放大圖像)

圖12 Analyze too many pa 關(guān)鍵字界面

選擇圖中所示的 “Method with too many parameters”,會出現(xiàn)如上圖10所示的界面,然后選擇“Whole Project”,運(yùn)行之后,分析得到的結(jié)果如下圖13所示:

(點(diǎn)擊放大圖像)

圖13 “Too many parameters”結(jié)果分析圖

Redundant local variable 實(shí)戰(zhàn)

冗余的局部變量,同樣是造成代碼壞味道的源頭,輸入 “Redudant  關(guān)鍵字”,同理執(zhí)行得出分析結(jié)果如下圖14所示,然后我們根據(jù)分析后的結(jié)果有針對性的去重構(gòu)優(yōu)化:

(點(diǎn)擊放大圖像)

圖14 冗余局部變量分析結(jié)果

Unused Declaration –無用函數(shù)實(shí)戰(zhàn)

無用函數(shù)是“代碼壞味道”來源之一,很多函數(shù)因?yàn)闅v史遺留的原因,需求已經(jīng)下線了但是代碼還在遺留在工程里面,或者因?yàn)橹貥?gòu),歷史遺留代碼沒有完全刪除或者想暫時留著下個版本使用,這些都是不好的習(xí)慣,不用的代碼應(yīng)該立即刪除,而不應(yīng)該保留在工程項(xiàng)目中。

同理打開如上圖8所示的界面,然后輸入 Unused declaration關(guān)鍵字打開如下圖15所示的界面:

(點(diǎn)擊放大圖像)

圖15 Unused declaration

分析結(jié)果能得出你整個工程中沒有被使用的函數(shù),我們都可以刪除掉。

無用函數(shù)參數(shù)-實(shí)戰(zhàn)

同理,輸入關(guān)鍵字 Unused method parameter, 如下圖16所示, 執(zhí)行可以分析出工程中有哪些函數(shù)存在無用參數(shù),可以針對性的進(jìn)行優(yōu)化。

(點(diǎn)擊放大圖像)

圖16 unused method parameter

infer 實(shí)戰(zhàn)

Infer 是Facebook開源的靜態(tài)代碼檢查工具,可檢查 Android 和 Java 代碼中的 NullPointException 和 資源泄露。除了以上,Infer 還可發(fā)現(xiàn) iOS 和 C 代碼中的內(nèi)存泄露,內(nèi)存泄露,內(nèi)存泄露。

Android studio 已經(jīng)將infer集成到工具欄里面,點(diǎn)擊Analyze->infer Nullity,執(zhí)行分析得出的界面類似如下圖17所示:

(點(diǎn)擊放大圖像)

圖17 infer Nullity 分析結(jié)果圖

點(diǎn)擊圖17所示的分析結(jié)果具體項(xiàng),可以定位到具體的代碼文件,然后我們?nèi)ナ謩优袛?或者直接點(diǎn)擊“Infer Nullity Annotations”,工具直接幫我去完成改造結(jié)果。

第三方插件與Android studio 的集成

FindBugs 集成

FindBugs是一個開源的靜態(tài)代碼分析工具,基于LGPL開源協(xié)議,無需運(yùn)行工程就能對代碼進(jìn)行分析的工具。它不注重style及format,注重檢測真正的bug及潛在的性能問題 ,以bytecode(*.class、*.jar)為對象進(jìn)行檢查。除了單獨(dú)運(yùn)行,還可以用作Android-studio 和Eclipse 的Plug-in,以及嵌入Ant或者M(jìn)aven作為task之一進(jìn)行運(yùn)行。

Findbugs自帶60余種Bad practice,80余種Correntness,1種Internationalization,12種Malicious code vulnerability,27種Multithreaded correntness,23種Performance,43種Dodgy。它可以檢測檢測java programing中容易陷入的bug pattern,比如 equals() 實(shí)現(xiàn)時的一般規(guī)約違反Null pointer的參照 ,Method的返回值的check遺漏 ,初始化前field的訪問, Multi-thread的正確性,無條件的wait,Code的脆弱性 ,可以變更的靜態(tài)object ,內(nèi)部數(shù)列參照的return等。

Android Studio 可以通過插件的方式安裝,具體是打開Android Studio->Preference –>搜索plugin 選擇 Plugins Tab ,打開界面如下圖17所示:

(點(diǎn)擊放大圖像)

圖17 搜索FindBugs-IDEA界面

如上圖17中,點(diǎn)擊install ,downloading plugin install ,然后重啟Android studio ,會有提示界面如下圖18所示:

(點(diǎn)擊放大圖像)

圖18 Android FindBugs Enable

點(diǎn)擊“Enable Android FindBugs”,會打開界面,在見面中添加Plugin For Android FindBugs即可。

然后在Android Studio工具欄上,打開如下圖19所示的界面:

(點(diǎn)擊放大圖像)

圖19 FindBugs 入口界面

如上圖19所示,可以分析對前的文件,可以分析一個Module files ,也可以分析一個工程文件,選擇一項(xiàng)會得出分析結(jié)果如下圖20所示:

(點(diǎn)擊放大圖像)

圖20 FindBugs分析結(jié)果圖

根據(jù)圖20所示的結(jié)果,我們可以查看具體的Bug details ,存在什么問題,然后具體跟蹤到對應(yīng)的代碼,根據(jù)對應(yīng)的建議去修改。

MetricsReloaded 集成

MetricsReloaded是一個計(jì)算代碼復(fù)雜度即圈復(fù)雜度的Jetbrain開源開發(fā)的第三方插件。關(guān)于代碼復(fù)雜度,有個維度的衡量,在這里需要普及下軟件復(fù)雜度的相關(guān)知識:基本復(fù)雜度(Essential Complexity (ev(G))、模塊設(shè)計(jì)復(fù)雜度(Module Design Complexity (iv(G)))、Cyclomatic Complexity (v(G))圈復(fù)雜度。

ev(G)基本復(fù)雜度是用來衡量程序非結(jié)構(gòu)化程度的,非結(jié)構(gòu)成分降低了程序的質(zhì)量,增加了代碼的維護(hù)難度,使程序難于理解。因此,基本復(fù)雜度高意味著非結(jié)構(gòu)化程度高,難以模塊化和維護(hù)。實(shí)際上,消除了一個錯誤有時會引起其他的錯誤。

Iv(G)模塊設(shè)計(jì)復(fù)雜度是用來衡量模塊判定結(jié)構(gòu),即模塊和其他模塊的調(diào)用關(guān)系。軟件模塊設(shè)計(jì)復(fù)雜度高意味模塊耦合度高,這將導(dǎo)致模塊難于隔離、維護(hù)和復(fù)用。模塊設(shè)計(jì)復(fù)雜度是從模塊流程圖中移去那些不包含調(diào)用子模塊的判定和循環(huán)結(jié)構(gòu)后得出的圈復(fù)雜度,因此模塊設(shè)計(jì)復(fù)雜度不能大于圈復(fù)雜度,通常是遠(yuǎn)小于圈復(fù)雜度。

v(G)是用來衡量一個模塊判定結(jié)構(gòu)的復(fù)雜程度,數(shù)量上表現(xiàn)為獨(dú)立路徑的條數(shù),即合理的預(yù)防錯誤所需測試的最少路徑條數(shù),圈復(fù)雜度大說明程序代碼可能質(zhì)量低且難于測試和維護(hù),經(jīng)驗(yàn)表明,程序的可能錯誤和高的圈復(fù)雜度有著很大關(guān)系。

同理,如上圖17所示一樣去安裝MetricsReloaded插件,安裝成功后執(zhí)行Analyze->Calculate Metrics,打開如下圖21所示的界面:

(點(diǎn)擊放大圖像)

圖21 Calculate Metrics 界面

如上圖所示,選擇Complexity metrics,執(zhí)行分析結(jié)果如下圖22所示:

(點(diǎn)擊放大圖像)

圖22 Calculate Metrics 分析結(jié)果圖

如上圖22所示界面中的紅顏色部分,代表需要去重構(gòu)優(yōu)化的,點(diǎn)擊當(dāng)前行,會定位到源代碼,然后我們針對性去優(yōu)化函數(shù)。上圖中,可以分析出方法的圈復(fù)雜度、類的圈復(fù)雜度、包的圈復(fù)雜度、模塊的圈復(fù)雜度、工程的圈復(fù)雜度。

Sonar 集成

對于Android(Java)工程,Sonar官方提供了Java Plugin和Java-specific Plugins,這些插件可以實(shí)現(xiàn)大部分Findbugs、PMD、Checkstyle、Android Lint等的檢查規(guī)則。主要可以從以下幾個方面檢測代碼質(zhì)量:

(1)復(fù)雜度:項(xiàng)目中方法、類、文件的復(fù)雜度分布情況;

(2)重復(fù):展示代碼中重復(fù)嚴(yán)重的地方;

(3)單元測試覆蓋率:統(tǒng)計(jì)并展示單元測試覆蓋率(主要用于java工程);

(4)代碼標(biāo)準(zhǔn):通過PMD、CheckStyle等代碼規(guī)則檢測工具規(guī)范代碼編寫;

(5)代碼注釋:沒有注釋或者過多的注釋都不是一個良好的編程習(xí)慣;

(6)潛在的bug: 通過PMD、Findbugs等代碼檢測工具檢測出潛在的bug;

(7)架構(gòu)設(shè)計(jì):可以檢測耦合、依賴關(guān)系、架構(gòu)規(guī)則、管理第三方的jar包等。

集成Sonar之后,我們需要著種解決的就是代碼重復(fù)率問題,這也是“代碼壞味道”最典型的問題,開發(fā)者最容易犯這個問題,特別是不少開發(fā)者喜歡偷懶,容易拷貝來拷貝去,造成工程代碼的重復(fù)率比較高。一次構(gòu)建運(yùn)行之后,我們可以得出分析結(jié)果,類似如下圖23所示:

(點(diǎn)擊放大圖像)

圖23 sonar構(gòu)建運(yùn)行結(jié)果

點(diǎn)擊重復(fù)率,我們可以看出哪些文件之間的代碼是重復(fù)的,然后針對性使用抽取工具類、合并類、合并分解函數(shù)等技術(shù)重構(gòu)手段去優(yōu)化。

SonarLint集成

前面我們所講到的Sonar之前的提供的本地工具是需要依賴SonarQube服務(wù)器的,這樣導(dǎo)致其運(yùn)行速度緩慢。 新出的SonarLint的掃描引擎直接安裝在本地,速度超快,實(shí)時探測代碼技術(shù)債務(wù),給程序員最快速的反饋,排除代碼異味的絕佳利器,幫助程序員獲得Clean code。 新版SonarLint也能鏈接SonarQube服務(wù)器,但這并不必要。本地安裝SonarLint來做代碼本地掃描,本地發(fā)現(xiàn)本地修改,而且能快速看到修改結(jié)果,快速處理代碼臭味,有效控制技術(shù)債務(wù)。

按照如上圖17所示一樣去安裝SonarLint插件,安裝之后重啟Android Studio ,即可動態(tài)掃描出結(jié)果如下圖24所示:

(點(diǎn)擊放大圖像)

圖24 SonarLint掃描結(jié)果圖

重構(gòu)技巧實(shí)戰(zhàn)-小結(jié)

本文我們講述了在Android程序開發(fā)過程中如何結(jié)合工具去幫助我們做重構(gòu)優(yōu)化的各種技能包括Android Studio自己已經(jīng)集成的插件Code Inspection 、infer Nullity以及FindBugs、MetricsReloaded、Sonar、SonarLint等第三方插件工具,其實(shí)還有很多類似著名的插件比如QAPlugin、PMD、Hammurapi 、Lint4j等工具,大家可以自行嘗試使用,在這里我不一一說明。

所謂工欲善其事必先利就是這個道理,我們?nèi)绻枰プ鲋貥?gòu)優(yōu)化,首先我們要知道我們做的不好的地方-代碼的壞味道在哪里,這種工作如果靠人為手動的去發(fā)現(xiàn),那么效率和產(chǎn)出將會及其低下,所以我們需要借助于集成插件工具幫我們自動去掃描發(fā)現(xiàn)問題,然后再去針對性的重構(gòu)優(yōu)化,產(chǎn)出Clean code 。

寫在最后

重構(gòu)是一門比較大而深的話題和課題,筆者這次主要探討了如何通過有效的重構(gòu)技巧去寫成優(yōu)秀的整潔代碼,代碼整潔之道就是要將重構(gòu)始終貫穿在整個開發(fā)過程中,不斷的持續(xù)的漸進(jìn)重構(gòu),從而將以前的技術(shù)債全部還完。

重構(gòu)是個技術(shù)活,需要很資深的人士去整體架構(gòu)把控技術(shù)方案和產(chǎn)品質(zhì)量,才能使重構(gòu)做的更加有效并且不會引入新的問題,但是無論我們最終采取什么手段去重構(gòu),最終我們都需要盡量符合Solid設(shè)計(jì)相關(guān)原則:

  • 類的單一職責(zé):體現(xiàn)了類只應(yīng)該做一件事,良好的軟件設(shè)計(jì)中系統(tǒng)是由一組大量的短小的類組成,以及需要他們之間功能協(xié)作完成,而不是幾個上帝類。如果類的職責(zé)超過一個,這些職責(zé)之間就會產(chǎn)生耦合。改變一個職責(zé),可能會影響和妨礙類為其他人服務(wù)的功能。這種類型的耦合將會導(dǎo)致脆弱的設(shè)計(jì),在修改的時候可能會引入不少未知的問題。

  • 開閉原則:其定義是說一個軟件實(shí)體如類,模塊和函數(shù)應(yīng)該對擴(kuò)展開放,而對修改關(guān)閉,具體來說就是你應(yīng)該通過擴(kuò)展來實(shí)現(xiàn)變化,而不是通過修改原有的代碼來實(shí)現(xiàn)變化,該原則是面相對象設(shè)計(jì)最基本的原則。其指導(dǎo)思想就是(1)抽象出相對穩(wěn)定的接口,這部分應(yīng)該不動或者很少改動;(2)封裝變化;不過在軟件開發(fā)過程中,要一開始就完全按照開閉原則來可能比較困難,更多的情況是在不斷的迭代重構(gòu)過程中去改進(jìn),在可預(yù)見的變化范圍內(nèi)去做設(shè)計(jì)。

  • 里氏替換原則:子類可以擴(kuò)展父類的功能,但不能改變父類原有的功能。簡單來說,所有使用基類代碼的地方,如果換成子類對象的時候還能夠正常運(yùn)行,則滿足這個原則,否則就是繼承關(guān)系有問題,應(yīng)該廢除兩者的繼承關(guān)系,這個原則可以用來判斷我們的對象繼承關(guān)系是否合理。通常在設(shè)計(jì)的時候,我們都會優(yōu)先采用組合而不是繼承,因?yàn)槔^承雖然減少了代碼,提高了代碼的重用性,但是父類跟子類會有很強(qiáng)的耦合性,破壞了封裝。

  • 接口隔離原則:不能強(qiáng)迫用戶去依賴那些他們不使用的接口。簡單來說就是客戶端需要什么接口,就提供給它什么樣的接口,其它多余的接口就不要提供,不要讓接口變得臃腫,否則當(dāng)對象一個沒有使用的方法被改變了,這個對象也將會受到影響。接口的設(shè)計(jì)應(yīng)該遵循最小接口原則,其實(shí)這也是高內(nèi)聚的一種表現(xiàn),換句話說,使用多個功能單一、高內(nèi)聚的接口總比使用一個龐大的接口要好。

  • 依賴倒置(DIP):高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié);細(xì)節(jié)應(yīng)該依賴抽象。其實(shí)這就是我們經(jīng)常說的“針對接口編程”,這里的接口就是抽象,我們應(yīng)該依賴接口,而不是依賴具體的實(shí)現(xiàn)來編程。DIP描述組件之間高層組件不應(yīng)該依賴于底層組件。依賴倒置是指實(shí)現(xiàn)和接口倒置,采用自頂向下的方式關(guān)注所需的底層組件接口,而不是其實(shí)現(xiàn)。DI模式很好例子的就是應(yīng)用IOC(控制反轉(zhuǎn))框架,構(gòu)造方式分為分構(gòu)造注入,函數(shù)注入,屬性注入 。

當(dāng)我們在做重構(gòu)優(yōu)化的時候應(yīng)該充分考慮上面這幾個原則,一開始可能設(shè)計(jì)并不完美,不過可以在重構(gòu)的過程中不斷完善。但其實(shí)很多人都跳過了設(shè)計(jì)這個環(huán)節(jié),拿到一個模塊直接動手編寫代碼,更不用說去思考設(shè)計(jì)了,項(xiàng)目中也有很多這樣的例子。當(dāng)然對于簡單的模塊或許不用什么設(shè)計(jì),不過假如模塊相對復(fù)雜的話,能夠在動手寫代碼之前好好設(shè)計(jì)思考一下,養(yǎng)成這個習(xí)慣,肯定會對編寫出可讀性、穩(wěn)定性、健壯性、靈活性、可服用性、可擴(kuò)展性較高的代碼有幫助。

感謝徐川對本文的審校。

給InfoQ中文站投稿或者參與內(nèi)容翻譯工作,請郵件至[email protected]。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號: InfoQChina )關(guān)注我們。

 

來自:http://www.infoq.com/cn/articles/clean-code-refactor

 

標(biāo)簽: ssd 代碼 服務(wù)器 開發(fā)者 搜索 推廣 移動平臺

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:從源頭入手,一分鐘秒懂為什么要搞微服務(wù)架構(gòu)?

下一篇:軟件架構(gòu)設(shè)計(jì)中要注意的六個方面