iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Software Development

遠古聖遺物改造工程:遺留系統全面重構實務指南系列 第 11

[Day 11] 重複的程式碼過多,如何使用框架改善架構?

  • 分享至 

  • xImage
  •  

重複的程式碼過多,如何使用框架改善架構?

前面已經選定目標系統使用的框架(Framework),也建立了相依項目的管理方式。接下來要把框架提供的結構與生命週期用在實際程式中,避免每項功能都自行處理相同工作。

重複程式碼可能來自共同的功能規則、反覆出現的處理流程,或技術要求的固定寫法。這些情況需要採用不同方法。只把相似內容移到大型工具類別,雖然能減少表面上的程式碼行數,卻可能隱藏責任、增加相依關係,讓後續修改更難判斷影響範圍。

先確認程式碼為甚麼重複

外觀相同不代表責任相同。兩段程式碼只有在目的、規則與變更原因一致時,才適合共用。相反地,兩段寫法不同的程式碼也可能正在處理同一項共同責任,只是既有系統缺少統一做法。

可以按照下列類型判斷處理方式:

重複原因 判斷方式 適合的處理方向
相同的功能規則分散在多處 規則改變時,多個位置必須一起修改 將規則放入責任清楚的函式、類別或模組
多條執行路徑都有相同前置或後續處理 每次執行都要按照相同順序處理 使用框架生命週期或處理管線統一執行
多個模組重複建立相同相依項目 建立方式、設定與存續時間需要一致 使用依賴注入管理建立方式與存續範圍
技術要求大量規律且固定的宣告 內容可以由設定或結構穩定推導 使用框架內建機制、命令列工具或程式碼產生
程式碼目前相似,但處理目的與變更原因不同 兩者會因不同需求而各自改變 保持分開,避免建立不穩定的抽象介面

判斷時,先列出重複位置的觸發時機、輸入、輸出、狀態變化與失敗處理,再比較它們是否遵循相同規則。只有少量文字相同,或目前剛好採用相同計算步驟,都不足以支持抽取共用程式碼。

如果共用函式需要持續增加布林參數、模式選項或例外分支,通常表示其中包含多項不同責任。這時應該重新劃分邊界,停止繼續擴充同一個共用入口。

找出適合集中處理的共同責任

框架通常會定義程式從接收輸入、執行功能到產生輸出的生命週期。多條路徑都需要、而且執行順序一致的工作,可以放入框架提供的擴充位置,避免每項功能自行呼叫。

常見候選項目包括:

  • 在進入功能處理前檢查輸入格式及必要欄位。
  • 建立該次執行所需的共同資訊,並在後續步驟中傳遞。
  • 將未處理的錯誤轉換成一致且可追查的結果。
  • 按照相同欄位與格式記錄執行結果及錯誤資訊。
  • 如果系統具有身分識別與存取限制,在共同入口執行適用於多項功能的檢查。
  • 如果資料保存方式允許交易(Transaction),在已定義的一致性範圍內統一開始、確認或取消交易。

共同處理不代表所有功能都要套用同一組步驟。每項責任需要明確定義適用範圍,並保留功能層級的差異。例如,輸入格式可以由共同機制檢查,特定狀態是否允許執行仍屬於功能規則,應該留在對應功能中。

資料存取也不適合只因寫法重複就全部放入共同管線。查詢條件、狀態變更與一致性範圍通常和功能目的有關。框架可以統一連線建立、物件轉換或交易管理,實際資料操作仍要由責任清楚的元件表達。

將共同流程放入框架處理管線

不同框架提供的名稱與執行方式不完全相同,常見擴充點包括中介軟體(Middleware)、攔截器(Interceptor)與篩選器(Filter)。採用前要先查明框架文件中的實際語意,不能只按照名稱推測用途。

擴充方式 常見用途 導入時要確認的內容
中介軟體 包覆一段輸入至輸出的處理流程,決定是否繼續交給下一個步驟 執行順序、中止條件、錯誤傳遞與適用路徑
攔截器 在特定呼叫前後加入計時、轉換、記錄或其他共同處理 呼叫範圍、傳入內容、傳回內容與失敗時的行為
篩選器 在框架指定階段處理驗證、錯誤或結果轉換 觸發條件、優先順序,以及與其他擴充點的關係

選擇擴充點時,應該使用能涵蓋需求的最小範圍。只適用單一功能的規則不需要放入全域管線,只適用部分模組的共同處理也應該限制註冊範圍。範圍過大會讓功能在沒有明確呼叫的情況下改變行為,增加除錯難度。

