在軟體開發的過程中,我們常常幻想著把各種情況都考慮進去,然後一次全部寫好吧?不要這樣幹,這樣的寫法往往會導致過度設計,開發者容易陷入"未來可能需要"的思考模式,結果提前實作了許多當下根本不需要的功能。
YAGNI(You Aren't Gonna Need It)原則 正是為了解決這個問題而生。這個原則告訴我們:只實作目前真正需要的功能,不要為了未來可能的需求而過度設計。這個詞彙最早可以追溯極限編程 (Extreme programming) 方法論,而將方法論撰寫成書名為 Extreme Programming Installed 一書的作者 Ron Jeffries,雖然未直接提及 You Aren't Gonna Need It,但書中提到簡單性(Simplicity)、增量設計(Incremental Design)等等的實踐都是與 YAGNI 原則相輔相成的。什麼是 Simplicity?也就是保持簡單的設計來滿足當前的需求,而軟體開發應當以小量增加並且漸進式開發來實現逐
讓我們舉例一個過度設計案例。假設我們要做一個卡片,目前只需要一個簡單的 8 折功能,我們常常會想既然要寫折扣功能,不如一次就把各種折扣策略都準備好吧?
查看以下範例
import React, { createContext, useContext, useMemo, useState } from "react";
// 金額型別
type Money = { amount: number; currency: "TWD" };
// 折扣策略函式型別(目前只有一種,但卻先預留多種可能)
type DiscountStrategy = (p: Money) => Money;
// 策略函式:打折
const percentageOff = (pct: number): DiscountStrategy => {
return (p: Money) => ({
...p,
amount: Math.round(p.amount * (1 - pct)),
});
};
// Context(為了讓折扣策略可替換,但現在根本只有一個策略)
const DiscountContext = createContext<DiscountStrategy | null>(null);
const useDiscount = () => {
const strat = useContext(DiscountContext);
if (!strat) throw new Error("Discount provider missing");
return strat;
};
// 顯示價格元件
function Price({ value }: { value: Money }) {
const [showDisc, setShowDisc] = useState(false);
const discount = useDiscount();
// 過度抽象,必須透過策略函式計算價格
const display = useMemo(
() => (showDisc ? discount(value) : value),
[showDisc, value, discount]
);
return (
<div className="p-4 border rounded-xl">
<div>價格:${display.amount} {display.currency}</div>
<button
onClick={() => setShowDisc(v => !v)}
className="mt-2 px-3 py-1 rounded bg-blue-600 text-white"
>
{showDisc ? "隱藏折扣" : "顯示折扣"}
</button>
</div>
);
}
// 主組件
export default function ProductCard() {
// 預先設計「可替換策略」但其實當前只用到 20% 折扣
const provider = percentageOff(0.2);
return (
<DiscountContext.Provider value={provider}>
<Price value={{ amount: 1000, currency: "TWD" }} />
</DiscountContext.Provider>
);
}
簡單的說法就是,我們為了「可能永遠不會出現的需求」,讓程式碼變得過度複雜。
現在讓我們看看遵循 YAGNI 原則的寫法:
import React, { useState } from "react";
type Money = { amount: number; currency: "TWD" };
export default function ProductCard() {
const price: Money = { amount: 1000, currency: "TWD" };
const [showDisc, setShowDisc] = useState(false);
// 目前唯一需求:顯示時打 8 折;未來真的需要多規則再重構
const displayAmount = showDisc ? Math.round(price.amount * 0.8) : price.amount;
return (
<div className="p-4 border rounded-xl">
<div>價格:${displayAmount} {price.currency}</div>
<button onClick={() => setShowDisc(v => !v)} className="mt-2 px-3 py-1 rounded bg-blue-600 text-white">
{showDisc ? "隱藏折扣" : "顯示折扣"}
</button>
</div>
);
}
這時候我們可以發現,YAGNI 原則不是反對設計,而是反對過早的、不必要的設計。當我們真的需要多種折扣策略時,TypeScript 的型別系統和良好的測試覆蓋率,會讓重構變得相對安全。
需要注意的是,YAGNI 原則也有其限制。對於那些成本極高且難以後續添加的功能(例如資料庫設計、API 版本控制),適度的設計是必要的。
YAGNI 原則強調在開發過程中應專注於當下真正需要的功能,避免為尚未出現的情境預先開發不必要的程式碼。我們透過也許還不會出現的未來需求進行過度設計顯然是不必要的,我們可以透過延遲決策,將設計上的選擇推遲到擁有更多資訊的時刻,可以降低浪費與錯誤判斷的風險。同時在撰寫保持程式碼的時候保持簡潔,讓系統更容易進行重構與維護,換句話說,我們要不斷的在需要在簡潔性與擴展性之間取得適當的平衡,既不因過度設計而增加負擔,也不因過度簡化而犧牲未來的可擴充性。