代碼重構(四):條件表達式重構規則

繼續更新有關重構的博客,前三篇是關於類、函數和數據的重構的博客,內容還算比較充實吧。今天繼續更新,本篇博客的主題是關於條件表達式的重構規則。有時候在實現比較複雜的業務邏輯時,各類條件各類嵌套。若是處理很差的話,代碼看上去會很是的糟糕,並且業務邏輯看上去會很是混亂。今天就經過一些重構規則來對條件表達式進行重構,讓業務邏輯更爲清晰,代碼更以維護和擴展。git

今天博客中的代碼示例依然是Swift班,在對條件表達式重構時也會提現出Swift的優雅之處,會用上Swift特有的語法及其特色,好比使用guard來取代if-let語句等。若是你的需求的業務邏輯及其複雜,那麼妥善處理條件表達式尤其重要。由於對其妥善處理能夠提升代碼的可讀性,以及提升代碼的可維護性。說這麼多仍是來些示例來的直觀,下方會根據一些Demo來着重分享一些條件表達式的部分重構規則,固然今天博客中沒有涵蓋全部的條件表達式的重構規則,更詳細的部分請參見經典的重構書籍。github

今天所分享的代碼段也將會在github上進行分享,分享地址在本篇博文的後方。廢話少說了,進入今天的主題。設計模式

一.Decompose Conditional(分解條件表達式)數組

顧名思義,分解條件表達式說白了,就是當你的條件表達式比較複雜時,你就能夠對其進行拆分。通常拆分的規則爲:經if後的複雜條件表達式進行提取,將其封裝成函數。若是if與else語句塊中的內容比較複雜,那麼就將其提取,也封裝成獨立的函數,而後在相應的地方進行替換。函數

下方代碼段就是咱們將要重構的代碼段。由於本篇博客的主題是對條件表達式的重構,因此咱們要對象下方的if-else的代碼塊進行重構。至於下方代碼片斷中其餘不規範以及須要重構的地方咱們暫且忽略。由於咱們本篇博客的主題是條件表達式的重構。接下來咱們就要對下方代碼片斷中的條件表達式進行分析了。由於下方這段代碼畢竟是一個Demo,在這兒咱們能夠作個假設,假設if後邊的表達式比較複雜,而後在if語句塊和else語句塊中都有一些複雜的處理,代碼看上去的大致樣子以下所示。測試

    

 

基於對上述代碼的結構的假設,接下來咱們將要對其進行重構。說白了,就是讓將條件表達式中的比較複雜的模塊進行拆分與提取。下方代碼段就是咱們重構後的結構,就是將咱們假設比較複雜的模塊進行封裝,而後在條件表達式中使用函數進行替換。這樣的話,在看條件表達式就比較清晰。固然,咱們這個Demo的條件表達式不夠複雜,而且if和else的邏輯塊所作的東西很少。不過咱們能夠假設一下,若是在比較複雜的狀況下,這種重構手法是比較實用的。具體的你們就看重構前與重構後的區別吧。spa

    

 

2、Consolidate Conditional Expression(合併條件表達式)設計

「合併條件表達式」這條規則也是比較好理解的,由於有時候會存在這樣的狀況,也就是一些條件表達式後的語句體執行的代碼塊相同。說白了也就是不一樣的條件有着一樣的返回結果。固然通常在你程序設計之初不會出現此問題,由於在咱們設計程序時,若是不一樣的條件返回相同的結果,咱們確定會將其合併的。不過當你在多個版本迭代,多個需求要增長,或者在別人的代碼上進行需求迭代的時候,該狀況是頗有可能發生的。3d

說這麼多,也許有些抽象,那麼就直接看下方須要重構的Demo了。固然,下方的Demo中,咱們爲了測試,其中的條件比較簡單。咱們假設每一個條件表達式是在不一樣的需求迭代中或者修改bug時添加的,從而形成了下方這種狀況(固然下方的狀況有些誇張,這也是爲了突出要合併條件的狀況)。對象

 

在上述誇張的Demo中一眼就能看出來如何進行重構了(在平常開發迭代中,由於業務邏輯的複雜性或者屢次迭代的緣由,每每不是那麼一目瞭然)。接下來咱們就要對不一樣條件,但返回相同結果的部分進行合併。下方就是咱們合併後的結果,重構手法就是講不一樣的條件表達式使用&&或者||等布爾運算進行合併。

   

合併後,若是條件比較複雜,那麼咱們就可使用本片博客中的第一部分使用的重構規則進行再次重構。下方代碼段是進行第二次重構,就是對比較複雜的表達式進行函數封裝,具體以下所示。仍是那句話,Demo有些誇張,不過用來演示該重構規則也是不錯的,思想就這個思想,具體在平常開發中的使用場景仍是須要進行琢磨和推敲的。

   

 