管線順序也是架構的一部分。假設輸入檢查、執行資訊、存取限制、功能處理與錯誤轉換都放入管線,就要定義每個步驟何時開始、甚麼情況可以中止,以及前一步建立的資訊如何交給下一步。順序只存在於框架設定中卻沒有測試時,調整註冊位置就可能改變整體行為。

框架擴充點應該處理穩定且跨功能的責任。複雜的功能判斷、特定資料變更或只對單一情境成立的例外,仍然要由功能本身負責,避免共同管線逐漸成為另一個難以理解的大型流程。

使用依賴注入統一相依管理

多個模組如果自行建立記錄元件、資料存取元件、外部整合元件或設定讀取元件,建立參數與存續時間很容易逐漸不同。依賴注入(Dependency Injection)可以把建立與組合責任交給框架管理,讓使用元件的程式只宣告需要哪些能力。

導入時需要確認下列事項:

  • 透過建構式或框架支援的明確方式宣告相依項目,讓依賴關係可以從介面看出。
  • 將註冊集中在固定位置,避免同一能力在不同模組採用不同建立方式。
  • 按照元件是否保存狀態、是否可安全共用及實際執行範圍設定存續時間。
  • 只有在需要隔離框架、替換實作或建立測試邊界時才加入抽象介面,避免每個類別都建立沒有用途的介面。
  • 避免功能程式在執行途中任意向框架容器查找元件,否則相依關係會再次被隱藏。

存續時間設定錯誤可能造成狀態互相影響,或讓短期相依項目被長期元件保留。框架如果提供不同的建立範圍,需要以實際元件行為選擇,並且針對共用狀態與同時執行情境進行測試。

依賴注入負責組合元件,不會自動產生合理的責任邊界。如果一個元件需要大量不相關的相依項目,或多個元件彼此循環依賴,仍然要重新檢查它是否承擔過多責任。

建立邊界清楚的共用元件

無法放入框架管線的重複內容,可能適合建立共用元件。共用內容需要有明確目的、穩定介面與可判斷的使用範圍,不能只把任何重複片段集中到同一個目錄。

可以按照內容選擇合適邊界:

  • 將確定相同的功能規則放入專用元件,並使用符合功能意圖的名稱表達用途。
  • 將日期、數值或資料格式轉換放入無狀態的轉換元件,明確定義無效輸入的處理方式。
  • 如果系統包含前端,將行為與外觀一致的畫面片段整理成可組合元件,並限制對外參數。
  • 將多個模組共同使用的整合方式包裝在單一邊界後,避免框架或外部相依項目的專屬介面散布到功能程式。
  • 將設定結構與讀取方式集中管理,但讓每個功能只取得自己需要的設定內容。

共用元件不應該反向依賴使用它的功能,也不應該透過全域可變狀態交換資料。當元件開始同時處理格式轉換、資料存取、錯誤處理與功能判斷時,名稱通常會變得模糊,使用端也需要了解大量內部行為。這是再次拆分責任的訊號。

大型基底類別與深層繼承也會隱藏相依關係。子類別可能因基底類別的一項修改而一起改變,卻難以從程式介面看出原因。需要共用行為時,優先使用小型元件組合。只有框架明確要求繼承,且共同生命週期確實穩定時,才建立最小的基底類別。

善用框架機制減少樣板程式碼

框架可能提供資料繫結、輸入驗證、設定載入、錯誤處理與測試支援。採用這些能力前,仍要確認它們符合已選定的技術條件與功能需求。

使用內建機制可以減少自行維護的樣板,也會讓程式受到框架生命週期、預設行為與版本變更影響。導入時要保存必要設定,了解預設值,並為會影響功能結果的行為建立測試。不能因為框架自動處理,就省略對輸入、錯誤與資料變更的確認。

對於可以由固定結構推導的大量內容,可以評估程式碼產生(Code Generation)或框架命令列工具。例如,按照資料結構產生初始轉換程式,或按照固定規格建立模組骨架。採用前要先回答下列問題:

  • 產生內容的來源是否受到版本管理,並且能重現相同結果?
  • 產生工具及範本版本是否固定,更新後能否檢查差異?
  • 產生檔案是否允許人工修改,重新產生時如何保留必要變更?
  • 產生結果是否會經過格式檢查、建置與測試?
  • 產生內容如果不再符合需求,是否能停止使用或替換工具?

