<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Yagni on Tarragon</title><link>https://tarrragon.github.io/blog/tags/yagni/</link><description>Recent content in Yagni on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 05 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/yagni/index.xml" rel="self" type="application/rss+xml"/><item><title>設計瑕疵還是避免過度設計？YAGNI 的真實適用條件</title><link>https://tarrragon.github.io/blog/record/%E8%A8%AD%E8%A8%88%E7%91%95%E7%96%B5%E9%82%84%E6%98%AF%E9%81%BF%E5%85%8D%E9%81%8E%E5%BA%A6%E8%A8%AD%E8%A8%88yagni-%E7%9A%84%E7%9C%9F%E5%AF%A6%E9%81%A9%E7%94%A8%E6%A2%9D%E4%BB%B6/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E8%A8%AD%E8%A8%88%E7%91%95%E7%96%B5%E9%82%84%E6%98%AF%E9%81%BF%E5%85%8D%E9%81%8E%E5%BA%A6%E8%A8%AD%E8%A8%88yagni-%E7%9A%84%E7%9C%9F%E5%AF%A6%E9%81%A9%E7%94%A8%E6%A2%9D%E4%BB%B6/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>核心命題&lt;/strong>：YAGNI 不是「永遠選最受限選項」的原則，是「不為未來投入額外成本」的原則。
&lt;strong>判斷工具&lt;/strong>：成本對稱性、可逆性、領域先驗——三軸框架。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="起點一個常見的工程爭論">起點：一個常見的工程爭論&lt;/h2>
&lt;p>「最早的設計者沒考慮到多個監聽需求，這算設計瑕疵，還是避免過度設計？」&lt;/p>
&lt;p>這類問題在 code review、事故檢討、技術選型討論裡反覆出現。指控太重會打擊個別工程師的判斷力信心，放任又會讓同類事故反覆發生。&lt;/p>
&lt;p>要釐清這個爭論，得先回到 YAGNI 原則的真實定義——很多被當成 YAGNI 的例子根本不在它的射程內。&lt;/p>
&lt;hr>
&lt;h2 id="yagni-的真實範圍">YAGNI 的真實範圍&lt;/h2>
&lt;p>YAGNI（You Aren&amp;rsquo;t Gonna Need It）的原意是：&lt;strong>不要投入額外成本去蓋你尚未需要的東西&lt;/strong>。它防的是這類情境：&lt;/p>
&lt;ul>
&lt;li>「我先寫個 plugin 系統，未來可以擴充」（成本：協議設計、抽象層、擴充點測試）&lt;/li>
&lt;li>「我先做多語系，未來會國際化」（成本：i18n 框架、所有字串外移）&lt;/li>
&lt;li>「我先支援多資料庫」（成本：repository 抽象、SQL 方言處理）&lt;/li>
&lt;li>「我先建多租戶切割」（成本：資料 schema 加 tenant 欄位、所有 query 加過濾）&lt;/li>
&lt;/ul>
&lt;p>這些選擇的共通特徵是：&lt;strong>為了未來付出當下的具體成本&lt;/strong>——抽象層、額外測試、複雜配置、學習負擔。YAGNI 說：別付，等真正需要再付，因為很可能你永遠不需要。&lt;/p>
&lt;p>但很多被指控為「過度設計」的選擇其實&lt;strong>沒有 upfront cost 差異&lt;/strong>。例如：&lt;/p>
&lt;ul>
&lt;li>Stream 工具用單訂閱版本還是廣播版本：建構子多打 11 個字元&lt;/li>
&lt;li>&lt;code>var&lt;/code> 還是 &lt;code>final&lt;/code>：3 個字元&lt;/li>
&lt;li>ID 用 &lt;code>int&lt;/code> 還是 &lt;code>String&lt;/code>（UUID）：抽象層成本一樣&lt;/li>
&lt;li>API 設計成同步還是 async：簽章只差 &lt;code>Future&amp;lt;&amp;gt;&lt;/code> 包裝&lt;/li>
&lt;li>Class 預設可繼承還是 sealed：一個 modifier&lt;/li>
&lt;li>Database column 預設 nullable 還是 NOT NULL：一個 keyword&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>這些不在 YAGNI 的射程內&lt;/strong>。把它們當成 YAGNI 來防禦會選錯方向。&lt;/p>
&lt;hr>
&lt;h2 id="真正的判斷軸成本不對稱性">真正的判斷軸：成本不對稱性&lt;/h2>
&lt;p>判斷「該不該選更通用的選項」，跑三個軸。&lt;/p>
&lt;h3 id="軸-1成本對稱性">軸 1：成本對稱性&lt;/h3>
&lt;p>「選擇 A 比選擇 B 多付出多少當下成本？」&lt;/p>
&lt;ul>
&lt;li>&lt;strong>對稱&lt;/strong>（成本相當、差幾個字元、無新概念）：選&lt;strong>未來更可能需要&lt;/strong>的那個——這不是過度設計，是合理 default&lt;/li>
&lt;li>&lt;strong>不對稱&lt;/strong>（一邊明顯較貴、要多寫框架、多加抽象、多學概念）：YAGNI 適用，選便宜的，需要時再升級&lt;/li>
&lt;/ul>
&lt;h3 id="軸-2改變決定的成本">軸 2：改變決定的成本&lt;/h3>
&lt;p>「如果選錯了，未來修正要付出什麼？」&lt;/p>
&lt;ul>
&lt;li>&lt;strong>可逆&lt;/strong>（一行改完、無 API 契約變動、無資料遷移）：YAGNI 適用，先選簡單的&lt;/li>
&lt;li>&lt;strong>不可逆 / 修正昂貴&lt;/strong>（牽動 API 契約、資料庫 schema、客戶端版本相容性、第三方 integration）：偏向預先選擇通用的&lt;/li>
&lt;/ul>
&lt;h3 id="軸-3領域先驗domain-prior">軸 3：領域先驗（domain prior）&lt;/h3>
&lt;p>「這個領域裡、這個模式發生的機率有多高？」——「先驗」（prior）借自 Bayesian 統計、用來指「在沒看到具體證據前、我們對某事發生機率的合理預期」。在工程領域、這個機率來自累積的領域知識（多視角同步、retry、併發、認證⋯⋯這些 pattern 的歷史發生率）。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>強先驗&lt;/strong>（教科書級別）：多視角狀態同步是廣播、有用戶系統一定有 logged-in / anonymous 兩種、長時間運行服務一定會有 retry 需求、有交易就會有併發&lt;/li>
&lt;li>&lt;strong>弱先驗&lt;/strong>（純臆測）：「未來可能會有 plugin 機制吧」「未來可能要換資料庫吧」「未來可能要支援其他平台吧」&lt;/li>
&lt;/ul>
&lt;h3 id="三軸的綜合判斷">三軸的綜合判斷&lt;/h3>
&lt;p>任一軸顯著偏向「該選通用」，YAGNI 就不適用。&lt;/p>
&lt;p>&lt;strong>選通用不是過度設計，是對工具屬性與領域常識的尊重&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="案例對照兩個極端">案例對照：兩個極端&lt;/h2>
&lt;h3 id="案例-astream-預設選錯">案例 A：Stream 預設選錯&lt;/h3>
&lt;p>某個事件廣播 service 用了 &lt;code>StreamController()&lt;/code> 預設建構子（單訂閱）。當下只有一個訂閱者，運作正常數個月。後來加第二個訂閱者，瞬間 throw &lt;code>Bad state: Stream has already been listened to&lt;/code>。&lt;/p>
&lt;p>跑三軸：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>成本對稱性&lt;/strong>：對稱（差 11 個字元、零認知負擔）&lt;/li>
&lt;li>&lt;strong>可逆性&lt;/strong>：中等偏高（事故必須在 production 暴露才會發現，要審所有訂閱方、改實作 + mock）&lt;/li>
&lt;li>&lt;strong>領域先驗&lt;/strong>：強（pub-sub / 事件廣播場景天生多訂閱）&lt;/li>
&lt;/ul>
&lt;p>三軸都指向廣播版本。&lt;strong>這是設計瑕疵&lt;/strong>——不是因為「沒考慮多訂閱」，而是&lt;strong>在三軸都不利於單訂閱的情況下選了單訂閱&lt;/strong>。&lt;/p>
&lt;blockquote>
&lt;p>完整事故重現、單訂閱 vs broadcast 的程式碼對比、修復決策過程：&lt;a href="https://tarrragon.github.io/blog/work-log/dart-streamcontrollersingle-subscription-vs-broadcast-%E7%9A%84%E8%A8%AD%E8%A8%88%E9%81%B8%E5%9E%8B%E5%95%8F%E9%A1%8C/" data-link-title="Dart StreamController：single-subscription vs broadcast 的設計選型問題" data-link-desc="Dart `Bad state: Stream has already been listened to.` 的根因：預設單訂閱在第二個訂閱者出現時才爆。StreamController vs .broadcast() 修復決策、與 Rx / .obs 的比較。">Dart StreamController：single-subscription vs broadcast 的事故實錄&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h3 id="案例-b建立-plugin-系統">案例 B：建立 plugin 系統&lt;/h3>
&lt;p>「我先建個 plugin 系統，未來功能模組可以動態擴充」——典型的 over-engineering 焦慮表現。&lt;/p>
&lt;p>跑三軸：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>成本對稱性&lt;/strong>：嚴重不對稱（plugin 系統需要設計協議、加載機制、版本管理、隔離測試）&lt;/li>
&lt;li>&lt;strong>可逆性&lt;/strong>：可逆（之後要做的話成本跟現在做差不多）&lt;/li>
&lt;li>&lt;strong>領域先驗&lt;/strong>：弱（多數應用程式不會有第三方擴充需求）&lt;/li>
&lt;/ul>
&lt;p>三軸都指向「先別做」。&lt;strong>這是 YAGNI 的標準適用情境&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>核心命題</strong>：YAGNI 不是「永遠選最受限選項」的原則，是「不為未來投入額外成本」的原則。
<strong>判斷工具</strong>：成本對稱性、可逆性、領域先驗——三軸框架。</p></blockquote>
<hr>
<h2 id="起點一個常見的工程爭論">起點：一個常見的工程爭論</h2>
<p>「最早的設計者沒考慮到多個監聽需求，這算設計瑕疵，還是避免過度設計？」</p>
<p>這類問題在 code review、事故檢討、技術選型討論裡反覆出現。指控太重會打擊個別工程師的判斷力信心，放任又會讓同類事故反覆發生。</p>
<p>要釐清這個爭論，得先回到 YAGNI 原則的真實定義——很多被當成 YAGNI 的例子根本不在它的射程內。</p>
<hr>
<h2 id="yagni-的真實範圍">YAGNI 的真實範圍</h2>
<p>YAGNI（You Aren&rsquo;t Gonna Need It）的原意是：<strong>不要投入額外成本去蓋你尚未需要的東西</strong>。它防的是這類情境：</p>
<ul>
<li>「我先寫個 plugin 系統，未來可以擴充」（成本：協議設計、抽象層、擴充點測試）</li>
<li>「我先做多語系，未來會國際化」（成本：i18n 框架、所有字串外移）</li>
<li>「我先支援多資料庫」（成本：repository 抽象、SQL 方言處理）</li>
<li>「我先建多租戶切割」（成本：資料 schema 加 tenant 欄位、所有 query 加過濾）</li>
</ul>
<p>這些選擇的共通特徵是：<strong>為了未來付出當下的具體成本</strong>——抽象層、額外測試、複雜配置、學習負擔。YAGNI 說：別付，等真正需要再付，因為很可能你永遠不需要。</p>
<p>但很多被指控為「過度設計」的選擇其實<strong>沒有 upfront cost 差異</strong>。例如：</p>
<ul>
<li>Stream 工具用單訂閱版本還是廣播版本：建構子多打 11 個字元</li>
<li><code>var</code> 還是 <code>final</code>：3 個字元</li>
<li>ID 用 <code>int</code> 還是 <code>String</code>（UUID）：抽象層成本一樣</li>
<li>API 設計成同步還是 async：簽章只差 <code>Future&lt;&gt;</code> 包裝</li>
<li>Class 預設可繼承還是 sealed：一個 modifier</li>
<li>Database column 預設 nullable 還是 NOT NULL：一個 keyword</li>
</ul>
<p><strong>這些不在 YAGNI 的射程內</strong>。把它們當成 YAGNI 來防禦會選錯方向。</p>
<hr>
<h2 id="真正的判斷軸成本不對稱性">真正的判斷軸：成本不對稱性</h2>
<p>判斷「該不該選更通用的選項」，跑三個軸。</p>
<h3 id="軸-1成本對稱性">軸 1：成本對稱性</h3>
<p>「選擇 A 比選擇 B 多付出多少當下成本？」</p>
<ul>
<li><strong>對稱</strong>（成本相當、差幾個字元、無新概念）：選<strong>未來更可能需要</strong>的那個——這不是過度設計，是合理 default</li>
<li><strong>不對稱</strong>（一邊明顯較貴、要多寫框架、多加抽象、多學概念）：YAGNI 適用，選便宜的，需要時再升級</li>
</ul>
<h3 id="軸-2改變決定的成本">軸 2：改變決定的成本</h3>
<p>「如果選錯了，未來修正要付出什麼？」</p>
<ul>
<li><strong>可逆</strong>（一行改完、無 API 契約變動、無資料遷移）：YAGNI 適用，先選簡單的</li>
<li><strong>不可逆 / 修正昂貴</strong>（牽動 API 契約、資料庫 schema、客戶端版本相容性、第三方 integration）：偏向預先選擇通用的</li>
</ul>
<h3 id="軸-3領域先驗domain-prior">軸 3：領域先驗（domain prior）</h3>
<p>「這個領域裡、這個模式發生的機率有多高？」——「先驗」（prior）借自 Bayesian 統計、用來指「在沒看到具體證據前、我們對某事發生機率的合理預期」。在工程領域、這個機率來自累積的領域知識（多視角同步、retry、併發、認證⋯⋯這些 pattern 的歷史發生率）。</p>
<ul>
<li><strong>強先驗</strong>（教科書級別）：多視角狀態同步是廣播、有用戶系統一定有 logged-in / anonymous 兩種、長時間運行服務一定會有 retry 需求、有交易就會有併發</li>
<li><strong>弱先驗</strong>（純臆測）：「未來可能會有 plugin 機制吧」「未來可能要換資料庫吧」「未來可能要支援其他平台吧」</li>
</ul>
<h3 id="三軸的綜合判斷">三軸的綜合判斷</h3>
<p>任一軸顯著偏向「該選通用」，YAGNI 就不適用。</p>
<p><strong>選通用不是過度設計，是對工具屬性與領域常識的尊重</strong>。</p>
<hr>
<h2 id="案例對照兩個極端">案例對照：兩個極端</h2>
<h3 id="案例-astream-預設選錯">案例 A：Stream 預設選錯</h3>
<p>某個事件廣播 service 用了 <code>StreamController()</code> 預設建構子（單訂閱）。當下只有一個訂閱者，運作正常數個月。後來加第二個訂閱者，瞬間 throw <code>Bad state: Stream has already been listened to</code>。</p>
<p>跑三軸：</p>
<ul>
<li><strong>成本對稱性</strong>：對稱（差 11 個字元、零認知負擔）</li>
<li><strong>可逆性</strong>：中等偏高（事故必須在 production 暴露才會發現，要審所有訂閱方、改實作 + mock）</li>
<li><strong>領域先驗</strong>：強（pub-sub / 事件廣播場景天生多訂閱）</li>
</ul>
<p>三軸都指向廣播版本。<strong>這是設計瑕疵</strong>——不是因為「沒考慮多訂閱」，而是<strong>在三軸都不利於單訂閱的情況下選了單訂閱</strong>。</p>
<blockquote>
<p>完整事故重現、單訂閱 vs broadcast 的程式碼對比、修復決策過程：<a href="/blog/work-log/dart-streamcontrollersingle-subscription-vs-broadcast-%E7%9A%84%E8%A8%AD%E8%A8%88%E9%81%B8%E5%9E%8B%E5%95%8F%E9%A1%8C/" data-link-title="Dart StreamController：single-subscription vs broadcast 的設計選型問題" data-link-desc="Dart `Bad state: Stream has already been listened to.` 的根因：預設單訂閱在第二個訂閱者出現時才爆。StreamController vs .broadcast() 修復決策、與 Rx / .obs 的比較。">Dart StreamController：single-subscription vs broadcast 的事故實錄</a>。</p></blockquote>
<h3 id="案例-b建立-plugin-系統">案例 B：建立 plugin 系統</h3>
<p>「我先建個 plugin 系統，未來功能模組可以動態擴充」——典型的 over-engineering 焦慮表現。</p>
<p>跑三軸：</p>
<ul>
<li><strong>成本對稱性</strong>：嚴重不對稱（plugin 系統需要設計協議、加載機制、版本管理、隔離測試）</li>
<li><strong>可逆性</strong>：可逆（之後要做的話成本跟現在做差不多）</li>
<li><strong>領域先驗</strong>：弱（多數應用程式不會有第三方擴充需求）</li>
</ul>
<p>三軸都指向「先別做」。<strong>這是 YAGNI 的標準適用情境</strong>。</p>
<h3 id="兩個案例的對比">兩個案例的對比</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>成本對稱性</th>
          <th>可逆性</th>
          <th>領域先驗</th>
          <th>該怎麼選</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Stream 預設</td>
          <td>對稱</td>
          <td>中等偏高</td>
          <td>強</td>
          <td>提前選通用</td>
      </tr>
      <tr>
          <td>Plugin 系統</td>
          <td>嚴重不對稱</td>
          <td>可逆</td>
          <td>弱</td>
          <td>YAGNI（先別做）</td>
      </tr>
  </tbody>
