I. 太陽之下無新事:如何面對既有程式碼
既有程式碼
可以說,任何軟體系統從設計部署好的第二天起,就都變成了 既有程式碼(existing code)。一個幾年的系統和一個幾周的系統中存在的問題,並無本質上的差異。
俗話說,創業容易守業難。比新搭建一個系統更常見的工作,正是對既有系統的日常維護,一般包括:
- 新增新特性
- 修正bug
- 優化設計和效能
如何有效進行這些工作,而不是陷入“修復一個bug,還你幾個新bug”的惡性迴圈,是每個開發者必然面對的課題。
遺留程式碼
既有程式碼的一種極端情況,就 是遺留程式碼(legacy code),一般指那些無人再維護的,或架構非常過時,亦或執行在老舊的作業系統上的程式碼。
相比於在大抵上每幾天就打個照面的既有程式碼中修修改改;當面對一頭霧水的遺留程式碼時,如果沒有正確的方法,即便再小心翼翼,也總會陷入束手無策的境地。
II. 既有程式碼的困境
每個開發者可能都見過奇奇怪怪的各種具體問題,但歸納起來,主要有這麼三種情況:
- 過重的依賴:例項化或方法呼叫的過程中,過多過深的依賴於其他類或元件
- 錯誤的封裝:類或元件承擔了不應有或過多的功能
- 裸奔的功能:沒有測試程式碼或過時、不完整的測試覆蓋
III. 重構的意義
面對以上困境,需要做的就是重構:
在不改變程式碼功能的前提下,改善其設計的行為被稱為 重構(refactor)
重構的意義:使既有程式碼更具可維護性,並消除其不確定性
重構的關鍵:在其過程中不應該有任何功能上的改變
在之前提到過的幾種日常工作中,無一例外不需要先進行有效的重構,才能保證工作的順利進行。
IV. 重構三板斧:解開依賴、合理封裝、測試覆蓋
同樣顯而易見的是,將三重困境一一化解,就可以達到理想的重構:
-
依賴:
- 簡化導致邏輯複雜的過重依賴,將關注點從散佈在艱深冗長的呼叫鏈條中拉回來
- 設定setter方法或借鑑interface的思路等,解決由於關聯了太多其他類或元件,從而無法在測試用例中例項化和呼叫方法的問題
-
封裝:
- 類或元件只應該承擔儘量簡單而少量的職責,過長的類或元件應抽取成多個
- 類或元件不應當包含其子層級或兄弟層級的邏輯
- 出現在多處的重複程式碼總是可疑的,應該儘量抽象和提取出來
-
測試:
- 測試的作用就是檢驗正確性和檢驗變化
- 迴歸測試(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
的函式簽名,明確引數的意義 - 將展現部分的邏輯和介面分別提取為單一的部分,統一呼叫
- 將之前用數字區分的邏輯歸納成常量類,交由各處統一呼叫
- 為
NumberStepper
和OverLimit
等元件編寫測試,保證改動的全面和正確性
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/單元測試
長按二維碼或搜尋 fewelife 關注我們哦