遺留程式碼處理技巧與案例演示

2022-11-09 12:00:24

1 什麼是遺留程式碼

本質是一種技術債務,產生原因一方面是業務原因:如業務本身場景繁多、流程複雜等;另一方面是技術原因:如程式碼不規範、設計不合理、祖傳程式碼檔案註釋缺失等。它會影響我們的程式很多方面:如可讀性、可修改性、可複用性、可維護性、可測試性等。

2 遺留程式碼處理過程拆解

劃分為梳理->重構/重寫->替換/驗證三個階段

2.1 梳理

遺留程式碼的處理是一種逆向工程,從已有的程式碼+資料模型+檔案倒推出業務模型、互動和規則,在保真的前提下再重新構建程式碼+資料模型+檔案。
我們這裡可以參考下DDD領域驅動設計裡戰略設計部分常用的工具(事件風暴法)來進行這部分梳理工作。

事件風暴本質上是一種系統建模的方法,與它處於對等位置的,會有「UML建模」、「事件驅動建模」等。事件風暴跟敏捷開發裡的一些理念(如使用者故事)的產生背景類似,都是在理性思考無法應對變化頻繁且文字難以描述的情況下,通過一些輔助性的提示卡片、視覺手段,輔以相關人員的集中、高頻溝通來完成對於業務的準確把握和抽象建模。

事件風暴的過程:

  1. 通過梳理業務流程,建立相應的領域事件(Event)
  2. 補充引發每個領域事件的命令(Command)
  3. 通過實體/聚合把命令和事件關聯起來
  4. 劃分領域邊界及事件流動線條
  5. 識別使用者操作所需的關聯檢視及其角色

事件風暴的產物:

  1. 領域物件 即實體/聚合。這裡的領域物件並非資料庫模型, 而是與業務緊密聯絡的「物件」。因為事件風暴是一種物件導向的建模方式, 而不是面向資料庫的建模方式。
  2. 領域事件 即物件在某些操作或特點時點下所產生的事件, 這些事件將決定之後多個聚合和限界上下文(BC)之間的通訊方式。
  3. 限界上下文 當所有的物件(實體/聚合)被梳理出來後,屬於同一種「通用語言」的物件, 則會被歸入同一個限界上下文邊界內;不屬於同一種「通用語言」的物件, 則會被邊界給分割開,劃入不同的子域或限界上下文。

梳理結果範例:

2.2 重構/重寫

通過重構/重寫對軟體要素進行重新組織,使其不改變外部行為的情況下,提升程式碼的可讀性或使其結構更合理。

針對不同層次的軟體要素要做不同的處理和控制:

並且整個重構/重寫過程有些需要遵照的原則:

  1. 單一職責:可以將依賴歸攏,統一行為和控制。權責明確,場景明確。
  2. 單一原則:消除重複的資料宣告、行為;因為單一所以保證了複用,統一標準 ,可裝配性。
  3. 封裝原則:不需要過度關心依賴類內部實現,最好一個.就能呼叫。
  4. 歸屬原則:上帝的歸上帝,凱撒的歸凱撒。誰提供的資料更多,歸屬於誰。
  5. 抽象層次:越高層的抽象越穩定,越細節的東西越容易變化。舉例:介面應傳遞職責而非實現細節。
  6. 開閉原則:對修改關閉,對擴充套件開放。
  7. kiss原則: 好理解,好維護。
  8. 清晰原則:唯讀小部分程式碼就可以知道怎麼改邏輯,做擴充套件。而不是要通讀所有程式碼,才能理清。

其中有兩點落地細節我們具體分析下:

  1. 業務邏輯的處理
    業務程式碼和技術程式碼解耦
    主流程程式碼和附加流程程式碼解耦
    長鏈路的拆解編排
  2. 關注點的分離
    雙向依賴:上下文之間缺少一層未被澄清的上下文,或者兩個上下文其實可被合為一個;
    迴圈依賴:任何一個上下文發生變更,依賴鏈條上的上下文均需要改變;
    過深的依賴:自身依賴的資訊不能直接從依賴者獲取到,需要通過依賴者從其依賴的上下文獲取並傳遞,依賴鏈路過長,依賴鏈條上的任何一個上下文發生變更,其鏈條後的任何一個上下文均可能需要改變;

2.3 替換驗證

大概分為以下幾個要點:

  1. 領會意圖,抽取用例,增加可複測性
  2. 增加可監測性
  3. 分成小塊,逐步替換
  4. 試點、看到成效

可藉助過程管理工具如PDCA法進行管理

3 案例演示

3.1 案例1:針對強耦合的實現做重構

原始需求:案例為一個轉賬服務,使用者可以通過銀行網頁轉賬給另一個賬號,支援跨幣種轉賬。同時因為監管和對賬需求,需要記錄本次轉賬活動。

原始架構:是一個傳統的三層分層結構:UI層、業務層、和基礎設施層。上層對於下層有直接的依賴關係,導致耦合度過高。在業務層中對於下層的基礎設施有強依賴,耦合度高。我們需要對這張圖上的每個節點做抽象和整理,來降低對外部依賴的耦合度。

重構關鍵設計點:

重構後程式碼特徵:

業務邏輯清晰,資料儲存和業務邏輯完全分隔。
Entity、Domain Primitive、Domain Service都是獨立的物件,沒有任何外部依賴,但是卻包含了所有核心業務邏輯,可以單獨完整測試。
原有的轉賬服務不再包括任何計算邏輯,僅僅作為元件編排,所有邏輯均delegate到其他元件。

3.2 案例2:提高老程式碼的複用性

原始需求:現有幾個策略實現類,被很多程式碼使用。現在需要根據不同的業務方在每個策略執行前做不同的前置邏輯處理。

解法分析:儘量避免把邏輯耦合到已有的實現類中。引入外部類進行控制反轉。這裡我們使用存取者模式。

存取者模式把資料結構和作用於結構上的操作解耦合,使得操作集合可相對自由地演化。存取者模式適用於資料結構相對穩定演演算法又易變化的系統。因為存取者模式使得演演算法操作增加變得容易。若系統資料結構物件易於變化,經常有新的資料物件增加進來,則不適合使用存取者模式。存取者模式的優點是增加操作很容易,因為增加操作意味著增加新的存取者。存取者模式將有關行為集中到一個存取者物件中,其改變不影響系統資料結構。其缺點就是增加新的資料結構很困難。

具體實現:

重構後程式碼特徵:

可以通過存取者對老程式碼邏輯進行編排,將修改外接,減少對老邏輯的影響
通過java8預設介面實現提供預設存取行為,避免大量策略子類的感知,只需要需要提供自己實現行為的子類對預設實現進行覆寫

4 總結

遺留程式碼的處理能力一方面是對技術的要求,另一方面也是對業務掌握的挑戰。希望我們可以跨越荊棘、穿過迷霧,順利到達成功的彼岸!

作者:馮鴻儒