重構------整潔程式碼之道
重構 — 作用
重構,絕對是軟體開發寫程式過程中最重要的事之一。那麼什麼是重構,如何解釋重構。名詞:對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本。動詞:使用一系列重構手法,在不改變軟體可觀察行為的前提下,調整其結構。
重構不只可以改善既有的設計結構,還可以幫助我們理解原來很難理解的流程。比如一個複雜的條件表示式,我們可能需要很久才能看明白這個表示式的作用,還可能看了好久終於看明白了,過了沒多長時間又忘了,現在還要從頭看,如果我們把這個表示式運用Extract Method抽象出來,並起一個易於理解的名字,如果函式名字起得好,下次當我們再看到這段程式碼時,不用看邏輯我們就知道這個函式是做什麼的。
如果對這個函式內所有難於理解的地方我們做了適當的重構,把每個細小的邏輯抽象成一個小函式並起一個容易理解的名字,當我們看程式碼時就有可能像看註釋一樣,不用再像以前一樣通過看程式碼的實現來猜測這段程式碼到底是做什麼的,我一直堅持和秉持這個觀點:好的程式碼勝過註釋,畢竟註釋還是有可能更新不及時的,不及時最新的註釋容易更其他人帶來更多的理解上的困惑。
此外重構可以使我們增加對程式碼和業務邏輯功能的理解,從而幫助我們找到Bug;重構可以幫助我們提高程式設計速度,即重構改善了程式結構設計,並且因為重構的可擴充套件性使新增新功能變得更快更容易。
重構 — 時機
理解了重構的意義和作用,那麼我們何時開始重構呢?筆者一直堅持這種觀點:重構是一個持續的系統性的工程,它是貫穿於整個軟體開發過程中,我們無需專門的挑出時間進行重構,重構應該隨時隨地的進行,即遵循三次法則:事不過三,三則重構。這個準則表達的意思是:第一次去實現一個功能儘管去做,但是第二次做類似的功能設計時會產生反感,但是還是會去做,第三次還是實現類似的功能做同樣的事情,那你就應該去重構。三次準則比較抽象,那麼對應到我們具體的軟體開發流程中,一般可以在這三個時機去進行:
(1) 當新增新功能時如果不是特別容易,可以通過重構使新增特性和新功能變得更容易。在新增新功能的時候,我們就先清理這個功能所需要的程式碼。花一點時間,用滴水穿石的方法逐漸清理程式碼,隨著時間的推移,我們的程式碼就會越來越乾淨,開發速度也會越來越快。
(2) 修改Bug的時候去重構,比如你在查詢定位Bug的過程中,發現以前自己的程式碼或者別人的程式碼因為設計缺陷比如可擴充套件性、健壯性比較差造成的,那麼此時就是一個比較好的重構時機。可能這個時候很多同學就有疑問了,認為我開發要趕進度,沒有時間去重構,或者認為我打過補丁把Bug解決不就行了,不需要去重構。根據筆者之前多年的經驗得出的結論:遇到即要解決即那就是每遇到一個問題,就馬上解決它,而不是選擇繞過它。完善當前正在使用的程式碼,那些還沒有遇到的問題,就先不要理它。在當前前進的道路上,清除所有障礙,以後你肯定還會再一次走這條路,下次來到這裡的時候你會發現路上不再有障礙。
軟體開發就是這樣。或許解決這個問題需要你多花一點時間。但是從長遠來看,它會幫你節省下更多的時間。也就是重構是個循序漸進的過程,經過一段時間之後,你會發現之前所有的技術債務會逐步都不見了,所有的坑相繼都被填平了。這種循序漸進的程式碼重構的好處開始顯現,程式設計的速度明顯會加快。
(3)Code Review時去重構,很多公司研發團隊都會有定期的Code Review,這種活動的好處多多,比如有助於在開發團隊中傳播知識進行技術分享,有助於讓較有經驗的開發者把知識傳遞給欠缺經驗的人,並幫助更多的人對軟體的其他業務模組更加熟悉從而實現跨模組的迭代開發。Code Review可以讓更多的人有機會對自己提出更多優秀好的建議。同時重構可以幫助審查別人的程式碼,因為在重構前,你需要先閱讀程式碼得到一定程度的理解和熟悉,從而提出一些建議和好的idea,並考慮是否可以通過重構快速實現自己的好想法,最終通過重構實踐你會得到更多的成就感滿足感。為了使審查程式碼的工作變得高效有作用,據我以前的經驗,我建議一個審查者和一個原作者進行合作,審查者提出修改建議,然後兩人共同判斷這些修改是否能夠通過重構輕鬆實現,如果修改成本比較低,就在Review的過程中一起著手修改。
如果是比較大型比較複雜的設計複查稽核工作,建議原作者使用UML類序列圖、時間序列圖、流程圖去向審查者展現設計的具體實現細節,在整個Code Review中,審查者可以提出自己的建議或者修改意見。在這種情景下,審查者一般由團隊裡面比較資深的工程師、架構師、技術專家等成員組成。
關於Code Review的形式,還可以採取極限程式設計中的“結對程式設計”形式。這種形式可以採取兩個人位置坐在一起去審查程式碼,可以採取兩個平臺比如IOS 和android 的開發人員一起去審查,或者經驗資深的和經驗不資深的人員一起搭配去審查。
重構的這三個時機要把握好原則,即什麼時候不應該重構,比如有時候既有程式碼實現太混亂啦,重構它還不如重新寫一個來得簡;此外,如果你的專案已經進入了尾期,此時也應該避免重構,這時機應該儘可能以保持軟體的穩定性為主。
理解了重構是做什麼,重構的作用,為什麼要重構,以及重構的時機,我們對重構有了初步認識,接下來筆者重點篇幅來講解如何使用重構技巧去優化程式碼質量達成Clean Code .
重構技巧 — 函式重構
重構的源頭一切從重構函式開始,掌握函式重構技巧是重構過程中很關鍵的一步,接下來我們來探討下函式重構有那些實用技巧。
重新命名函式(Rename Function Name) : Clean Code要求定義的變數和函式名可讀性要強,從名字就可以知道這個變數和函式去做什麼事情,所以好的可讀性強的函式名稱很重要,特別是有助於理解比較複雜的業務邏輯。
移除引數(Remove Parameter): 當函式不再需要某個引數時,要果斷移除,不要為了某個未知需求預留引數,過多的引數會給使用者帶來引數困擾。
將查詢函式和修改函式分離:如果某個函式既返回物件值,又修改物件狀態。這時候應該建立兩個不同的函式,其中一個負責查詢,另一個負責修改。如果查詢函式只是簡單的返回一個值而沒有副作用,就可以無限次的呼叫查詢函式。對於複雜的計算也可以快取結果。
令函式攜帶引數:如果若干函式做了類似的工作,只是少數幾個值不同導致行為略有不同,合併這些函式,以引數來表達不同的值。
以明確函式取代引數:有一個函式其中的邏輯完全取決於引數值而採取不同行為,針對該引數的每一個可能值建立一個單獨的函式。
保持物件完整性:如果你需要從某個物件取若干值,作為函式的多個引數傳進去,特別是需要傳入較多引數比如5個引數或者更多引數時,這種情況建議直接將這個物件直接傳入作為函式引數,這樣既可以減少引數的個數,增加了物件間的信賴性,而且這樣被呼叫者需要這個物件的其他屬性時可以不用人為的再去修改函式引數。
以函式取代引數:物件呼叫某個函式,並將所得結果作為引數傳遞給另外一個函式,而那個函式本身也能夠呼叫前一個函式,直接讓那個函式呼叫就行,可以直接去除那個引數,從而減少引數個數。
引入引數物件:某些引數總是同時出現,新建一個物件取代這些引數,不但可以減少引數個數,而且也許還有一些引數可以遷移到新建的引數類中,增加類的引數擴充套件性。
移除設值函式(Setting Method):如果類中的某個欄位應該在物件建立時賦值,此後就不再改變,這種情景下就不需要新增Setting method。
隱藏函式:如果有一個函式從來沒有被其他類有用到,或者是本來被用到,但隨著類動態新增介面或者需求變更,之後就使用不到了,那麼需要隱藏這個函式,也就是減小作用域。
以工廠函式取代建構函式:如果你希望建立物件時候不僅僅做簡單的構建動作,最顯而易見的動機就是派生子類時根據型別碼建立不同的子類,或者控制類的例項個數。
重構技巧 — 條件表示式
分解條件表示式:如果有一個複雜的條件語句,if/else語句的段落邏輯提取成一個函式。
合併條件表示式:一系列條件測試,都得到相同的測試結果,可以將這些測試表達式合併成成一個,並將合併後的表示式提煉成一個獨立函式,如果這些條件測試是相互獨立不相關的,就不要合併。
合併重複的條件片段:在條件表示式的每個分支上有著相同的一段程式碼,把這段程式碼遷移到表示式之外。
移除控制標記:不必遵循單一出口的原則,不用通過控制標記來決定是否退出迴圈或者跳過函式剩下的操作,直接break或者return。
以衛語句替代巢狀條件表示式:條件表示式通常有兩種表現形式,一:所有分支都屬於正常行為;二:只有一種是正常行為,其他都是不常見的情況。對於一的情況,應該使用if/else條件表示式;對於二這種情況,如果某個條件不常見,應該單獨檢查條件並在該條件為真時立即從函式返回,這樣的單獨檢查常常被稱為衛語句。
以多型取代條件表示式:如果有個條件表示式根據物件型別的不同選擇而選擇不同的行為,將條件表示式的每個分支放進一個子類內的覆寫函式中,將原始函式宣告為抽象函式。
引入Null物件:當執行一些操作時,需要再三檢查某物件是否為NULL,可以專門新建一個NULL物件,讓相應函式執行原來檢查條件為NULL時要執行的動作,除NULL物件外,對特殊情況還可以有Special物件,這類物件一般是Singleton.
引入斷言:程式狀態的一種假設
以MAP取代條件表示式:通過HashMap的Key-Value鍵值對優化條件表示式,條件表示式的判斷條件作為key值,value值儲存條件表示式的返回值。
通過反射取代條件表示式:通過動態反射原理