程式碼產生適合處理規律結構,不適合隱藏複雜功能規則。產生結果如果仍需要大量人工修補,表示來源資訊或產生邊界可能不完整,繼續擴大產生範圍只會增加維護成本。

避免把重複轉換成新的耦合

減少重複的成果不能只用程式碼行數判斷。共同元件如果要求每個功能理解大量隱藏規則,或一次修改就影響無關功能,架構仍然沒有改善。

導入時要特別留意下列情況:

  • 萬用工具模組持續加入不同目的的函式,呼叫端無法從名稱判斷責任。
  • 大型基底類別同時處理設定、記錄、資料存取與錯誤處理,子類別被迫接受不需要的行為。
  • 共用函式依靠大量模式參數切換流程,新增一種情境就要修改原有分支。
  • 全域框架擴充點包含特定功能規則,導致其他功能也受到影響。
  • 元件直接依賴框架的內部介面,使單獨測試、升級或替換變得困難。
  • 自動產生的程式碼無法重現,或產生後還需要在多處手動維護相同修改。

遇到這些情況時,應該回到責任與變更原因重新判斷。允許少量、目的不同的程式碼保留相似寫法,通常比建立錯誤的共用抽象更容易維護。

用測試確認架構調整沒有改變行為

在目標系統建立共同架構時,可以先選擇一條具代表性的功能路徑,使用已確認的規格與測試固定預期行為,再導入框架擴充點及共用元件。確認結果正確後,其他功能才沿用相同結構。

測試至少要涵蓋下列層次:

  • 直接測試共同元件的正常、邊界與失敗情況,確認輸入輸出及狀態變化。
  • 透過框架執行環境測試處理管線,確認註冊範圍、執行順序、中止條件與錯誤傳遞。
  • 測試依賴注入設定能建立完整元件,並確認不同存續時間不會共用不該共用的狀態。
  • 對採用共同架構的代表性功能執行整合測試,確認框架自動處理與功能程式能正確銜接。
  • 重新執行程式碼產生、建置與測試,確認產生流程可以重現且沒有遺漏人工修改。

如果目標系統在建立共同架構前已經出現重複實作,完成抽取後還要移除被取代的內容,並避免保留繞過共同入口的另一套做法。兩種路徑同時存在會讓後續功能不知道該採用哪一種,也可能讓相同規則再次分歧。

何時算是完成共同架構?

共同架構完成時,應該可以確認下列結果:

  • 相同功能規則只有一個明確負責位置,目的不同的程式碼沒有被強迫共用。
  • 共同前置與後續處理已放入適當的框架擴充點,並且定義適用範圍與執行順序。
  • 元件透過明確相依關係組合,建立方式與存續時間都有一致設定。
  • 共用元件具有清楚名稱、穩定介面及單一主要責任,不依賴使用它的特定功能。
  • 框架內建機制與程式碼產生流程可以重現,產生結果會經過建置與測試。
  • 測試能確認共同元件、框架管線與代表性功能維持預期行為。
  • 被取代的重複實作已經移除,後續功能有明確方式沿用共同架構。

如果新增一項功能仍要複製整段基礎處理,或修改共同規則仍需要尋找多個位置,表示責任邊界或框架整合尚未完成。另一方面,如果任何小差異都需要修改共用元件,也要檢查抽象範圍是否過大。

重點整理

  • 判斷重複程式碼時,要比較目的、規則與變更原因,不能只比較外觀或程式碼行數。
  • 多條執行路徑都需要的共同處理,可以放入框架生命週期或處理管線,並且明確限制適用範圍與順序。
  • 依賴注入可以統一元件建立、組合與存續時間,但元件責任仍需要由設計明確劃分。
  • 共用元件應該具有清楚邊界與穩定介面,避免大型基底類別、萬用工具模組及全域可變狀態形成新的耦合。
  • 框架內建機制與程式碼產生適合減少規律樣板,使用時仍要保存設定、固定產生方式並驗證結果。
  • 共同元件、框架管線、依賴注入設定與代表性功能都要經過測試,才能確認減少重複後仍維持預期行為。

上一篇
[Day 10] 如何管理函式庫與套件?需要使用套件管理工具?
下一篇
[Day 12] 程式碼應該遵循哪些規範?
系列文
遠古聖遺物改造工程:遺留系統全面重構實務指南14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言