XP 的「簡單設計」把好設計壓成三句箴言:KISS(考慮可能有效的最簡方案)、YAGNI(你不會需要它——別為想像中的未來預先加基礎設施)、DRY(一次且僅一次——發現重複就消除,這會自然驅動出好抽象)。它們是 SOLID 之外的「日常剎車」:讓設計只服務當前迭代,不預先擔憂未來。
🧠 When to Use
TIP
訊號:你正在寫「以後可能會用到」的擴充點;同一段邏輯在三個地方各抄一份;或為了「更有彈性」把簡單需求包成三層抽象。這三條原則就是叫停的手煞車。
- 面對一個需求,想確認「最簡方案」到底長怎樣、有沒有過度設計。
- 看到重複的知識(邏輯、規則、常數)散在多處,想收斂成單一來源。
- 想抵抗「先把框架搭好」的誘惑,讓基礎設施服務於真實需求而非假設。
⚖️ Structure & Variants
KISS — 考慮可能有效的最簡方案
// ❌ 需求只是「打九折」,卻先開一套可插拔的規則引擎
class DiscountEngine(private val rules: List<DiscountRule>) { /* ... 一堆抽象 ... */ }
// ✅ 最簡方案:一個函式就夠,等真的有第二種折扣再長出抽象
fun memberPrice(base: Int): Int = (base * 0.9).toInt()- 「Consider the simplest thing that could possibly work」——先寫能動的最簡碼,需要彈性時再靠重構引入抽象。
YAGNI — 你不會需要它
// ❌ 現在只支援信用卡,卻預先塞了 PayPal / 加密貨幣 / 分期 的空殼
interface PaymentGateway { /* ... */ }
class CreditCard : PaymentGateway
class PayPalStub : PaymentGateway // 沒人用,先擺著
class CryptoStub : PaymentGateway // 想像中的未來
// ✅ 只做當前迭代要的;真的要加通道時,那時的需求會告訴你正確的介面長相
class Checkout(private val card: CreditCard) { /* ... */ }- 抵抗「提前加入基礎設施」的誘惑。預先加的擴充點多半猜錯形狀,反而成為之後改動的阻力——這與 OCP 的「上當一次(fool me once)」是同一件事的兩面。
DRY — 一次且僅一次(Once and Only Once)
// ❌ 同一條稅率規則抄在三個地方,改一處就漏兩處
fun invoiceTotal(x: Int) = x + x * 5 / 100
fun quoteTotal(x: Int) = x + x * 5 / 100
fun receiptTotal(x: Int) = x + x * 5 / 100
// ✅ 知識只存在一處,重複被消除
const val TAX_RATE = 0.05
fun withTax(x: Int): Int = x + (x * TAX_RATE).toInt()- DRY 針對的是知識的重複(同一條規則、同一個決策),不是「長得像的程式碼」。消除重複會自然把共同知識逼成一個好抽象。
⚠️ Misuse & Anti-patterns
- 把 DRY 當成「消滅所有相似程式碼」:兩段碼此刻長得像、但源自不同的變更理由,強行合併會製造錯誤耦合(改 A 需求卻動到 B)。DRY 談的是知識重複,不是形狀重複。
- 拿 YAGNI 當「不用想架構」的藉口:YAGNI 反對的是投機性的預先建設,不是反對必要的設計與抽象。已經反覆出現的變更,該抽就抽。
- KISS 誤成「不切分」:把所有東西塞一個大函式不叫簡單,那是 God Object。簡單指的是「表達力高且無多餘結構」,不是「不分層」。
🔑 Takeaways
- KISS / YAGNI / DRY 是讓設計「只服務當前迭代」的三條剎車,與 SOLID 互補。
- YAGNI 與 OCP 的「上當一次」同源:別預測變更,等它出現再保護。
- DRY 收斂的是知識而非形狀;消除真正的重複會自然長出好抽象,但別把巧合的相似硬綁在一起。
No notes yet — jot your takeaways or Q&A here.