如何實施代碼重構?
先來講講我認為怎麼樣才算是一次合格的重構。
對於什麼是重構,《重構》書中已經有明確的定義,分名詞和動詞兩種形式。
重構(名詞):對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本。
重構(動詞):使用一系列重構手法,在不改變軟體可觀察行為的前提下,調整其結構。
就像「看板」不是「我們看到的那個白板」一樣,「重構」也不是「重新修改代碼」那麼簡單。
我就看到過太多打著重構的幌子,把系統改的面目全非,最後出了問題直接甩鍋到重構身上的場景了。那怎樣才算是一次合格的重構呢?我覺得至少需要做到以下幾點:
- 消除味道:一個重構應該是從識別一個壞味道(Bad Smell)開始,以消除一個壞味道結束,任何不以消除壞味道為目標的重構都是耍流氓。
- 始終工作:即重構定義中的「在不改變軟體可觀察行為的前提下」,說白了就是重構過程不能破壞甚至改變軟體外在功能。
- 持續集成:不需要為重構單建分支,重構過程可以做到Feature開發在同一分支上持續集成持續交付。
- 隨時中止:例如一個方法重命名,需要修改100個調用點,當改到50個的時候有個緊急的Feature,我可以隨時暫停重構,立即切換到Feature開發上,且不需要回滾已做的重構。
- 斷點續傳:還是上邊的例子,假如我已經完成了緊急Feature的開發,可以隨時繼續之前的重構,完成剩下50個調用點的重命名。
- 過程可逆:對於重構,經常有人會問:你怎麼保證重構就會更好而不是更壞呢?重構的偉大就在於他跳出了對錯之爭,將關注點放到如何快速平滑安全的變化上,當然也包括反向重構。所以我的回答是:無法保證,但是我可以一分鐘就重構回來。如果仔細看,《重構》書里的所有重構手法都是雙向的,比如「Extract Method」和「Inline Method」。
可以反思一下,我們平時自認為的那些重構,是否都符合了以上的這些要求?
- 多少次我們打著重構的旗號,七零八碎,無法復原。
- 多少次我們打著重構的旗號,分支開發,集成困難。
- 多少次我們打著重構的旗號,半途而廢,迷途難返
- 多少次我們打著重構的旗號,孤注一擲,進退兩難。
在我的眼裡,這些都不是合格的重構,甚至都不能稱之為重構,好的重構應該像一邊開車一邊換輪胎一樣,保證系統隨時可工作的前提下,還可以對其結構做出安全高效的調整。
可見重構並不簡單,那要怎樣才能達到上述的那些要求呢?
重構的心法
在過去的幾年,我一直在學習和思考重構的各種手法。從剛開始的亂改一氣,到學習基於IDE和插件的各種快捷鍵流的重構手法,以及研究如何通過組合各種基礎重構手法形成「連招」,從而快速實現更複雜的重構過程。
隨著對於基於IDE的快捷鍵重構手法越來越嫻熟,在IDE和插件的幫助下,我的重構手法越來越華麗而迅捷,在沾沾自喜的同時心裡也慢慢萌生了一些質疑:難道這就是重構么?如果沒有IDE沒有了插件,我還會做重構么?如何用編輯器(Vim,Emacs)做重構?重構只是代碼級別的么?資料庫如何重構呢?系統架構如何重構呢?工具框架如何重構呢?微服務架構下的服務重構呢?公司組織重構呢?
這種感覺就像是武俠小說中的某個柔弱書生,無意中掉到了一個懸崖下,找到了一本武林秘籍,照著上邊的招式練了練就自以為已絕學在身,結果出去雖然能招架一時,但禁不住更大的挑戰。被打的體無完膚後,重新掏出那本秘籍,收起浮躁,懷著誠敬之心努力去參悟那些招式背後更深的哲理,也就是所謂的心法。此時對於我來說,而那本武林秘籍就叫做《重構》。
在帶著這些疑問重讀《重構》的過程中,我欣喜地發現書中那些細緻入微但看似笨拙拖沓的重構手法(例如Rename,使用現代IDE一個快捷鍵就可以搞定,但是老馬用了很多步驟才完成),其實都蘊含著重構最重要最基本的原則和思路,只要按著這些原則去做,無論什麼層次的重構:代碼重構、架構重構、服務重構甚至是組織重構,都可以做到上面提到的一個合格重構的基本要求,即平滑安全可停可續。
把其中的原則思路抽取出十六個字,即所謂的:重構十六字心法。
解釋起來也很簡單,往往我們做」重構「的時候就是在舊的結構(這裡的結構可以是一個方法、一個對象、一個服務、一個資料庫、一個服務甚至是一個組織結構)上直接修改,導致系統長時間處於一個中間不可用狀態,這個狀態持續的時間越長,」重構「失敗的可能性和負面影響就會越大。
而《重構》告訴我們,做內部結構調整時,先不要直接修改舊的結構,保持舊的結構不變,先按照新的設計思路創建一個新的結構,因為這個過程中對於舊的內部結構沒有任何影響,所以是安全的,可持續集成的。當新的結構構件完成時,我們再把對於舊結構的依賴一個個的切換到新的結構上,即所謂的」一步切換「。最後當確認所有對於舊的結構都切換到新的結構上,而且沒有問題後,再將已經沒有任何引用的舊結構刪除掉,完成整個重構過程。
這裡的「一步切換」並不是說整個重構的切換過程必須是一步完成的,例如前面重命名的例子,100個調用點的切換可能是分多次完成的,在這個例子里一步切換指的是每一個調用點的切換過程。這個切換過程是最容易暴露出問題的,所以越簡單越快速越好,一旦出現了問題,就快速的切換回舊的結構後再慢慢排查問題,從而實時保證系統的可用性。
大道至簡,一旦領悟並掌握了這個心法,就發現自己一下從之前狹義的代碼重構中跳脫出來,任何廣義上的重構都立刻變得有章可循。
在架構重構中常用的抽象分支(BranchByAbstraction),以及在微服務架構下服務重構常用到的絞殺者模式,其實都是這種原則的一種體現。
總結
重構可以使軟體更容易地被修改和被理解。通過不斷地改進軟體設計以達到簡單設計的目標,減少由於設計與業務的不匹配帶來的架構與設計腐化。
掌握了重構的手法和心法,會讓重構變得更加簡單安全高效可控,從而真正的發揮出其巨大的威力,讓我們的軟體永葆青春。
文/ThoughtWorks @王健 原文:重構之十六字心法 - ThoughtWorks洞見
絕不增加也不減少功能。
個人在創業小團隊中的體會是:重構絕對不是一項任務。不應該說我們從今天開始做重構,三個月後完成。重構應該是隨時發生的。每一次功能改進都需要進行一點重構。
單元測試寫了沒?沒寫先寫了再考慮重構。
《重構》http://book.douban.com/subject/1229923/
同意@劉宇波的看法,但個人認為在小型公司和團隊中很難持續推進代碼重構:
1) 重構並不直接創造「價值」,或者創造的價值並不能在短時間內很快顯現出來。對於不懂coding的boss來說,「先讓輪子滾起來」比「輪子是否圓潤」重要的多的多。
2) 重構需要很好的技術背景作為支撐,需要很好的「嗅覺」。這些技術包括但不僅限於:設計模式,不同編程語言的編程哲學和理念,不同框架的best practice等等。而這些需要在自己寫過大量的代碼和閱讀過大量其他人寫的優秀的代碼的基礎上才能慢慢開始嗅到code中的bad smell。小團隊內技術水平層次不齊,很難從同一個基點開始進行優化。
3) 小型團隊手頭的工作和待開發的功能往往較多,產品迭代周期快,往往沒有精力去做重構。
4) 重構需要專註和持續。需要你能靜得下心來深入到代碼裡面,去思考,去優化。並且持之以恆地去做這件事,才能慢慢看到效果。但是在一切以快為前提,結果導向的浮躁的小型創業公司文化氛圍里,重構大部分都只是一個可選項。
我覺得重構最重要的首先是清晰的目標,比如為了獲得更好得相容性,讓軟體可以更容易獲得某些擴展功能...當然,為了讓代碼更美也可以是目標之一,而獲得的好處往往是以後會讓人大吃一驚的...但重構如果只是單純得為了應用某些設計模式,個人覺得有些本末倒置...然後才能檢視在代碼中不夠優美或完善的地方,在這個基礎之上,就可以重新設計代碼結構,並且評估重構所需要的代價...
如何實施代碼重構?閱讀《重構》的筆記獻上。
從消除重複代碼開始,這裡都是講怎麼重構的,大家可以看下~~如何消除重複代碼?
先設計後動手,多想少寫。對不遠的未來做預期,覺得有必要就重構,寧可寫新模塊,不要改舊模塊。讓代碼永遠保持比理想狀態差一點。時刻重構和過度設計一樣,是病,得治。
看完重構一書的總結:
《重構》一書經典總結(一) - 世上只有一種英雄主義 - CSDN博客
我著重說一下代碼重構,我建議將重構提前到編碼時就重構。當然編碼完成成後也需要重構,但那時重構就要顧慮更多了。
首寫,寫單元測試(ut),在第一個public函數完成時,就寫單元測試代碼。這時單元測試可以推動你做如下重構,1 去除不必要的引用,如c/c++中的include 頭文件,因為寫ut要寫makefile文件,多一個引用,會多複雜一分。2 去耦合,耦合太大沒法測試 3 功能分層,一個函數跨多個層沒法測試 4 重新思考函數定位,定位不好,不符合正交性的函數難以測試。 其次,代碼自查,1 將大的函數拆分為小的函數,最好看看小函數是否可以操象成一個通用函數。2 注意名稱,看有沒有名實不符的。 3 將魔術數據用只讀變數,枚舉,宏替換 4 關注一下函數,變數的可見範圍。能用private的,不要用protected. 5 注意可讀性。 看看有沒有表達不明確,明了的地方,如有些if條件中有多條判斷條件,看看能不能寫成一個函數。 6 繼承的類可否換成組合。再次,在實現幾個功能後,回頭看看有沒有相同或相似的邏輯,若有,抽象出來。注意,在代碼完成後的重構一定要小心,前提是對代碼非常了解,然後小步重構,每重構一步,驗證一下,沒問題,將代碼簽入代碼庫,再重構下一步。一定要保證修改的代碼能回退回去。就因為這些問題,我比較喜歡編碼時重構。還有,經過測試人員測試又沒有自動化測試的支持,建議不要重構了。有多餘的時間支配就可以
重構不是任務,是對優秀代碼的不懈追求,是一種精益求精的工匠精神
推薦閱讀:
※作為程序員你寫過的最漂亮的代碼是什麼?
※寫作和編程的關係?能否認為編程是寫作的一種?
※程序間以結構化數據而不是字元串傳遞信息是否更合理?
※在循環語句中,for(i=0;i<n;i++)和for(i=0;i<n;++i)有什麼區別?
※編寫彙編代碼最好的IDE是什麼?