iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
自我挑戰組

程式碼門診:診斷壞味道、開出重構處方系列 第 5

YAGNI 原則 - You Aren't Gonna Need It!

  • 分享至 

  • xImage
  •  

為什麼不需要的功能會成為負擔?

在軟體開發的過程中,我們常常幻想著把各種情況都考慮進去,然後一次全部寫好吧?不要這樣幹,這樣的寫法往往會導致過度設計,開發者容易陷入"未來可能需要"的思考模式,結果提前實作了許多當下根本不需要的功能。

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?也就是保持簡單的設計來滿足當前的需求,而軟體開發應當以小量增加並且漸進式開發來實現逐

違反 YAGNI 原則的範例

讓我們舉例一個過度設計案例。假設我們要做一個卡片,目前只需要一個簡單的 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>
  );
}

這段程式碼有什麼問題?

  1. 過度抽象:為了「未來可能的多種折扣策略」,引入了 Strategy Pattern、Context API
  2. 維護負擔:當前只有一種折扣,卻要維護整套策略系統
  3. 認知負載:新進開發者需要理解不必要的抽象層,增加學習成本

簡單的說法就是,我們為了「可能永遠不會出現的需求」,讓程式碼變得過度複雜。

遵循 YAGNI 原則的修正範例

現在讓我們看看遵循 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 原則強調在開發過程中應專注於當下真正需要的功能,避免為尚未出現的情境預先開發不必要的程式碼。我們透過也許還不會出現的未來需求進行過度設計顯然是不必要的,我們可以透過延遲決策,將設計上的選擇推遲到擁有更多資訊的時刻,可以降低浪費與錯誤判斷的風險。同時在撰寫保持程式碼的時候保持簡潔,讓系統更容易進行重構與維護,換句話說,我們要不斷的在需要在簡潔性與擴展性之間取得適當的平衡,既不因過度設計而增加負擔,也不因過度簡化而犧牲未來的可擴充性。

參考資料

上一篇
Keep It Simple, Stupid 原則
下一篇
讓函式只做一件事 - 單一功能原則 (Single Responsibility Principle)
系列文
程式碼門診:診斷壞味道、開出重構處方7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言