高效重構 C++ 代碼

引言

Martin Fowler的《重構:改善既有代碼的設計》一書從2003年問世至今已有十幾年時間了,按照計算機領域突飛猛進的變化速度,重構已經算是一門陳舊的技術了。可是陳舊並不表明不重要,偏偏隨着演進式設計被愈來愈普遍的使用,重構技術已經被認爲是現代軟件開發中的一項必備的基本技能!因此今天在任何軟件開發團隊中,你都會不時聽到或看到和重構相關的代碼活動。然而對於這樣一種被認爲應該是如同「軟件開發中的空氣和水」同樣的技術,在現實中卻比比皆見對重構的錯誤理解和應用。首先是不知道重構使用的正確場合,老是等到代碼已經腐化到積重難返的時候纔想起重構;其次面對一堆的代碼壞味道沒有選擇標準、無從下手;接下來修改代碼的過程當中不懂得安全、小步的重構手法,老是大刀闊斧地將代碼置於危險的境地,很難再收回來;最後要麼構建、測試失敗後沒法恢復只能推到重來,或者最終結果只是將代碼從一種壞味道修改到了另外一種壞味道!編程

總結以上問題,一部分緣由是由於沒有正確的理解重構,不知道重構的起點和目標,對重構的對象和目標沒有衡量和比較的標準;其次是由於沒有掌握形式化的重構手法和步驟,重構過程每每只是跟着感受走;最後實踐重構的過程當中,沒有先理順本身的開發、構建和測試環境,致使重構成本很高! 對於開發、構建和測試環境的問題,C/C++領域尤爲嚴重,除了沒有像Java領域那麼好用的自動化重構工具,不少開發人員連一個好用的IDE都找不到,更不要說廣泛認知的構建速度慢,自動化測試匱乏等等問題!安全

本文站在做者學習和實踐重構的基礎上,爲你們梳理重構技術,帶領你們從新認識重構的目標和起點,重構手法背後的原理以及實踐方式。最後介紹在實踐中高效實施C/C++重構的經驗、技巧和工具。架構

什麼是重構?

重構的定義

Martin Fowler在《重構:改善既有代碼的設計》一書中給出了重構的兩個定義.編程語言

第一個是名詞形式:分佈式

Refactoring: 對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本.函數

第二個是動詞形式:工具

Refactor: 使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構.學習

重構的目標

重構的目標是什麼? 重構的目標毫不是將代碼從別人的taste改爲本身的taste,也不是將代碼從一種壞味道改到另外一種壞味道!測試

Matin Fowler利用上面兩個定義,指出了重構的目標:編碼

  • 不改變軟件可觀察行爲

  • 提升軟件可理解性

  • 下降軟件修改爲本

而對於上述目標,咱們再深刻一點分析,發現其實已經有更經典的定義. 那就是Kent Beck的簡單設計四原則:

  • Pass All Test: 經過所有測試;

  • No Duplication: 沒有重複(DRY)

  • Reveals Intent: 程序表達意圖,易於理解

  • Has no superfluous parts: 沒有冗餘,或者YAGNI原則

上述四條的重要程度依次下降.

到目前爲止,簡單設計四原則是對」什麼是好的軟件設計」最好的定義!

簡單設計四原則第一條定義好的軟件首先應該經過全部測試,即正確知足全部功能需求.而重構的目標中最基本的就是」不改變軟件的可觀察行爲」,也就是說:

1) 重構後的軟件不能破壞原來全部測試!

Matin定義的重構的其它兩條目標,對應了簡單設計原則的第2和第3條:

2) 重構應該消除重複: 下降軟件修改爲本;

3) 重構應該讓程序顯示錶達意圖: 提升軟件可理解性;

最後,咱們把簡單設計四原則的最後一條也加入重構的目標:

4) 重構應該消除冗餘:下降軟件沒必要要的複雜度.

因此之後當咱們再來討論重構的目標,或者評判重構有沒有收益的時候,就用簡單設計四原則來衡量它.

從哪裏開始?

對於重構的目標達成一致後,咱們回到起點:什麼樣的軟件須要重構? 以及何時進行重構?

