代碼重構(一):函數重構規則


重構是項目做到 一定程度后必然要做的事情。代碼重構,可以改善既有的代碼設計,增強既有工程的可擴充、可維護性。隨着項目需求的不斷迭代,需求的不斷更新,我們在項目中 所寫的代碼也在時時刻刻的在變化之中。在一次新的需求中,你添加了某些功能模塊,但這些功能模塊有可能在下一次需求中不在適用。或者你因為需求迭代與變 更,使你原有的方法或者類變得臃腫,以及各個模塊或者層次之間耦合度增加。此時,你要考慮重構了。

 

重構,在《重構,改善既有代碼的設計》這本經典的書中給出了定義,大概就是:在不改變代碼對外的表現的情況下,修改代碼的內部特征。說白了,就是我們的測試用例不變,然后我們對既有的代碼的結構進行修改。重構在軟件開發中是經常遇到的,也是非常重要的。在需求迭代,Debug,Code Review時,你都可以對你既有的代碼進行重構。

 

在接下來的幾篇博文中,我想與大家一塊去窺探一下代碼重構的美麗,學習一下代碼重構的一些規則。當然在每個規則中都有小的Demo, 在本篇博客以及相關內容的博客是使用Swift語言實現的。當然,和設計模式相同,重構重要的是手法和思想,和使用什么樣的語言關系不大。經典的重構書籍中是使用Java語言來實現的,如果你對PHP, Python等其他語言比較熟悉,完全可以使用這些語言來測試一些重構手法。

 

本篇博客的主題就是通過一些列的重構手法,對既有的需要重構的函數或者方法進行重 構。並且會將每個示例在GitHub上進行分享,感興趣的小伙伴可以對其進行下載。有的小伙伴說了,我沒有Mac,怎么對你寫的Swift代碼進行編譯 呢?這個問題好解決,你可以看我之前發表的這篇博客窺探Swift之使用Web瀏覽器編譯Swift代碼以及Swift中的泛型。你可以將相關代碼進行拷貝,在瀏覽器中觀察結果。因為在線編譯的網站是國外的,訪問起來也許會有一些卡頓,不過是可以用的。好前面扯了這么多了,進入今天的主題。

目錄:

一、將大函數按模塊拆分成幾個小的函數

二、將微不足道的小函數通過inline進行整合

三、將一些臨時變量使用函數替換(以函數查詢替代臨時變量)

四、將復雜表達式拆分成多個表達式(以多個解釋性臨時變量拆分復雜表達式)

五、將在不同語義下具有不同含義的臨時行變量進行拆分

六、移除對函數參數的賦值(引入另一個臨時性變量)

七、以對象取代函數

 

一、Extract Method(提取函數)-------將大函數按模塊拆分成幾個小的函數

