兩個互為鏡像的變更味道。發散式修改:一個類別因不同原因、被不同方式反覆修改。散彈式修改:一個變更卻要同時改動好幾個類別。好的設計讓「一種變化」對上「一個模組」。
🧠 Why It's a Problem
- Divergent Change(發散式):一個類別受多種變化影響——換一種資料庫要改它、加一種金融工具也要改它。這代表它其實裝了好幾個彼此無關的職責。
- Shotgun Surgery(散彈式):一種變化影響多個類別——為了一個決策,得東改一點、西改一點,很容易漏改。這代表相關邏輯被打散了。
- 理想狀態:需求變更時,修改範圍應被限縮在單一模組內。
NOTE
- 發散式修改是「一個類別受多種變化影響」(一對多)。
- 散彈式修改是「一種變化影響多個類別」(多對一)。
兩者是同一枚硬幣的兩面:都在破壞「一種變化 ↔ 一個模組」的對應關係。
⚖️ Refactoring
- 發散式 → 拆分成不同類別,建立領域邊界(Extract Class),讓每個類別只因一種原因而變。
- 散彈式 → 搬移函式/欄位,把散落的相關邏輯集中到同一模組(Move Function)。
發散式 — 一個類別,三種變化都打在它身上
// 前:顯示、儲存、版稅三種不相干的變化,全落在同一個類別
class Article {
formatForWeb() {
/* 顯示規則變 → 改這 */
}
saveToDatabase() {
/* 儲存方式變 → 改這 */
}
calcRoyalty() {
/* 版稅規則變 → 改這 */
}
}
// 後:依變化原因拆成三個類別,各自只因一種原因而變
class ArticleRenderer {
formatForWeb(article) {}
}
class ArticleRepository {
save(article) {}
}
class RoyaltyCalculator {
calc(article) {}
}散彈式 — 一種變化,散落在多個類別
// 前:「新增一種支付幣別」這一種變化,得同時改好幾個類別
class OrderView {
formatPrice(o) {
/* 幣別符號 */
}
}
class Invoice {
totalLine(o) {
/* 幣別換算 */
}
}
class Receipt {
footer(o) {
/* 幣別四捨五入 */
}
}
// 後:把散落的幣別邏輯集中到一個模組,之後改幣別只動這裡
class Money {
symbol() {}
convert() {}
round() {}
}🔑 Takeaways
- 兩者互為鏡像:發散是「一個類別、多種變化」;散彈是「一種變化、多個類別」。
- 發散式 → Extract Class,讓每個類別只因一種原因而變。
- 散彈式 → Move Function,把散落的相關邏輯收攏到同一模組。
- 目標始終一致:一種變化,只需改一個地方。
No notes yet — jot your takeaways or Q&A here.