對於第一個問題,因爲咱們重構的目標是使軟件知足簡單設計四原則,那麼任何違反簡單設計四原則的代碼都應該是咱們重構的目標.例如1)代碼很容易出現bug,致使測試失敗! 或者 2)代碼存在知識重複使得不易修改! 或者 3)代碼寫的晦澀很是難以理解! 或者 4)代碼存在過分設計,存在冗餘致使複雜!

現實中可能有一堆的代碼問題等待咱們解決,而時間、成本、人力是有限的,因此咱們須要從最有價值,最沒有爭議的部分開始重構. 因爲簡單設計四原則的重要程度是依次下降的,對於四條原則的斷定從上往下也是逐漸主觀化,因此咱們選擇重構的代碼的優先級順序也是按照它們破壞簡單四原則的順序依次下降! 若是一坨代碼存在不少重複,另一坨代碼不易理解,那麼咱們優先選擇去解決重複代碼的問題,由於按照簡單四原則消除重複更重要,也更容易被客觀評價.

在《重構》一書中Martin爲了不引發所謂編程美學的含混爭辯,總結了代碼的22條壞味道. 在實踐中咱們通常都是從某一代碼壞味道着手重構的,可是對於優先重構哪一個壞味道,咱們遵照上面描述的原則.

對於進行重構的時機,Matin給出:

  • 重複地作某一件事情的時候 (三次法則)

  • 添加新功能的時候

  • 修改Bug的時候

  • Code Review的時候

事實上在個人工做過程當中,重構是隨時隨地進行的. 尤爲對於採用演進式設計方法論,重構和代碼開發是緊密結合難以分割的,甚至不少時候只有依託重構才能完成代碼的開發.

重構的手法

明白了起點和目標,下來最重要的就是掌握完成這一過程的手段! 而重構的手法則是帶領咱們正確到達目標的工具.

不少人認爲學習重構只要掌握背後的思想就足夠了,其詳細繁瑣的操做手法並不重要.因而乎現實中咱們看到不少人在實際操做重構的過程當中章法全無,一旦開始半天停不下來,代碼不少時候處於不可編譯或者測試不能經過的狀態,有時改的出錯了很難再使代碼回到初始狀態,只能推倒重來! 實際上重構是一項很是實踐性的技術,可以正確合理地使用重構操做,安全地,小步地,高效地完成代碼修改,是評價重構能力的核心標準.

那麼什麼纔是正確的重構手法?

Martin對重構的第二個定義中提到使用一系列的重構手法,可是對這一系列的重構手法卻沒有歸納.

而William Opdyke在他的論文」Refactoring Objected-Oriented Frameworks」裏面對重構給出了以下定義:

重構:行爲保持(Behavior Preservation)的程序重建和程序變換.

在論文裏面將重構手法定義爲一些程序重建或者程序變換的操做,這些操做知足行爲保持(Behavior Preservation)的要求. 論文裏面對行爲保持的定義以下:

Behavior Preservation : For the same set of input values,the resulting set of output values should be the same before and after the refactoring.

也就是說存在一系列代碼變換的操做,應用這些操做以後,在相同的輸入條件下,軟件的輸出不會發生變化. 咱們把知足上述要求的代碼操做稱之爲代碼等價變換操做. 在William Opdyke的論文中針對C++提出了26種低層次的代碼等價變換操做(例如: 重命名變量,爲函數增長一個參數,刪除一個不被引用的類…). 按照必定設計好的順序組合上述低層次的代碼等價變換操做,咱們能夠完成一次安全的代碼重構,保證代碼重構先後的行爲保持要求.

這裏代碼等價變換的過程. 相似於初等數學中的多項式變換.例如對於以下公式變化:

每一步咱們運用一次多項式等價變換公式,一步一步地對多項式進行化簡,每次變換先後多項式保持等價關係.

在多項式化簡的這個例子中,承載簡化過程的是已經被數學證實過的多項式等價變換的公式. 同理承載重構的則是被證實過的一個個表明代碼等價變換操做的重構手法.