3、Consolidate Duplicate Conditional Fragments(合併重複的條件片斷)

第二部分合並的是條件表達式,本部分是合併的是重複的條件片斷。什麼叫合併重複的條件片斷呢?這種狀況也是通常不會在設計程序之初所出現,可是隨着時間的推移,項目不斷迭代更新,或者需求變動和迭代更新等等,在項目後期維護時比較容易出現重複的條件片斷。在開發中是比較忌諱重複的代碼的,若是出現重複的代碼,那麼說明你的代碼應該被重構了。   

下方代碼片斷中if與else中有着相同的語句,就是這個print語句。固然這個示例也是比較誇張的,可是足以說明問題。若是你在開發業務邏輯比較複雜的條件表達式時,要謹慎的檢查一下有沒有下方這種狀況。也就是出現了重複的條件片斷。這種狀況在需求迭代或者變動中是及其容易出現的。固然下方只是咱們這兒列舉的一個誇張的示例。

   

對於這個示例而言,咱們不難看出,去代碼的重複化。將print語句移到條件以外。可是要學會觸類旁通呢,重要的是重構手法和思想。在真正的項目中,若是你要提取重複的代碼段通常還要結合着其餘重構手法,好比將重複的部分先提取成一個獨立的模塊(獨立的類或者方法),而後在條件中使用,最後再去重複話。這樣一來,重構的思路就比較清晰了。雖然今天的示例比較簡單,可是足以表達這個思路。下方是重構後的代碼。若是你對下方代碼看着不爽的話,徹底能夠根據以前咱們介紹的重構手法「使用查詢來替代臨時變量」,將下方的代碼繼續重構,在本章博客中就不作過多介紹了。

    

 

4、Remove Control Flag(移除控制標記)

「移除控制標記」這一點仍是比較重要的,我平時在代碼開發中有時候也會使用到標記變量,來標記一些事物的狀態。使用標記變量最直觀的感覺就是不易維護,不易理解。由於在需求變動或者迭代中,你還得維護這標記變量。若是維護一個標記變量簡單的話,那麼維護多個標記變量就沒這麼容易了。並且在你的程序中使用標記變量時,不易理解,而且會顯得邏輯混亂。固然這是個人直觀感覺,在寫程序時,我儘可能會避免使用標記變量。

固然,下方又是一個有點誇張的例子,可是該例子能夠說明問題。下方代碼中咱們使用了一個flag標記變量,固然下方代碼沒有什麼意義了。在平時開發中咱們會使用一些標記變量來標記一個或者一些數據的狀態,或者一些控件的狀態,再次爲了簡化示例,咱們就簡單的引入了一個flag標記變量。下方代碼不難理解,當i爲20時,咱們就翻轉標記變量的狀態,而後if中的語句塊就不被執行了。

雖然下方代碼片斷是我寫的,可是我我的看着超級的不舒服。引入的這個flag增長了代碼的邏輯複雜度,讓代碼變得不那麼直觀。我我的建議,在平時開發中儘可能的要少使用標記變量。不到萬不得已,不要在你的代碼中引入標記變量。若是有,嘗試着去除標記變量。

   

標記變量通常是可使用其餘語句進行替換的,可使用break、return、continue等等,這個要根據具體狀況而定。總之,代碼中有標記變量不是什麼好的事情。下方代碼段就是對上述代碼去除標記變量的重構。重構後的代碼以下所示,固然還有好多其餘去除的方法,此處僅僅給出了一種。

   

 

5、Replace Nested Condition with Guard Clauses(以衛語句取代嵌套的條件)

條件表達式的嵌套是使人討厭的東西。代碼中有多層if-else嵌套會下降代碼的可讀性以及可維護性,若是此時在加上for循環等等其餘邏輯語句,想一想均可怕。這種業務邏輯較強的代碼要慎重對待。儘可能不要將if-else進行嵌套,由於嵌套的if-else確實很差理解,若是在出現bug時,更是很差定位bug。要記住,你寫的代碼不是給機器看的,而是給人看的,這一點很是重要。不光是代碼編寫規範,也儘可能不要使用理解起來比較費勁的語句來實現你的邏輯。

下方咱們將建立一種場景,人爲的建立多個if嵌套的狀況。下方的demo理解起來應該不難,第一個數組中存儲的是第二個字典的key,第二個字典中存儲的value是下一個字典也就是第三個字典的key,以此類推。將咱們在使用從相應的字典中取出的value作爲key再次取值時,咱們要保證該值不爲nil,因此咱們要進行if-let判斷。if-let所表示的意思是在取值時,若是當前取出的值不爲nil,那麼就執行if後的語句體,若是爲nil,那麼就不執行。這樣一來,就會出現多層if-let嵌套的狀況。