</table>
<p>兩者表面看都是「未來可能需要」，但三軸框架告訴你它們是<strong>完全不同類別</strong>的決定。一概而論「該/不該為未來準備」會兩邊都做錯。</p>
<hr>
<h2 id="為什麼這類瑕疵可被原諒">為什麼這類瑕疵「可被原諒」</h2>
<p>要老實講：<strong>指出某個選擇是設計瑕疵，不等於把責任全部推給個別工程師</strong>。</p>
<p>同類型瑕疵在實務上極常見，原因往往是系統性陷阱。</p>
<h3 id="1-語言--工具的預設值誤導">1. 語言 / 工具的預設值誤導</h3>
<p>很多語言把「需要明確選擇」的東西做成「最少打字的預設」：</p>
<ul>
<li>Dart 的 <code>StreamController()</code> 是 single-subscription</li>
<li>多數 SQL 的 column 預設 nullable</li>
<li>JavaScript 的 <code>==</code> 預設寬鬆比對</li>
<li>多數語言的 class 預設可繼承</li>
<li>HTTP 預設不加密</li>
<li>多數語言的 mutable 是 default</li>
</ul>
<p>這些預設都把多數人推向「比較容易出錯但不立即爆」的選項。<strong>API 設計把成本均衡的選擇做成「便宜便輸出受限」vs「貴一點輸出通用」是 framework 設計的責任轉嫁</strong>——把跨用例的判斷成本丟給用戶。</p>
<h3 id="2-領域知識需要被觸發過才會內化">2. 領域知識需要被觸發過才會內化</h3>
<p>很多事是遇過一次才會記得。「stream 預設是單訂閱」「nullable column 之後加 NOT NULL 要 backfill」「同步 API 之後改 async 是 breaking change」——這些不是經驗少的問題，是這些事實<strong>需要遇到才會內化進直覺判斷</strong>。</p>
<p>新人讀文件不會看到、code review 不會自動 catch、靜態分析不會主動警告——只能等某次遇到。</p>
<h3 id="3-失敗模式的低調性掩蓋風險">3. 失敗模式的低調性掩蓋風險</h3>
<p>很多設計瑕疵的失敗模式只在特定觸發條件下顯現：</p>
<ul>
<li>Stream 多訂閱限制只在第二次 <code>listen()</code> 時暴露</li>
<li>Mutable shared state 的 race condition 只在高併發下爆</li>
<li>Cache 失效邏輯只在 cache miss 模式變化時出問題</li>
<li>API 沒做 idempotent 只在重試時出現重複</li>
</ul>
<p>平常測試跑都過，給人「沒問題」的錯覺。<strong>沒有立即反饋的設計瑕疵 = 隱形的技術債</strong>。</p>
<h3 id="4-工具替代品掩蓋知識需求">4. 工具替代品掩蓋知識需求</h3>
<p>有些底層概念被高層框架封裝後，使用者根本不會碰到，所以「應該知道」的知識沒有被反覆強化。例如：</p>
<ul>
<li>Flutter 開發者多用 GetX / Riverpod / Bloc，極少碰 raw <code>StreamController</code></li>
<li>ORM 用戶多不寫 SQL，極少思考 query plan</li>
<li>雲端 SDK 用戶多不思考 retry / backoff，極少接觸底層 HTTP</li>
</ul>
<p>當有一天必須繞過框架直接用底層工具時，那個事故就會發生。</p>
<h3 id="結論">結論</h3>
<p>設計者只承擔最後一棒。要把同類瑕疵變少，<strong>修補方向在制度層面</strong>。</p>
<hr>
<h2 id="制度層面的補強">制度層面的補強</h2>
<p>要把「該選通用 default 但選了受限預設」的錯誤變少，個人記憶不可靠，要靠三層機制。</p>
<h3 id="機制-1介面層的-review-checklist">機制 1：介面層的 review checklist</h3>
<p>把容易出錯的 default 列入 PR review 檢查清單。例如：</p>
<ul>
<li>Service 對外暴露 <code>Stream&lt;T&gt;</code> 時、預設用 broadcast；用 single 要在註解寫明理由</li>
<li>資料庫 column 預設用 NOT NULL；nullable 要在註解寫明業務理由</li>
<li>公開 API 預設用 async；sync 要寫明理由</li>
<li>公開類別預設用 sealed / final；可繼承要寫明理由</li>
<li>HTTP 預設用 HTTPS；plain HTTP 要寫明理由</li>
</ul>
<p>把「需要記得」變成「review 強制檢查」。Checklist 不需要多，每個項目對應一個遇過的事故。</p>
<h3 id="機制-2架構規範把選擇從-default-取消">機制 2：架構規範把選擇從 default 取消</h3>
<p>更徹底的做法是用工具或規範<strong>禁掉問題 default</strong>：</p>
<ul>
<li>App 層 service 禁用 raw <code>StreamController</code>，強制用框架的廣播原語</li>
<li>用 lint rule 警告 <code>StreamController()</code> 的無參數呼叫</li>
<li>DB schema migration 工具預設產出 NOT NULL，nullable 要明確指定</li>
<li>API gateway 預設 deny，要顯式 allow 才放行</li>
</ul>
<p>這把選擇從「需要記得」變成「<strong>不需要選，做錯會被擋</strong>」。是最高效的補強。</p>
<h3 id="機制-3領域先驗清單">機制 3：領域先驗清單</h3>
<p>每個團隊應該維護一份「<strong>我們的領域裡這些事一定會發生</strong>」的清單。範例：</p>
<p>POS 系統：</p>
<ul>
<li>一台主機要服務多視角（多顯示螢幕、多通知模組）</li>
<li>會員身份會即時切換</li>
<li>有離線運作需求</li>
<li>多分店不同設定</li>
</ul>
<p>電商：</p>
<ul>
<li>商品價格會變動，歷史訂單要保留下單當時的價格</li>
<li>庫存會超賣，需要 reserve / commit 機制</li>
<li>退款是必然發生的，不是 edge case</li>
<li>客戶會有多個收件地址</li>
</ul>
<p>新功能設計時對照清單——強領域先驗就直接設計進去，<strong>不必每次重新評估</strong>。新進團隊成員也能快速吸收領域常識。</p>
<hr>
<h2 id="一個能套到無數情境的-heuristic">一個能套到無數情境的 heuristic</h2>
<p>把整個討論濃縮成一句話：</p>
<blockquote>
<p>當你的選擇「<strong>沒有 upfront cost 差異</strong>」時、就該選未來自由度高的那個。</p></blockquote>
<p>這個 heuristic 能套到無數技術決定：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>「便宜但受限」</th>
          <th>「同樣便宜但通用」</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Stream 廣播</td>
          <td><code>StreamController()</code></td>
          <td><code>StreamController.broadcast()</code></td>
      </tr>
      <tr>
          <td>集合不可變性</td>
          <td><code>var list = [1, 2]</code></td>
          <td><code>final list = const [1, 2]</code></td>
      </tr>
      <tr>
          <td>API 回傳值</td>
          <td>同步 method</td>
          <td><code>Future&lt;&gt;</code> 包裝</td>
      </tr>
      <tr>
          <td>函式參數</td>
          <td>positional args</td>
          <td>named args</td>
      </tr>
      <tr>
          <td>Class 設計</td>
          <td>預設可繼承</td>
          <td><code>sealed</code> / <code>final class</code></td>
      </tr>
      <tr>
          <td>Resource handle</td>
          <td>manual cleanup</td>
          <td>RAII / <code>using</code> block</td>
      </tr>
      <tr>
          <td>Time</td>
          <td>local time</td>
          <td>UTC + timezone metadata</td>
      </tr>
      <tr>
          <td>ID 型別</td>
          <td><code>int</code> auto-increment</td>
          <td><code>String</code> (UUID)</td>
      </tr>
      <tr>
          <td>Money</td>
          <td><code>double</code></td>
          <td>專用 <code>Decimal</code> 型別</td>
      </tr>
      <tr>
          <td>字串編碼</td>
          <td>平台預設</td>
          <td>顯式 UTF-8</td>
      </tr>
  </tbody>