另外,因爲完成一項重構須要使用一系列的重構手法,這些手法的使用順序也是相當重要的!

咱們學習重構,就是要來學習每種場景下所使用的小步安全的重構手法及其使用順序,並不斷加以練習! 可以靈活而流暢地使用一系列重構手法完成一項重構,是衡量重構能力的一個很是重要的指標.

而本文後面的一個重點就是對經常使用的重構手法以及運用順序進行提煉,下降你們的學習難度.

最後,既然重構中使用的是安全小步的代碼等價變換手法,爲何咱們還須要測試? 首先是由於咱們是人,咱們總會犯錯! 另外因爲編程語言的複雜性致使所謂的等價變換是受上下文約束的,例如在C++中爲一個存在繼承關係的類的成員方法重命名,有可能致使新的方法名和它某一父類中有默認實現的虛方法重名,而即便編譯器也不能發現該錯誤.

高效地重構

雖然咱們瞭解瞭如何/什麼時候開始,目標,以及重構的手法,可是若是咱們有了下面這些因素的輔助,會讓咱們更加安全和高效.

  • 覆蓋良好高效的自動化測試

  • 合適的IDE,最好提供基本的自動化重構菜單

  • 良好的工程設置

  • 高效的構建環境

  • 良好的編碼習慣

對於上面這些,不一樣語言面臨的現狀不一樣,針對C++語言咱們後面會專門總結.

哪些不是重構?

針對上面的討論,咱們站在嚴格的重構定義上來看看下面這些反模式:

  • 「我把bug重構掉了!」

  • 「Debug一下剛纔的重構那裏出錯了」

  • 「昨晚重構出來的Bug到如今尚未查出來」

  • 「先把代碼重構好,再看測試爲啥不過」

  • 「我把軟件架構由集中式重構成分佈式了」

想一想上面的場景哪裏存在問題?

在實際的開發過程當中,咱們還常常面臨另一種場景,那就是對某一已經開發完成的軟件模塊進行總體重構. 在這樣的過程當中,雖然也存在頻繁地使用重構手法對原有模塊代碼進行修改,可是更多的是進行大量的架構和設計方案上的修改.爲了與咱們要討論的重構進行區分,對於這樣的過程,咱們稱其爲reengineering(軟件重建).

軟件重建通常是站在以前開發、測試的基礎上,伴隨着對軟件要解決的問題和解決方式自己有了更深刻的理解,經過修改軟件把這些學習成果反映到軟件的結構中去,使得軟件能夠更好、更精煉的解決業務問題。站在DDD(領域驅動設計)的角度,軟件重建通常是對領域模型的進一步精練,使得軟件更加貼合業務的本質!雖然成功的軟件重建每每能對組織帶來較大的收益,可是因爲軟件重建的開銷廣泛較大,而軟件開發又是一項商業活動,因此須要對軟件重建謹慎評估其成本收益比以及過程風險後才能決定是否啓動。而本文中的重構技術,則只是一項平常編碼中頻繁使用的安全、高效的代碼修改技術,被廣泛認爲是現代軟件開發技術中必備的一項基本技能,是演進式軟件設計或者軟件重建目標達成的一項必要手段!

關於本文

咱們總結一下,重構有三個要點,見下圖:

 

  1. 你要有一個敏感的鼻子,可以嗅出代碼中的壞味道; 通常只要發現不符合簡單設計四原則的Code,就是咱們須要重構的目標對象. 而Martin總結的22條代碼壞味道給咱們一個很好的實踐起點.

  2. 你要知道重構的目標,就是讓代碼逐漸靠近簡單設計四原則.

  3. 須要掌握小的安全的重構手法,以及在不一樣場景下合理的使用順序,以便安全高效地承載重構目標的達成.

 

因爲重構手法和實施順序是學習重構的關鍵,因此本文後面會重點講述這個主題. 另外,在實踐中如何高效和安全的進行重構,和具體使用的編程語言及其開發、構建、測試環境關係也很密切.本文最後會針對C++語言總結這方面相關問題.

相關文章
相關標籤/搜索