固然,在一些業務邏輯比較複雜的需求中,嵌套的每層if後都跟着不一樣的表達式,而不只僅是if-let。由於爲了建立這個if嵌套的場景,再次咱們使用了if-let嵌套。這麼多的if-let嵌套顯然不是什麼好的事情,因此咱們要對此重構。

   

若是多層if嵌套,會出現一種叫作「厄運金字塔」的現象,由於在if左邊會出現一個三角號的空間。這可不是什麼好的標誌,這樣的代碼結構通常理解起來會比較困難,維護起來也不是那麼的理想。因此下方咱們要對上述代碼進行結構。要去除上面的嵌套模式,咱們能夠將if後的條件進行翻轉,根據具體需求再引入return、break、continue等衛語句。下方是講條件進行翻轉而後引入了continue語句,代碼以下:

   

該部分的第二段代碼要比第一段代碼容易理解的多。通過條件翻轉+continue,將上述嵌套的條件語句進行了拆分。拆分紅了三個獨立的if語句,雖然代碼結構不一樣,可是其實現功能都是同樣的。不過上面的解決方案在Swift中並不完美。由於Swift語言是很是優雅的,Swift語言在設計的時候就考慮到了這種狀況,因此在Swift 2.0時推出了guard語句。在這種狀況下使用guard語句再合適不過了,下方代碼段就是使用guard語句進行了重構。

使用guard let聲明的變量與guard自己同在一個做用域,也就是說下方代碼在guard let中聲明的變量能夠在for循環中直接使用。guard語句的用法就是若是guard 後方的賦值語句所取出的值爲nil,那麼就會執行else中的語句,不然就會繼續往下執行。在else中通常是break、return、continue等衛語句。這種語法形式很好的對上述糟糕的形式進行了解決,並且還易於理解。

   

 

6、Replace Condition with Polymorphism(以多態取代條件表達式)

在介紹「以多態取代條件表達式」以前呢,首先要理解面向對象中多態是什麼,也就是說多態是幹嗎的。顧明思議,多態就是類的不一樣類型的對象有着不一樣的行爲狀態。若是在你的條件表達式中條件是對象的類型,也就是根據對象的不一樣類型而後作不一樣的事情。在這種狀況下使用多態在合適不過了。若是該部分在設計模式中,應該對應着狀態模式這一部分。這就是以多態來取代條件表達式。

下方是一個比較簡單的示例,這也正是咱們要進行重構的示例。在Book類中有三中類型,也就是咱們的書有三種,具體每種書是什麼這不是該示例的重點。在Book類實例化時,須要爲書的對象指定該書的類型(三種類型中的一種)。在Book類中,還有一個核心方法,那就是計算書的價格。在charge()函數中,根據不一樣的書的種類,給出了不一樣的價格。固然在Switch中的分支的計算方法在本例中很是簡單,可是咱們要假設每一個分支的計算很是複雜,並且有着多行代碼

在這種假設的狀況下,下方的條件語句是很是糟糕的,由於龐大的業務邏輯增長了代碼維護的成本。在這種狀況下咱們就可使用多態來取代複雜的條件表達式。

     

 

 

若是想使用多態,引入其餘類是必不可少的,並且每一個類中也必須有相應的對應關係。「以多態取代條件表達式」的作法的本質是將不一樣狀態的業務邏輯的處理的代碼移到相應的類中。在本示例中,咱們要建立三種書籍的價格類,而且將上述case中的「複雜」計算移入到相應的書籍類中。由於每一個書籍價格中都會有相應的計算方法,也就是charge()方法,因此咱們爲這三個書籍價格定義了一個協議(接口或者抽象類),在協議中就給出了charge()函數。而後咱們就能夠將不一樣種類的書籍實現該協議,在相應的方法中給出價格計算的代碼。具體作法以下所示:

    

引入上述幾個類後,在咱們的Book中就可使用多態了。在Book類中添加了一個price字段,這個字段的類型就是咱們的Price協議。也就是隻要是符合咱們的Price協議的對象均可以。而後在Book中也添加了一個charge()方法,在Book中的charge方法作的一件事情就是調用price對象的charge方法。關鍵的是根據不一樣的書籍類型建立不一樣的書籍價格對象。這樣一來,咱們就把每一個分支中的業務邏輯進行了分離,並使用了多態來獲取價格。重構後的優勢不言而喻。

   

 

今天關於「條件表達式的重構」的規則,固然這不是所有的,只是列舉了一些常見的,並且常用重構規則。篇幅有限,今天的博客就先到這兒,還會繼續更新其餘的重構規則。

今天博客中的Demo在github上的分享地址爲:https://github.com/lizelu/CodeRefactoring-Swift

相關文章
相關標籤/搜索