</table>
<p>這些都不是「過度設計」，是<strong>在零成本差異下選擇未來自由度更高的選項</strong>。YAGNI 不適用——YAGNI 的成本門檻在這裡根本不存在。</p>
<hr>
<h2 id="反向校正什麼時候該堅持-yagni">反向校正：什麼時候該堅持 YAGNI？</h2>
<p>為了避免本文被讀成「永遠選通用」，補一個反向案例。</p>
<p>YAGNI 在這些情境是對的：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>為什麼 YAGNI 適用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「先做個 admin 後台，未來方便」</td>
          <td>成本巨大，需求未確認，可逆</td>
      </tr>
      <tr>
          <td>「先支援自訂主題系統」</td>
          <td>成本中等，弱領域先驗，可逆</td>
      </tr>
      <tr>
          <td>「先做 API rate limiting」</td>
          <td>成本中等，現階段流量沒問題，可逆</td>
      </tr>
      <tr>
          <td>「先設計 multi-region 部署」</td>
          <td>成本巨大，多數產品永遠單 region</td>
      </tr>
      <tr>
          <td>「先抽 service 層」</td>
          <td>成本中等，function 直接呼叫已經夠用</td>
      </tr>
  </tbody>
</table>
<p>這些都是<strong>為了未來付出當下具體成本</strong>——抽象層、新概念、額外測試、配置複雜度。YAGNI 在這些情境會帶你做出對的選擇。</p>
<p>判斷的差異是：<strong>這個決定是「選哪個免費選項」，還是「要不要付一筆額外開發成本」？</strong> 前者三軸框架；後者 YAGNI。</p>
<hr>
<h2 id="總結">總結</h2>
<p>YAGNI vs 過度設計的爭論，常常因為兩邊在用不同定義而無法收斂。釐清如下：</p>
<blockquote>
<p><strong>YAGNI 適用於「為了未來而付出當下的具體成本」</strong>
<strong>不適用於「在成本相當的選項中選擇更通用的那個」</strong></p></blockquote>
<p>判斷時跑三軸：</p>
<ol>
<li><strong>成本對稱性</strong>：兩個選項的 upfront cost 是否相當？</li>
<li><strong>可逆性</strong>：選錯的話修正昂貴嗎？</li>
<li><strong>領域先驗</strong>：這個模式在領域裡發生機率多高？</li>
</ol>
<p>任一軸顯著偏向「該選通用」，YAGNI 就不適用，這不是過度設計。</p>
<p>回到開頭問題——「最早的設計者沒考慮到多個監聽需求、這算設計瑕疵還是避免過度設計？」答案<strong>取決於這三軸的具體狀況</strong>、不能一概而論。</p>
<p>但如果像 Stream 這個案例、三軸全部不利於受限預設、那就是設計瑕疵。<strong>只是這類瑕疵反映的是工具預設與領域知識內化的系統性問題、不是個別工程師的判斷力不足</strong>——修補方向是制度而非個人責備。</p>
<h3 id="一句話帶走">一句話帶走</h3>
<p>日常情境中、把三軸壓縮成一個問題就夠用：</p>
<blockquote>
<p>「<strong>我在多付什麼成本？</strong>」</p></blockquote>
<ul>
<li>多付<strong>抽象層、新概念、額外測試</strong> → YAGNI 適用、先別付</li>
<li>多付<strong>幾個字元、一個關鍵字</strong> → 不是 YAGNI、選通用的</li>
</ul>
<p>需要更精細的時候、再回頭跑完整三軸框架。</p>
]]></content:encoded></item></channel></rss>