前面已經選定目標系統使用的框架(Framework),也建立了相依項目的管理方式。接下來要把框架提供的結構與生命週期用在實際程式中,避免每項功能都自行處理相同工作。
重複程式碼可能來自共同的功能規則、反覆出現的處理流程,或技術要求的固定寫法。這些情況需要採用不同方法。只把相似內容移到大型工具類別,雖然能減少表面上的程式碼行數,卻可能隱藏責任、增加相依關係,讓後續修改更難判斷影響範圍。
外觀相同不代表責任相同。兩段程式碼只有在目的、規則與變更原因一致時,才適合共用。相反地,兩段寫法不同的程式碼也可能正在處理同一項共同責任,只是既有系統缺少統一做法。
可以按照下列類型判斷處理方式:
| 重複原因 | 判斷方式 | 適合的處理方向 |
|---|---|---|
| 相同的功能規則分散在多處 | 規則改變時,多個位置必須一起修改 | 將規則放入責任清楚的函式、類別或模組 |
| 多條執行路徑都有相同前置或後續處理 | 每次執行都要按照相同順序處理 | 使用框架生命週期或處理管線統一執行 |
| 多個模組重複建立相同相依項目 | 建立方式、設定與存續時間需要一致 | 使用依賴注入管理建立方式與存續範圍 |
| 技術要求大量規律且固定的宣告 | 內容可以由設定或結構穩定推導 | 使用框架內建機制、命令列工具或程式碼產生 |
| 程式碼目前相似,但處理目的與變更原因不同 | 兩者會因不同需求而各自改變 | 保持分開,避免建立不穩定的抽象介面 |
判斷時,先列出重複位置的觸發時機、輸入、輸出、狀態變化與失敗處理,再比較它們是否遵循相同規則。只有少量文字相同,或目前剛好採用相同計算步驟,都不足以支持抽取共用程式碼。
如果共用函式需要持續增加布林參數、模式選項或例外分支,通常表示其中包含多項不同責任。這時應該重新劃分邊界,停止繼續擴充同一個共用入口。
框架通常會定義程式從接收輸入、執行功能到產生輸出的生命週期。多條路徑都需要、而且執行順序一致的工作,可以放入框架提供的擴充位置,避免每項功能自行呼叫。
常見候選項目包括:
共同處理不代表所有功能都要套用同一組步驟。每項責任需要明確定義適用範圍,並保留功能層級的差異。例如,輸入格式可以由共同機制檢查,特定狀態是否允許執行仍屬於功能規則,應該留在對應功能中。
資料存取也不適合只因寫法重複就全部放入共同管線。查詢條件、狀態變更與一致性範圍通常和功能目的有關。框架可以統一連線建立、物件轉換或交易管理,實際資料操作仍要由責任清楚的元件表達。
不同框架提供的名稱與執行方式不完全相同,常見擴充點包括中介軟體(Middleware)、攔截器(Interceptor)與篩選器(Filter)。採用前要先查明框架文件中的實際語意,不能只按照名稱推測用途。
| 擴充方式 | 常見用途 | 導入時要確認的內容 |
|---|---|---|
| 中介軟體 | 包覆一段輸入至輸出的處理流程,決定是否繼續交給下一個步驟 | 執行順序、中止條件、錯誤傳遞與適用路徑 |
| 攔截器 | 在特定呼叫前後加入計時、轉換、記錄或其他共同處理 | 呼叫範圍、傳入內容、傳回內容與失敗時的行為 |
| 篩選器 | 在框架指定階段處理驗證、錯誤或結果轉換 | 觸發條件、優先順序,以及與其他擴充點的關係 |
選擇擴充點時,應該使用能涵蓋需求的最小範圍。只適用單一功能的規則不需要放入全域管線,只適用部分模組的共同處理也應該限制註冊範圍。範圍過大會讓功能在沒有明確呼叫的情況下改變行為,增加除錯難度。
管線順序也是架構的一部分。假設輸入檢查、執行資訊、存取限制、功能處理與錯誤轉換都放入管線,就要定義每個步驟何時開始、甚麼情況可以中止,以及前一步建立的資訊如何交給下一步。順序只存在於框架設定中卻沒有測試時,調整註冊位置就可能改變整體行為。
框架擴充點應該處理穩定且跨功能的責任。複雜的功能判斷、特定資料變更或只對單一情境成立的例外,仍然要由功能本身負責,避免共同管線逐漸成為另一個難以理解的大型流程。
多個模組如果自行建立記錄元件、資料存取元件、外部整合元件或設定讀取元件,建立參數與存續時間很容易逐漸不同。依賴注入(Dependency Injection)可以把建立與組合責任交給框架管理,讓使用元件的程式只宣告需要哪些能力。
導入時需要確認下列事項:
存續時間設定錯誤可能造成狀態互相影響,或讓短期相依項目被長期元件保留。框架如果提供不同的建立範圍,需要以實際元件行為選擇,並且針對共用狀態與同時執行情境進行測試。
依賴注入負責組合元件,不會自動產生合理的責任邊界。如果一個元件需要大量不相關的相依項目,或多個元件彼此循環依賴,仍然要重新檢查它是否承擔過多責任。
無法放入框架管線的重複內容,可能適合建立共用元件。共用內容需要有明確目的、穩定介面與可判斷的使用範圍,不能只把任何重複片段集中到同一個目錄。
可以按照內容選擇合適邊界:
共用元件不應該反向依賴使用它的功能,也不應該透過全域可變狀態交換資料。當元件開始同時處理格式轉換、資料存取、錯誤處理與功能判斷時,名稱通常會變得模糊,使用端也需要了解大量內部行為。這是再次拆分責任的訊號。
大型基底類別與深層繼承也會隱藏相依關係。子類別可能因基底類別的一項修改而一起改變,卻難以從程式介面看出原因。需要共用行為時,優先使用小型元件組合。只有框架明確要求繼承,且共同生命週期確實穩定時,才建立最小的基底類別。
框架可能提供資料繫結、輸入驗證、設定載入、錯誤處理與測試支援。採用這些能力前,仍要確認它們符合已選定的技術條件與功能需求。
使用內建機制可以減少自行維護的樣板,也會讓程式受到框架生命週期、預設行為與版本變更影響。導入時要保存必要設定,了解預設值,並為會影響功能結果的行為建立測試。不能因為框架自動處理,就省略對輸入、錯誤與資料變更的確認。
對於可以由固定結構推導的大量內容,可以評估程式碼產生(Code Generation)或框架命令列工具。例如,按照資料結構產生初始轉換程式,或按照固定規格建立模組骨架。採用前要先回答下列問題:
程式碼產生適合處理規律結構,不適合隱藏複雜功能規則。產生結果如果仍需要大量人工修補,表示來源資訊或產生邊界可能不完整,繼續擴大產生範圍只會增加維護成本。
減少重複的成果不能只用程式碼行數判斷。共同元件如果要求每個功能理解大量隱藏規則,或一次修改就影響無關功能,架構仍然沒有改善。
導入時要特別留意下列情況:
遇到這些情況時,應該回到責任與變更原因重新判斷。允許少量、目的不同的程式碼保留相似寫法,通常比建立錯誤的共用抽象更容易維護。
在目標系統建立共同架構時,可以先選擇一條具代表性的功能路徑,使用已確認的規格與測試固定預期行為,再導入框架擴充點及共用元件。確認結果正確後,其他功能才沿用相同結構。
測試至少要涵蓋下列層次:
如果目標系統在建立共同架構前已經出現重複實作,完成抽取後還要移除被取代的內容,並避免保留繞過共同入口的另一套做法。兩種路徑同時存在會讓後續功能不知道該採用哪一種,也可能讓相同規則再次分歧。
共同架構完成時,應該可以確認下列結果:
如果新增一項功能仍要複製整段基礎處理,或修改共同規則仍需要尋找多個位置,表示責任邊界或框架整合尚未完成。另一方面,如果任何小差異都需要修改共用元件,也要檢查抽象範圍是否過大。