Extract Method被翻 譯成中文就是提取函數的意思,這一點在代碼重構中用的非常非常的多。在重構時提倡將代碼模塊進行細分,因為模塊越小,可重用度就越大。不要寫大函數,如果 你的函數過大,那么這意味着你的函數需要重構了。因為函數過大,可維護性,可理解性就會變差。並且當你實現類似功能的時候就容易產生重復代碼。寫代碼時, 最忌諱的就是代碼重復。這也就是經常所說的DRY(Don`t Repeat Yourself)原則。所以當函數過長時,你需要將其細分,將原函數拆分成幾個函數。

 

下 方將會通過一個示例來直觀的感受一下Extract Method,當然這些示例不是我原創的,是《重構:改善既有代碼的設計》中Java示例演變的Swift版,在寫Swift代碼時,對原有的示例進行了 一些修改,算是偽原創吧。不過目的只有一個:希望與大家交流分享。實在是沒有必要再找其他的例子說明這些重構規則,因為《重構:改善既有的代碼的設計》這 本書真的是太經典了。

 

1.需要重構的代碼如下所示。下方代碼中的MyCustomer類中有兩個常量屬性,並且該類提供了一個構造器。該類還提供了一個輸出方法,就是第一該類中的屬性進行打印說明,其實該類中沒有什么功能。

  

 

在寫好需要重構的類后,我們要為該類寫一個測試用例。這便於在我們重構時對重構的 正確性進行驗證,因為每次重構后都要去執行該測試用例,以保證我們重構是正確的。下方截圖就是為上方示例寫的測試用例以及該測試用例的打印結果。當然重構 后我們也需要調用該測試用例,並觀察打印結果是否與之前的一致。當然如果你不想自己觀察,你可以為上面的類添加相應的單元測試,這也是在常規項目中經常使 用的。至於如果添加測試用例,我們會在后面的博客中進行詳細介紹。下方就是上述類的測試用例和輸出結果:

      

 

2.接下來我們對上面類中的printOwning函數進行分析。上述類可以正常 工作,這是肯定的。但是printOwning()函數寫的不夠好。因為它干了太多的事情,也就是說該函數包括了其他子模塊,需要對其進行拆分。由上面截 圖中的紅框可以看出,每個紅框就代表着一個獨立的功能模塊,就說明這一塊代碼可以被拆分成獨立的函數。在拆分子函數時,我們要為該函數起一個與改代碼塊功 能相符合的名字。也就是說當你看到該函數名字時,你就應該知道該函數是干嘛的。

 

下方代碼段就是我們重構后的類。說白的,就是對函數中可以進行獨立的模塊進行提 取,並為提取的新的函數命一個合適的名稱。經過重構后printOwing()函數就只有兩行代碼,這樣看其中調用的函數名也是很容易理解其作用的。下方 拆分出來的三個函數也是一個獨立的模塊,因為函數短小,所以易於理解,同時也易於重用。經過Extract Method,當然好處是多多的。經過重構后的代碼,我在調用上述的測試用例,輸出結果和原代碼是一直的,如果不一致的話,那么說明你的重構有問題呢,需 要你進行Debug。

     

 

 

二. Inline Method ---- 內聯函數:將微不足道的小函數進行整合

看過《周易》的小伙伴應該都知道,《周易》所表達的思想有一點就是“物極必反”。 《周易》中的六十四卦中的每一卦的“上九”(第六位的陽爻)或者“上六”(第六位的陰爻)都是物極必反的表現。其實《周易》其實就是計算機科學中二進制的 表象,因為太極生兩儀(2進制中的2),兩儀生四象(2的平方為4),四象生八卦(4 x 2 = 8),八卦有演變出六十四卦。六十四卦的就是2進制中的0-1排列。九五至尊,九六就物極必反了。wo kao, 扯遠了,言歸正傳,當然這提到《周易》不是說利用周易如何去算卦,如何去預測,本寶寶不信這東西。不過《周易》中的哲學還是很有必要學習一下的。有所取, 有所不取。

 

回到本博客的主題,Inline Method其實是和Extract Method相對的。當你在重構或者平時編程時,對模塊進行了過度的封裝,也就是使用Extract Method有點過頭了,把過於簡單的東西進行了封裝,比如一個簡單的布爾表達式,而且該表達式只被用過一次。此時就是過度的使用Extract Method的表現了。物極必反,所以我們需要使用Inline Method進行中和,將過度封裝的函數在放回去,或者將那些沒有必要封裝的函數放回去。也就是Extract Method相反的做法。

至於Inline Method規則的示例呢,在此就不做過多的贅述了,因為只需要你將Extract Method的示例進行翻轉即可。

 

三.Replace Temp with Query----以查詢取代臨時變量: 將一些臨時變量使用函數替代

1.Replace Temp with Query說白了就是將那些有着復雜表達式賦值並且多次使用的臨時變量使用查詢函數取代,也就是說該臨時變量的值是通過函數的返回值來獲取的。這樣一來在 實現類似功能的函數時,這些復雜的臨時變量就可以進行復用,從而減少代碼的重復率。下方就是Replace Temp with Query規則的一個特定Demo,接下來我們要對getPrice()函數使用Replace Temp with Query規則進行重構。

  

 

 

對上面的小的demo創建對應的測試用例是少不了的,因為我們要根據測試用例還測試我重構后的代碼是否一致,下方截圖就是該代碼的測試用例以及輸出結果,具體如下所示。

   

 

2.接下來就是對Procut類中的getPrice()函數進行分析並重構了。在getPrice()函數中的第一個紅框中有一個 basePrice臨時常量,該常量有一個較為復雜的賦值表達式,我們可以對其使用Replace Temp with Query進行重構,可就是創建一個函數來返回該表達式的值。第二個紅框中的discountFactor臨時變量被多次使用,我們可以對其通過 Replace Temp with Query規則進行重構,具體重構后的代碼如下所示。

由重構后的代碼容易看出,上面我們提到的臨時常量或者變量都不存在了,取而代之的是兩個查詢方法,對應的查詢方法返回的就是之前消除的臨時變量或常量的值。

   

 

四、Inline Temp ---內聯臨時變量:與上面的Replace Temp with Query相反

當臨時變量只被一個簡單的表達式賦值,而該臨時變量妨礙了其他重構手法。此時我們就不應 該使用Replace Temp with Query。之所以有時我們會使用到Inline Temp規則,是因為Replace Temp with Query規則使用過度造成的情況,還是物極必反,使用Replace Temp with Query過度時,就需要使用Inline Temp進行修正,當然Inline Temp的示例與Replace Temp with Query正好相反,在此就不做過多的贅述了。

 

五、Introduce Explaining Variable---引入解釋性變量:將復雜的表達式拆分成多個變量

當一個函數中有一個比較復雜的表達式時,我們可以將 表達式根據功能拆分成不同的變量。拆分后的表達式要比之前未拆分的表達式的可讀性更高。將表達式拆分成相應的臨時變量,也就是Introduce Explaining Variable,如果臨時變量被多次使用的話,我們還可以嘗試着使用Replace Temp with Query規則去除臨時變量,也就是將臨時變量換成查詢函數。

1.在下方Product類中的getPrice()方法中返回了一個比較長的表達式,第一眼看這個函數感覺會非常的不舒服。因為它返回的表達式太長了,而且可讀性不太好。在這種情況下就很有必要將該表達式進行拆分。

   

 

2.接下來就可以使用Introduce Explaining Variable規則,引入解釋性變量。顧名思義,我們引入的變量是為了解釋該表達式中的一部分的功能的,目的在於讓該表達式具有更好的可讀性。使用 Introduce Explaining Variable規則,就相當於為該表達式添加上相應的注釋。下方截圖就是使用 Introduce Explaining Variable規則進行重構后的結果。

   

 

3.引入臨時變量是為了更好的可讀性,如果臨時變量所代表的表達式多次使用,我們就可以對上述函數在此使用Replace Temp with Query規則進行重構。也就是去除經常使用而且表達式比較復雜的臨時變量,下方代碼段是對上述函數進行Replace Temp with Query重構,去掉臨時變量,再次重構后的結果如下所示。

    

 

六、Split Temporary Variable-----分解臨時變量:一心不可二用

什么叫分解臨時變量的,具體說來就是在一個函數中一個臨時變量不能做兩種事 情,也就是一個臨時變量不能賦上不同意義的值。如果你這么做了,那么對不起,請對該重復使用的臨時變量進行分解,也就是說你需要創建一個新的臨時變量來接 收第二次分配給第一個臨時變量的值,並為第二個臨時變量命一個確切的名字。

下方第一個函數是重構前的,可以看出temp被重復的賦值了兩次的值,如果 這兩個值關系不大,而且temp不足以對兩個值的意思進行說明。那么就說明該段代碼就應該被重構了。當然,重構的做法也是非常簡單的,只需要術業有專攻即 可,各司其職,並且為每個臨時變量命一個合適的名字即可。具體做法如下所示。

 

 

 七、Remove Assignments to Parameters----移除對參數的賦值

“移除對參數的賦值”是什么意思呢?顧名思義,就是在函數中不要對函數參數 進行賦值。也就是說你在函數的作用域中不要對函數的參數進行賦值(當然,輸入輸出參數除外),當直接對函數的參數進行修改時,對不起,此時你應該對此重 構。因為這樣會是參數的原始值丟失,我們需要引入臨時變量,然后對這個臨時變量進行操作。

1.下方這個discount()函數就做的不好,因為在 discount()函數中直接對非inout參數inputVal進行了修改並且返回了,我們不建議這樣做。遇到這種情況,我們需要使用Remove Assignments to Parameters規則對下方的函數進行重構。

   

  

2.當然重構的手法也特別簡單,就是需要將上面的inputVal使用函數的臨時變量進行替代即可,下方就是重構后的函數。

   

 

八.Replace Method with Method Object----以函數對象取代函數

當一個特別長的函數,而且函數中含有比較復雜的臨時變量,使用上述那些方法 不好進行重構時,我們就要考慮將該函數封裝成一個類了。這個對應的類的對象就是函數對象。我們可以將該場函數中的參數以及臨時變量轉變成類的屬性,函數要 做的事情作為類的方法。將函數轉變成函數類后,我們就可以使用上述的某些方法對新的類中的函數進行重構了。具體做法請看下方示例。

1.下方示例中的discount函數有過多的參數(當然,現實項目工程中參數比這個還要多),並函數中含有多個臨時變量,假設函數功能比較復雜,而且比較長。下方示例對該函數使用上述那些規則進行重構會比較復雜,此時我們就可以將該函數抽象成一個類。

   

 

2.重構的第一步就是將上述discount()函數抽象成Discount類。在Discount類中有六個屬性,這六個屬性分別對應着 discount()函數的不同參數。除了添加參數屬性外,我們在函數類提取時還添加了一個Account的委托代理對象。該委托代理對象是為了在 Discount類中訪問Account類中依賴的數據,下方是第一次重構后的代碼。

   

 

3.接着,我們就可以在新的Discount類中的compute()方法中使用 我們上述介紹的規則進行重構。對compute()方法進行分析,我們發現importandValue等屬性是可以通過Replace Temp with Qurey 規則進行消除的。所為我們可以再次對上述方法進行重構,重構后的具體代碼如下:

     

 

今天的博客主要講了如何對既有代碼中的函數進行重構,在本篇博客中提到了8大規 則。這8大規則在函數代碼重構時時非常實用的,並且也是非常重要的。還是那句話,雖然代碼是使用Swift語言實現的,但是代碼重構的手法和思想和語言無 關。接下來還會繼續更新關於代碼重構的博客,敬請期待吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM