Yagni
2015 年 5 月 26 日
Yagni 最初是「You Aren't Gonna Need It」的縮寫。這是來自 極限程式設計 的口號,經常在敏捷軟體團隊中普遍使用。這句話表示我們假設軟體在未來需要的某些功能不應該現在就建置,因為「你不會需要它」。
Yagni 是指 XP 中的簡單設計實務(來自 白皮書 的第一版,第二版指的是相關的「增量設計」概念)。[1] 與 XP 的許多元素一樣,這與 90 年代後期廣泛認可的軟體工程原則形成鮮明對比。當時,人們大力推動在軟體開發之前進行仔細的規劃。
想像一下,我正在與米那斯提力斯的一家新創公司合作,為航運業務銷售保險。他們的軟體系統分為兩個主要元件:一個用於定價,一個用於銷售。相依性是,在相關的定價軟體完成之前,他們無法有效建置銷售軟體。
目前,團隊正在更新定價元件,以增加對暴風雨風險的支援。他們知道六個月後,他們還需要支援海盜風險的定價。由於他們目前正在開發定價引擎,因此他們考慮現在就建置海盜定價的假設功能[2],因為這樣定價服務在他們開始開發銷售軟體之前就會完成。
Yagni 反對這種做法,它表示,由於你六個月內不需要海盜定價,因此在有必要之前不應該建置它。因此,如果你認為建置這個軟體需要兩個月,那麼你應該再過四個月才開始(忽略排程風險和更新銷售元件的任何緩衝時間)。
Yagni 的第一個論點是,雖然我們現在可能認為我們需要這個假設功能,但我們很可能會錯。畢竟,敏捷方法的背景是接受我們歡迎變更需求。以計畫為導向的需求大師可能會反駁,這是因為我們沒有做好需求分析,我們應該投入更多時間和精力在上面。我反駁這一點,指出預先找出你的需求有多麼困難和昂貴,但即使你可以,當剛鐸海軍消滅海盜,從而破壞整個商業模式時,你仍然可能措手不及。
在這種情況下,預設功能的成本很明顯,就是建置成本:分析、程式設計和測試這個現在無用的功能所花費的所有心力。
但讓我們假設我們完全正確地了解我們的需求,而且剛鐸海軍沒有消滅海盜。即使在這種樂觀的情況下,建置預設功能也會產生兩個嚴重的成本。第一個成本是延遲價值的成本。由於將我們的精力花費在防盜定價軟體上,我們並沒有建置其他功能。如果我們將精力投入建置天氣風險的銷售軟體,我們就可以在兩個月前將完整的暴風雨風險功能投入生產並產生收益。由於預設功能而產生的這個延遲成本是兩個月的暴風雨保險收益。
人們建置預設功能的常見原因是,他們認為現在建置會比以後建置便宜。但這種成本比較必須至少針對延遲成本進行,最好考慮到建置不必要功能的機率,而你的機率至少為 ⅔。 [3]
人們通常不會考慮現在建置與以後建置的比較成本。我在這種情況下指導開發人員時使用的一種方法是,要求他們想像改構,他們需要在需要時引入功能。通常,這種思想實驗足以說服他們,以後新增功能並不會顯著增加成本。這種想像的另一個結果是新增一些現在很容易做、增加的複雜性很小,但顯著降低以後成本的事情。使用查閱表來取得錯誤訊息,而不是內嵌文字,就是一個簡單但可以讓以後的翻譯更容易支援的範例。
提醒一下,任何從未使用的擴充性點不僅是浪費精力,也很可能會阻礙你的進度
延遲成本是成功假設功能所造成的成本之一,而另一個則是持有成本。假設功能的程式碼會為軟體增加一些複雜性,此複雜性會讓修改和除錯該軟體變得更困難,進而增加其他功能的成本。在軟體中加入防盜版定價功能所產生的額外複雜性,可能會讓建立暴風雨保險銷售元件的時間增加數週。這兩週會產生兩種影響:建立功能的額外成本,以及由於花費較長時間投入生產而產生的額外延遲成本。我們會在現在和防盜版保險軟體開始發揮作用的時間之間建立的每個功能上產生持有成本。如果我們永遠不需要防盜版定價軟體,我們會在建立每個功能時產生持有成本,直到我們移除防盜版定價功能(假設我們會移除)以及移除它的成本。
到目前為止,我已將假設功能分為兩類:成功和不成功。當然,實際上是一個範圍,而範圍中有一個值得強調的點:正確的功能建立錯誤。開發團隊總是在學習,關於他們的使用者和他們的程式碼庫。他們會學習他們正在使用的工具,而這些工具會定期升級。他們也會學習他們的程式碼如何一起運作。所有這些都表示,你經常會發現六個月前編寫的功能,並非以你現在了解應該執行的方式完成。在這種情況下,你已累積技術債務,並且必須考慮該功能的修復成本或因應其困難所產生的持續成本。
因此,我們最終得到三類假設功能,以及當你忽略 yagni 時會發生的四種成本。

我的保險範例說明了相對使用者可見的功能,但相同的論點也適用於支援未來彈性的抽象化。在建立暴風雨風險計算器時,你可能會考慮現在加入抽象化和參數化,以在稍後支援防盜版和其他風險。Yagni 表示不要這麼做,因為你可能不需要其他定價功能,或者如果你需要,你目前的想法和實際需要時會了解的抽象化需求並不相符。這並不表示要放棄所有抽象化,但這表示任何讓了解程式碼以符合目前需求變得更困難的抽象化都被視為有罪。
Yagni 在較大的功能中會最為明顯,但你會更頻繁地在小事物中看到它。最近我寫了一些程式碼,允許我突顯程式碼行的一部分。為此,我允許使用正規表示法指定突顯的程式碼。我看到的一個問題是,由於整個正規表示法都被突顯,因此我無法處理需要正規表示法比我想突顯的更大區段的情況。我預計我可以透過在正規表示法中使用群組,並讓我的程式碼僅在存在群組時突顯該群組來解決這個問題。但我還不需要使用比我突顯的內容更多的正規表示法,所以我沒有擴充我的突顯程式碼來處理這個情況,而且在實際需要之前我不會這麼做。基於類似的理由,在我實際準備好使用欄位或方法之前,我不會新增它們。
像這樣的 yagni 小決定會在專案規劃中被忽略。作為開發人員,很容易花一個小時新增一個抽象,我們確定很快就會需要它。然而,上述所有論點仍然適用,而且許多 yagni 小決定加起來會大幅降低程式碼庫的複雜性,同時加快更急需的功能的交付。
現在我們了解 yagni 為何重要,我們可以深入探討關於 yagni 的常見混淆。Yagni 僅適用於內建在軟體中以支援假設功能的能力,它不適用於讓軟體更容易修改的努力。只有在程式碼容易變更時,yagni 才是一種可行的策略,因此在重構上投入精力並不違反 yagni,因為重構讓程式碼更具可塑性。類似的推理適用於 SelfTestingCode 和 ContinuousDelivery 等做法。這些是促進演化設計的做法,沒有它們,yagni 會從有益的做法變成詛咒。但是,如果你確實有一個可塑的程式碼庫,那麼 yagni 會強化這種靈活性。Yagni 具有既受演化設計啟發,又啟發演化設計的奇特特性。
Yagni 並不是忽視程式碼庫健全性的理由。Yagni 需要(並啟用)可塑的程式碼。
我也認為 yagni 僅適用於你現在引入額外複雜性,但直到以後才會利用它。如果你為未來的需求做了一些事情,而這並未實際增加軟體的複雜性,那麼就沒有理由援用 yagni。
說了這麼多,在應用 yagni 時還是會遇到問題,而且你會面臨昂貴的變更,而早先的變更會便宜得多。棘手的問題在於,這些案例很難事先發現,而且比 yagni 省力的案例更容易記住 [4]。我的感覺是,yagni 失效的情況相對少見,而其成本很容易被 yagni 成功時所抵銷。
備註
1: 此短語的起源是 Kent Beck 和 Chet Hendrickson 在 C3 專案上的早期對話。Chet 向 Kent 提出系統很快就會需要的幾項功能,Kent 對每一項都回答「你不會需要它」。Chet 學習很快,並迅速以發現應用 yagni 機會的能力而聞名。儘管「yagni」最初是縮寫,但我認為它現在已作為一個常規詞彙進入我們的詞彙中,因此放棄了大寫字母。
2: 在這篇文章中,我使用「假設功能」來指支援尚未可供使用的功能的任何程式碼。
3: ⅔ 這個數字是由 Kohavi 等人 提出,他們分析了在微軟產品上建置和部署的功能的價值,發現即使經過仔細的前期分析,只有 ⅓ 的功能改善了它們旨在改善的指標。
4: 這是可用性偏差的後果