軟體設計

YAGNI 總結:開發者認為「以後應該會用到」為何代價高昂

「之後好像會需要,要不要先做起來?」

閱讀 3 分鐘
YAGNI 總結:開發者認為「以後應該會用到」為何代價高昂 封面圖

「之後好像會需要,要不要先做起來?」

開發時,這個誘惑真的非常常見。先預留設定選項、先搭好多語支援的架構,甚至先建立資料表,讓管理介面可以修改。

今天要介紹的 YAGNI,正是正面對這個誘惑回答「不要」的原則。它和 KISS、DRY 一起被稱為三大開發原則。

YAGNI 是「You Aren’t Gonna Need It」的縮寫,意思是「那個反正不會需要」。

這個詞源自極限程式設計(XP)這套開發方法;Kent Beck、Ron Jeffries 等 XP 陣營的人經常掛在嘴邊,最後逐漸成為一項原則。

核心主張只有一個。

功能應該在實際需要時才實作,而不是預測可能需要時就先做。


先做起來到底有什麼問題?

你可能馬上會反駁:「先做起來,之後不是比較方便嗎?」問題在於,這種預測大多數時候都會錯。

Martin Fowler 在整理這個主題的文章中,將預先製作功能的成本分成四種。

成本 內容
實作成本 花在製作目前立即用不到的功能上的時間
延遲成本 原本應該利用這段時間製作的真正功能被延後
維護成本 即使是不使用的程式碼,測試、重構與修正錯誤仍會持續跟著來
修繕成本 真正需要時,需求已經改變,因此必須拆掉重做

最痛的是最後一項。「之後」真的到來時,那時的需求幾乎總是和自己預想的樣子不同。結果連重做預先製作程式碼的成本也得付兩次。


YAGNI 在實務中被打破的場景

用話說大家都會點頭,但在實戰中,它會悄悄以這些形式出現。

1. 先預留參數

// 目前只有電子郵件通知
func sendNotification(user: User, message: String, channel: String = "email",
                      retryCount: Int = 3, priority: String = "normal",
                      template: String? = nil) {
    // channel時才會運作的程式碼 "email"
}

所有呼叫端都只有sendNotification(user: user, message: msg)。其他參數原本是為了「之後要接 Slack 通知時使用」而建立,但 Slack 通知的需求始終沒有出現。

2. 先建立協定

明明只有一個實作,卻先建立協定的情況。這是建立一個UserService時,同時建立UserServiceProtocol的模式;在第二個實作出現之前,該協定只是增加檔案數量的裝飾品。

3. 「抽成設定不是比較好嗎?」

因為害怕硬編碼,就把所有值抽到設定檔和管理介面。實際上,一年甚至不會改一次這些設定;設定越多,只會增加「修改這個值會影響到哪裡?」這類問題。

不用的選項也會持續產生維護成本
不用的選項也會持續產生維護成本

哪些不屬於 YAGNI

為了避免誤解,先劃清界線。YAGNI 不是以下意思。

首先,它不是叫你不要設計。讓程式碼容易修改的努力(好的命名、小型函式、測試)現在就有價值,因此不屬於 YAGNI。反而應該讓程式碼容易修改,才能在「真正需要的時候」快速完成。

它也不是叫你連難以回頭的決策都延後。像是資料庫選擇或 API 公開規格這類一旦決定就難以更改的事項,本來就應該事先思考。YAGNI 所針對的是「明明之後很容易追加,卻先做起來的功能」。


我採用的判斷標準

當我想著「要不要先做起來?」時,會問自己兩個問題。

這項功能必要的依據是在路線圖上,還是在我的想像裡?

之後追加的成本會不會比現在高出很多?

如果依據只是想像,而且之後追加的成本差不多,我就不做。實務上大多數情況都屬於這一類。

只要兩個問題,就足以做出判斷
只要兩個問題,就足以做出判斷
等到需要的那天再做,也不會太遲
等到需要的那天再做,也不會太遲

總結

從 YAGNI 原則中可以帶走三件事。

  • 功能請在需要時製作,而不是覺得可能需要時就先做。
  • 預先製作的功能會讓你付出實作、延遲、維護、修繕四重成本。
  • 不過,良好的設計習慣與難以回頭的決策不屬於 YAGNI。

把 KISS、DRY 和 YAGNI 放在一起看,三項原則最後可以濃縮成一句話:今天需要的東西,簡單地,只做在一個地方。

如果有任何一項功能是抱著「以後應該會用到」的想法做完後就擱著,也許 2 年後你會親手把它刪掉。