在軟體開發的實務中,全新專案固然令人嚮往,但更常見的挑戰是面對一個充滿技術債、缺乏文件的舊有代碼庫。如何在這看似混亂的結構中,發掘隱藏的可重用資產與設計模式?本文將深入探討兩個經典且極具實戰價值的模式——組合方法與單一抽象層原則 (SLAP),示範如何透過重構,將「大雜燴」式的臃腫代碼轉化為清晰、可測試、可復用的框架雛形。
測試驅動開發 (TDD) 能引導我們在綠地專案中逐步長出設計,但面對現有的「遺產系統」,我們往往束手無策。Kent Beck 在《Smalltalk Best Practice Patterns》一書中提出的組合方法,提供了破局的鑰匙。其核心三原則直指要害:
嚴格遵循 TDD 自然能產出符合此模式的代碼,但對於現有代碼,我們需要主動重構來「提煉」這些資產。
以一個使用 JDBC 直接查詢資料庫並組裝 Part 物件的 populate() 方法為例。原始代碼混雜了驅動載入、連線建立、SQL 執行、結果集遍歷與物件映射等多層次細節,雖不長,卻違反了「單一職責」與「同一抽象層」原則。
將 populate() 拆解為 getDatabaseConnection()、createResultSet()、addPartToListFromResultSet() 等私有方法。此時 populate() 變成了一份清晰的「工作流大綱」,而非實作細節的堆砌。
觀察提取出的方法,getDatabaseConnection() 純粹處理資料庫連線,與業務實體 Part 無關,應上移至父類 BoundaryBase。createResultSet() 雖通用,但含有 SQL 常數,引入抽象方法 getSqlForEntity() 讓子類提供 SQL,即可將此法上移。同理,將 addPartToListFromResultSet() 抽象為 addEntityToListFromResultSet(),連同迴圈邏輯一併上移至父類的 populate() 模板方法中。
重構後,PartDb 專注於業務映射,BoundaryBase 擁有通用持久化骨架。這正是提煉框架的精髓:從實際代碼中長出解決真實問題的框架,而非關起門來設計充滿投機性功能的「大而全」框架(如 JSF 的生命週期複雜度)。擁有了細粒度的原子方法,組合重用成為可能,測試也變得輕而易舉。
組合方法的第二條規則——「同一抽象層」——即 SLAP 原則。違反 SLAP 的代碼會在高層業務邏輯(如 addOrder())與低層技術細節(如 c.setAutoCommit(false))之間跳躍,嚴重阻礙閱讀與設計發現。
將 addOrder() 重構為 setupDataInfrastructure()、add()、completeTransaction()、cleanUp() 等高層意圖導向的方法。即使 completeTransaction() 僅有一行 commit(),也不可內聯,因為它代表「完成交易」這一業務概念,隔離了 JDBC 細節。
wrapInTransaction進一步觀察,發現「在交易塊中保護更新」是系統中的慣用模式。利用 Command 模式(或 Groovy 閉包)提煉出 wrapInTransaction(Command c),將交易的樣板代碼(基礎建立、提交/回滾、清理)完全封裝,業務代碼僅需傳入核心邏輯。這不僅消除重複,更將設計意圖顯性化。
面對 300 行巨獸方法,第一步永遠是「分而治之」。組合方法賦予我們手術刀,SLAP 原則指引切割的紋理。將代碼拆解為原子塊,可重用資產與慣用模式便會浮現。這不是終點,而是緊急設計的起點——在下一階段,我們將基於這些資產,推演更宏大的架構演化。