程式碼的壞味道「上」

周小帥發表於2019-09-06

決定何時重構及何時停止和知道重構機制如何運轉一樣重要!\
必須培養自己的判斷力,學會判斷一個類內有多少例項變數算是太大,一個函式內有多少行程式碼才算太長。

1. 神秘命名

整潔程式碼最重要的一環就是好的名字,所有我們會深思熟慮如何給函式,模組,變數和類命名,是他們能清晰的表明自己的功能和用法;

  • 好的名字能節省未來用在猜謎上的大把時間;
  • 如果想不出一個好名字,說明背後很可能潛藏著更深的設計問題;
  • 可以採用 「改變函式命名」,「變數改名」,「欄位改名」等方法;

2. 重複程式碼

  • 如果在一個以上的地點看到相同的程式碼結構,那麼可以肯定:設法將它們合而為一,程式會變得更好;

  • 一旦有重複的程式碼存在,閱讀這些重複的程式碼時你就必須加倍仔細,留意期間細微的差異,。如果要修改重複程式碼,必須找出所有的副本來修改;

  • 可以採用

    • 「提煉函式」提煉出重複的程式碼,然後讓這兩個地方都呼叫被提煉出來的一段程式碼;
    • 「移動語句」:如果重複程式碼只是相似而不是完全相同,首先使用 移動語句的方法,重組程式碼順序,把相似的部分放在一起以便提煉;
    • 「函式上移」:如果重複程式碼短位於同一個超類的不同子類中,可以使用 函式上移 來避免在兩個子類之間互相呼叫;

3.過長函式

活得最長,最好的程式,其中的函式都比較短;

  • 拆分成簡短的函式的價值在於:間接性帶來的好處--- 更好的闡釋力,更易於分享,更多的喧選擇;

函式越長,越難理解;讓小函式易於理解的關鍵在於良好的命名;

  • 如果可以給函式起一個好名字,閱讀程式碼的人就可以透過名字瞭解函式的作用,根本不必去看其中寫了什麼;

  • 最終的效果時:應該更積極的分解函式,遵循一條原則:每當感覺需要以註釋來說明點什麼的時候,就把需要說明的東西寫進一個獨立的函式中,並以其作用命名,而不是實現手法命名;

  • 重構方法:

    • 要把函式變短,只需使用 「提煉函式」,找到函式中適合集中在一起的部分,將他們提煉出來形成一個新函式;
    • 在提煉函式時,如果把許多引數傳遞給被提煉出來的新函式中,導致可讀性沒有任何提升,此時,可以運用 「以查詢取代臨時變數」 來消除這些臨時變數;
    • 「引入引數物件」,「保持物件完整」 可以將過長的引數裂變變得更簡潔;

    如何確定該提煉哪一段程式碼呢?一個技巧時:尋找註釋;\
    如果程式碼前方有一行註釋,則可以將這段程式碼替換成一個函式,而且可以在註釋的基礎上給這個函式命名。

    • 條件表示式的提煉:可以使用 「分解表示式」 處理條件表示式,對於龐大的 switch 語句給予同一個條件進行分支選擇,就應該使用 「以多型取代表示式」;
    • 迴圈的提煉:應該將迴圈和迴圈內的程式碼提煉到一個獨立的函式中,如果發現提煉出的函式很難命名,可能是因為其中做了幾件不同的事,如果是這種情況,請使用 「拆分迴圈」 將其拆分成各自獨立的任務;

4. 過長引數列表

  • 如果可以向某個引數發起查詢而獲得另一個引數的值,就可以使用 「以查詢取代引數」 去掉第二個引數;

5. 全域性資料

  • 全域性資料的問題在於:從程式碼的任何一個角落都可以修改它,而且沒有任何機制可以探測出到底哪段程式碼做出來修改;
  • 全域性函式最顯而易見的形式及時全域性變數,類變數和單利也有這樣的問題
  • 重構方法:
    • 首要的防禦手段就是 「封裝變數」 每當看到可能被各處的程式碼汙染的資料,這總是我們應對的第一招;把全域性資料用一個函式包裝起來,就可以控制對它的訪問;

6. 可變資料

對資料的修改經常導致出乎意料的結果和難以發現的 bug;

  • 如果要更新一個資料結構,就返回一份新的副本,舊的資料仍保持不變;
  • 重構方法:
    • 可以用 「封裝變數」 來確保所有資料更新操作都透過很少幾個函式來進行,使其更容易監控和演進;
    • 如果一個變數在不同時候被用於儲存不同的東西,可以使用 「拆分變數」 將其拆分為各自不同用途的變數,從而避免危險的更新操作;
    • 使用 「移動語句」 和 「提煉函式」 儘量把邏輯從處理更新操作的程式碼中搬移出來,將沒有副作用的程式碼與執行資料更新的程式碼分開;
    • 設計 API 時,可以使用 「將查詢函式和修改函式分離」 確保呼叫者不會調到有副作用的程式碼;
    • 如果可變資料的值能在其他地方計算出來,使用 「查詢取代派生變數」來重構;

7. 發散式變化

如果某個模組經常因為不同的原因在不同的方向上發生變化,發散式變化就出現了;

  • 如果增加一個新功能,需要對原有功能做出大量的修改。這就是散發是變化的徵兆
  • 每當要對某個上下文做修改時,只需要理解這個上下文,而不必操心另一個,“每一次只關係一個上下文”這一點很重要;
  • 重構方法:
    • 如果發生變化的兩個方向自然地形成先後次序,可以用 「拆分階段」 將兩者分開;

8. 霰彈式修改

如果修改的程式碼散步四處,不但很難找到他們,也很容易錯過某個重要的修改;

  • 重構方法:
    • 應該使用 「搬移函式」 和 「搬移欄位」 把所有需要修改的程式碼放進同一個模組裡;
    • 面對霰彈式修改,一個常用的策略就是使用與內聯相關的重構---如 「行內函數」 或者 「內聯類」,把本不該分散的邏輯拽回一處;

9. 依戀情結

所謂模組化,就是力求將程式碼分出區域,最大化區域內部的互動,最小化跨區域的互動。

  • 有時會發現,一個函式跟另一個模組中的函式或者資料交流格外頻繁,遠勝於在自己所有的模組內部的交流,這就是典型的依戀情況;
  • 重構方法:
    • 這個函式想跟這些資料待在一起,就可以使用 「搬移函式」 把它移過去。
    • 有時候函式中只有一部分受這種依戀之苦,這時應該使用 「提煉函式」 把這部分提煉到獨立的函式中,再使用 「搬移函式」 ;

判斷哪個模組擁有的詞函式使用的資料最多,然後就把這個函式和那些資料擺在一起;

10. 重複的 switch

任何 switch 語句都應該用 「以多型取代條件表示式」 消除掉,所有條件邏輯都應該用多型取代,絕大多熟 if 語句都應該被消除掉;

  • 在不同的地方反覆使用同樣的 switch 邏輯,重複的 switch 的問題在於:每當你想增加一個選擇分支時,必須找到所有的 switch ,並逐一更新。多型可以幫我們寫出更優雅的程式碼庫;

11. 迴圈語句

函式作為一等公民已經得到了廣泛的支援,因為可以使用 「以管道取代迴圈」 來讓迴圈消失。

  • 「管道操作(filter/map)」 可以幫助我們更快地看被處理的元素以及處理它們的動作;
本作品採用《CC 協議》,轉載必須註明作者和本文連結
周小帥

相關文章