抽絲剝繭 – 例項簡析重構程式碼的三板斧

江米小棗tonylua發表於2019-02-18

I. 太陽之下無新事:如何面對既有程式碼

既有程式碼

可以說,任何軟體系統從設計部署好的第二天起,就都變成了 既有程式碼(existing code)。一個幾年的系統和一個幾周的系統中存在的問題,並無本質上的差異。

俗話說,創業容易守業難。比新搭建一個系統更常見的工作,正是對既有系統的日常維護,一般包括:

  • 新增新特性
  • 修正bug
  • 優化設計和效能

如何有效進行這些工作,而不是陷入“修復一個bug,還你幾個新bug”的惡性迴圈,是每個開發者必然面對的課題。

遺留程式碼

既有程式碼的一種極端情況,就 是遺留程式碼(legacy code),一般指那些無人再維護的,或架構非常過時,亦或執行在老舊的作業系統上的程式碼。

相比於在大抵上每幾天就打個照面的既有程式碼中修修改改;當面對一頭霧水的遺留程式碼時,如果沒有正確的方法,即便再小心翼翼,也總會陷入束手無策的境地。

II. 既有程式碼的困境

每個開發者可能都見過奇奇怪怪的各種具體問題,但歸納起來,主要有這麼三種情況:

  • 過重的依賴:例項化或方法呼叫的過程中,過多過深的依賴於其他類或元件
  • 錯誤的封裝:類或元件承擔了不應有或過多的功能
  • 裸奔的功能:沒有測試程式碼或過時、不完整的測試覆蓋

III. 重構的意義

面對以上困境,需要做的就是重構:

在不改變程式碼功能的前提下,改善其設計的行為被稱為 重構(refactor)

重構的意義:使既有程式碼更具可維護性,並消除其不確定性

重構的關鍵:在其過程中不應該有任何功能上的改變

在之前提到過的幾種日常工作中,無一例外不需要先進行有效的重構,才能保證工作的順利進行。

IV. 重構三板斧:解開依賴、合理封裝、測試覆蓋

同樣顯而易見的是,將三重困境一一化解,就可以達到理想的重構:

  1. 依賴:

    • 簡化導致邏輯複雜的過重依賴,將關注點從散佈在艱深冗長的呼叫鏈條中拉回來
    • 設定setter方法或借鑑interface的思路等,解決由於關聯了太多其他類或元件,從而無法在測試用例中例項化和呼叫方法的問題
  2. 封裝:

    • 類或元件只應該承擔儘量簡單而少量的職責,過長的類或元件應抽取成多個
    • 類或元件不應當包含其子層級或兄弟層級的邏輯
    • 出現在多處的重複程式碼總是可疑的,應該儘量抽象和提取出來
  3. 測試:

    • 測試的作用就是檢驗正確性和檢驗變化
    • 迴歸測試(regression testing):週期性的執行測試,來檢驗已知的良好行為是否依然正常工作。
    • 單元測試(unit testing):是指對軟體中的最小可測試單元進行檢查和驗證;在 JS 中“單元”一般可以認為是一個函式或一個類。
    • 測試用例(test case):就是設定輸入資料,執行被測試函式,然後判斷實際輸出是否符合預期

簡而言之,前兩項是最後“落在實處”的工作,而測試的重要性並不亞於任何工作,甚至是保證前兩項進行下去的關鍵。 **測試覆蓋率(test coverage)**越高,說明所覆蓋部分的可靠性越有保證,而不必時時擔心改動帶來的未知影響。

遵循為獨立單元(視情況為函式、類或元件等)編寫測試的理念,就可以寫出小而易理解的一個個測試用例,也反過來使得程式碼比寫註釋更容易理解。

對於新開發的功能,可以用測試驅動開發(TDD)的方法,即重複“寫一點程式碼->編寫測試->失敗->修改程式碼->測試通過”的過程,最終達到方法的完成。

對於既有程式碼,可以根據日常需求,對涉及到的部分逐步引入單元測試,持續不斷的提高系統的測試覆蓋率。

V. 一個stepper元件的重構例項

這裡舉一個足夠簡單也比較典型的例子:重構stepper元件

場景描述:

在這個由 react 元件構建的既有系統中,在若干介面中都引用了一個常見的 數字選擇器(numeric stepper),其變化會觸發判斷邏輯,提示商品對應的數量是否有足夠庫存、是否達到了限購數量等

既有的結構和問題:

抽絲剝繭 – 例項簡析重構程式碼的三板斧
  • “全域性”元件NumberStepper裡糅雜了具體業務邏輯“限購”和“庫存”的判斷
  • 以上判斷邏輯冒泡到各種容器元件中,“演化”出了同一函式的不同簽名形式 — 梳理後發現:雖然引數定義不同且含糊不清,實際要達成的邏輯卻是一樣的
  • 相關的邏輯判斷程式碼和彈窗jsx結構,均重複出現於不同元件中
  • NumberStepper和各種容器元件中,均分別存在用 0|1|2|3 定義的判斷和顯示邏輯,且無註釋說明

問題的分解:

抽絲剝繭 – 例項簡析重構程式碼的三板斧
困境 問題A 問題B 問題C
依賴過重
封裝錯誤
缺乏測試

問題的解決:

抽絲剝繭 – 例項簡析重構程式碼的三板斧
  • 在測試的保護下,將NumberStepper中的具體業務邏輯依賴刪除
  • NumberStepper暴露props.checkLimit,在數量增減時響應,將判斷邏輯交給呼叫者
  • 統一判斷邏輯onLimitOver的函式簽名,明確引數的意義
  • 將展現部分的邏輯和介面分別提取為單一的部分,統一呼叫
  • 將之前用數字區分的邏輯歸納成常量類,交由各處統一呼叫
  • NumberStepperOverLimit等元件編寫測試,保證改動的全面和正確性

VI. 總結

至此,之前的結構得到了有效的梳理;再進行相關的功能新增就有章可循、心中有數了。

  • 處理大部分“新增新特性、修正bug和優化”類的日常工作時,需要科學具體的方法論
  • 過重的依賴、錯誤的封裝、缺少測試,是既有程式碼的常見問題
  • 有針對性的從以上三個方面入手,並輔以必要的單元測試,就可以保證工作有條理的進行下去

VII. 參考資料

  • https://book.douban.com/subject/2248759/
  • https://en.wikipedia.org/wiki/Legacy_code
  • https://www.tuicool.com/articles/3QRBRr
  • https://baike.baidu.com/item/單元測試

(end)


長按二維碼或搜尋 fewelife 關注我們哦
抽絲剝繭 – 例項簡析重構程式碼的三板斧

相關文章