<?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>Use-Case on Tarragon</title><link>https://tarrragon.github.io/blog/tags/use-case/</link><description>Recent content in Use-Case on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 13 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/use-case/index.xml" rel="self" type="application/rss+xml"/><item><title>組裝層的可達性</title><link>https://tarrragon.github.io/blog/ddd/composition-root-reachability/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ddd/composition-root-reachability/</guid><description>&lt;p>組裝層是應用程式把 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/port/" data-link-title="Port" data-link-desc="判斷介面該宣告在哪一層、依賴方向該朝哪時使用。port 是 domain 對外宣告的介面——需求用領域語言說完、技術細節留在實作端。">port&lt;/a> 插上 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/adapter/" data-link-title="Adapter" data-link-desc="把領域需求翻譯成具體技術操作的實作該放哪、跟領域的邊界在哪時使用。adapter 是 port 的具體實作——技術細節被擋在六角形之外的位置。">adapter&lt;/a> 的地方——port 是 domain 對外宣告的介面、adapter 是介面的具體實作、&lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/dependency-injection/" data-link-title="Dependency Injection" data-link-desc="物件的依賴該由誰提供、測試怎麼換掉真實依賴時使用。依賴注入把「建構依賴」跟「使用依賴」分成兩個責任——使用方宣告需要什麼、提供方在組裝時決定給什麼。">依賴注入&lt;/a> 是把兩者接起來的機制。組裝發生在三個位置：依賴注入的組裝集中在 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/composition-root/" data-link-title="Composition Root" data-link-desc="依賴組裝、路由註冊該集中在哪、組裝斷裂去哪檢查時使用。composition root 是應用程式唯一的組裝起點——DI、路由、事件接線的集中處。">composition root&lt;/a>（組裝根、依賴注入唯一的組裝起點），路由表以單點註冊靠攏它，UI 事件的接線散佈在各畫面。位置不同、同屬組裝層的責任：把功能單元接到使用者按得到的入口上。使用者入口之外、機器觸發的組裝位置（事件訂閱、排程任務）有同一種證言缺口、判準同樣適用；本章聚焦使用者入口。&lt;/p>
&lt;p>本模組（&lt;a href="https://tarrragon.github.io/blog/ddd/" data-link-title="DDD 領域驅動設計指南" data-link-desc="領域模型的理論與判準層：一袋欄位還是領域模型、什麼時候值得建 entity、不變式該落在哪一層強制、狀態轉換怎麼留下稽核軌跡、建構路徑怎麼設計。語言無關，實作限制路由到各語言模組。">DDD 領域驅動設計指南&lt;/a>）的源頭句「讓違反規則的路徑走不通」守的是領域內部的規則；本章守它的對偶面——讓正確的路徑確實走得通。domain 模型全對、系統不可用，是分層架構特有的失效形態：每一層各自正確、層與層之間沒接上。&lt;/p>
&lt;h2 id="一個專案的五個問題">一個專案的五個問題&lt;/h2>
&lt;p>這個失效形態先用一個完整案例展開——本章後面的每個判準與工具都從它歸納而來。一個專案的單元測試全數通過、實機測試找出五個問題（完整記錄與 Flutter / Riverpod 的實作細節見 &lt;a href="https://tarrragon.github.io/blog/work-log/flutter_composition_root_wiring_gap/" data-link-title="測試全綠、功能失聯：五個 runtime 問題與組裝層的接線缺口" data-link-desc="113 張票收尾全綠的版本，實機測試找出五個問題：路由指向佔位頁、provider 佔位 throw、按鈕空 callback，加上兩個平台語意差異。單元測試的 mock override 是正當的測試 seam、同時是遮蔽接線斷裂的來源——記下三層共振的機制、反向追溯設計文件的結果、以及修補時規格層／測試層／發版層的分層落點。">測試全綠、功能失聯&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題&lt;/th>
 &lt;th>現象&lt;/th>
 &lt;th>斷裂點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>注入項佔位&lt;/td>
 &lt;td>注入項停在拋「requires override」的佔位、production 一解析就連鎖報錯&lt;/td>
 &lt;td>DI 組裝&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>路由佔位&lt;/td>
 &lt;td>入口按鈕彈出「功能開發中」、路由表把入口指向佔位頁——功能本體已全部完成&lt;/td>
 &lt;td>路由表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>空 callback&lt;/td>
 &lt;td>按鈕的事件處理是空實作、點擊沒有任何反應&lt;/td>
 &lt;td>UI 事件接線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>初始化順序&lt;/td>
 &lt;td>框架初始化與例外攔截散在不同範圍、非同步例外可能逃出攔截&lt;/td>
 &lt;td>平台語意&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料庫開啟失敗&lt;/td>
 &lt;td>一條資料庫設定指令在目標平台的執行語意不同、開啟直接失敗、持久化全面不可用&lt;/td>
 &lt;td>平台語意&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>前三個是同一種形狀：功能單元全部存在、對應的測試全部通過，斷的是「把功能接到入口」的那一段——依賴注入沒接上真實實作、路由表沒指向真實頁面、按鈕沒接上導航。後兩個是另一種形狀：程式碼在開發機的測試環境（host 環境）行為正確、在目標平台的語意下失效。兩種形狀的對策不同，平台差異的處置收在「邊界」一節；本章的主線是前三個。&lt;/p>
&lt;h2 id="分層的承諾與它的影子">分層的承諾與它的影子&lt;/h2>
&lt;p>分層架構（以及 ports &amp;amp; adapters、即六角形架構）的核心承諾是 domain 不依賴框架、可以脫離 infrastructure 單獨測試。兌現承諾的機制是 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/test-seam/" data-link-title="Test Seam" data-link-desc="測試想把真實依賴換成替身、該從哪裡換時使用。seam 是不修改程式本體就能替換其中一段行為的位置——介面加注入點是物件導向最常見的形式。">測試 seam&lt;/a>：seam 是不修改程式本體就能替換其中一段行為的位置——port 加上注入點、測試時把真實依賴換成 mock。seam 工作得越好、domain 的測試越乾淨，這是 DDD 教材反覆強調的部分。&lt;/p>
&lt;p>承諾有一道影子、而且跟承諾出自同一個機制：mock 換掉的正是組裝。本章用「證言」指一個測試通過時能證明的事——證言的範圍由測試的執行環境決定、mock 環境的綠燈證明的是 mock 環境下的行為。於是「組裝有沒有完成」這件事、在以 seam 為基礎的測試套件裡無從證明。兩類測試各自的證言範圍：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>測試&lt;/th>
 &lt;th>執行環境&lt;/th>
 &lt;th>證明什麼&lt;/th>
 &lt;th>對組裝的證言&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>行為測試&lt;/td>
 &lt;td>mock／override 注入&lt;/td>
 &lt;td>功能邏輯正確&lt;/td>
 &lt;td>零&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>接線測試&lt;/td>
 &lt;td>組裝路徑零 override、真實路由表與依賴&lt;/td>
 &lt;td>port 插上了 adapter&lt;/td>
 &lt;td>測試層唯一來源&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>行為測試維持 mock 注入、驗的是功能邏輯。上述案例的測試全落在這一類、全綠是誠實的：功能邏輯確實正確、綠燈在它的證言範圍內沒有失真——失真發生在把這個綠燈讀成「功能可用」的那一步。&lt;/p>
&lt;p>接線測試補的正是缺掉的那份證言、下一節展開。&lt;/p>
&lt;h2 id="接線測試組裝證言的來源">接線測試：組裝證言的來源&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/wiring-test/" data-link-title="Wiring Test" data-link-desc="行為測試全綠、還想知道組裝有沒有完成時使用。接線測試在組裝路徑零 override 的環境解析每個注入項、走真實路由表，只驗「port 插上了 adapter」。">接線測試&lt;/a>（wiring test）在組裝路徑零 override 的環境驗證組裝完成：逐一解析每個注入項、用真實路由表逐條導航、對每顆按鈕實際觸發並斷言導航發生。它只驗「插上了沒」、功能邏輯留給行為測試——兩類測試各守自己的證言範圍、彼此替代不了。&lt;/p>
&lt;p>它在既有測試光譜上的位置，可以從兩個熟悉的參照點定出來。第一個參照點是端對端測試：E2E 在目標平台同時驗行為與接線、代價是裝置需求與執行不穩定；接線測試把驗證範圍收窄到「接上了沒」、換取在 host 環境快速且確定地執行。第二個參照點是 DI 容器的自驗證：部分容器提供「啟動時逐一解析全部註冊項」的機制、專門攔沒被提供實作的註冊項；接線測試是這個機制的推廣——解析之外、把路由與 UI callback 的接線納進同一種證言。&lt;/p>
&lt;p>零 override 有明確的邊界：它指的是組裝路徑——domain 到入口之間不替換任何一段。最外圈的 infrastructure（遠端服務這類）可以在邊界替換、不影響組裝的證言。單機的 local-first 應用做得到全量零 override；有遠端依賴的專案、零 override 的範圍就是組裝路徑本身。&lt;/p>
&lt;p>該專案的修復批次先補了這批接線測試、對目標行為斷言、修復前確定性紅燈，修復以測試變綠為驗收點。日常的判讀分界在專案層級：override 出現在個別測試裡是正當用法；整個專案找不到任何一個「無 override 環境解析注入項」的測試、組裝層才算沒有證言。&lt;/p>
&lt;h2 id="佔位合法的中間態最靜默的失效">佔位：合法的中間態、最靜默的失效&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/placeholder/" data-link-title="Placeholder" data-link-desc="「開發中」頁、空 callback、拋出例外的注入項該怎麼管理時使用。佔位是先立介面後補實作的合法中間態——它的失效讓測試綠燈、比文件層約束更靜默。">佔位&lt;/a>是「先立介面、實作後補」的合法開發中間態：指向「開發中」頁的路由、空的事件 callback、拋「requires override」的注入項。先把介面立起來、實作後補，這個節奏本身沒有問題；問題在佔位的失效形態。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次&lt;/a> 記過文件層約束的失效是靜默的；佔位的失效比文件層再隱蔽一級——它讓測試綠燈。型別層看佔位是合法構件、行為測試的 override 讓它永遠沒被觸發、只有零 override 的解析會撞上它。文件層約束至少留下「規則寫在那裡、沒人遵守」的對照證據；佔位連對照證據都沒有、驗收讀到的只有通過。&lt;/p>
&lt;p>佔位因此需要一個獨立的攔截點。這個案例的處置是把攔截放進發版前置檢查：出現過的三種形態（佔位路由、佔位注入項、空 callback）都是靜態可掃描的——佔位頁的型別名、「requires override」的拋出語句、空函式體都 grep 得到——掃到即警告、由人判斷是刻意中間態還是漏網。&lt;/p>
&lt;p>回傳假值的佔位是掃描的死角：hardcoded 假資料、回空集合的 stub，在掃描與接線測試眼中都是正常構件——注入項解析得了、導航也會發生。這一類要靠行為斷言（對回傳內容驗證、假資料過不了斷言）或發版前的實機冒煙走查攔截；走查的三類敏感點在發版層一段收束、完整清單的住址在 work-log 記錄。同族的另一個死角是「插錯」：注入項插上的是真實作、只是不該是它（例如測試用實作被註冊進 production 組態）——解析成功、接線測試綠燈。接線測試的保證天花板在這裡很清楚：它證明「有插、插得起來」；插上的實作對不對、屬行為與整合測試的證言。&lt;/p>
&lt;h2 id="可達性作為組裝層的不變式">可達性作為組裝層的不變式&lt;/h2>
&lt;p>「use case 描述的每個入口在 production 環境可達」是一條 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">不變式&lt;/a>。不變式的落點判準（完整推導見 &lt;a href="https://tarrragon.github.io/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次&lt;/a>）把約束分成文件、型別、執行三層：寫在文件裡靠人記得並自律、做進型別讓錯誤的寫法在編譯期被擋下、放在執行層讓違反在 runtime 當場被拒絕。把判準套在可達性上、答案是多層都有位置、單獨任何一層都不夠：&lt;/p></description><content:encoded><![CDATA[<p>組裝層是應用程式把 <a href="/blog/ddd/knowledge-cards/port/" data-link-title="Port" data-link-desc="判斷介面該宣告在哪一層、依賴方向該朝哪時使用。port 是 domain 對外宣告的介面——需求用領域語言說完、技術細節留在實作端。">port</a> 插上 <a href="/blog/ddd/knowledge-cards/adapter/" data-link-title="Adapter" data-link-desc="把領域需求翻譯成具體技術操作的實作該放哪、跟領域的邊界在哪時使用。adapter 是 port 的具體實作——技術細節被擋在六角形之外的位置。">adapter</a> 的地方——port 是 domain 對外宣告的介面、adapter 是介面的具體實作、<a href="/blog/ddd/knowledge-cards/dependency-injection/" data-link-title="Dependency Injection" data-link-desc="物件的依賴該由誰提供、測試怎麼換掉真實依賴時使用。依賴注入把「建構依賴」跟「使用依賴」分成兩個責任——使用方宣告需要什麼、提供方在組裝時決定給什麼。">依賴注入</a> 是把兩者接起來的機制。組裝發生在三個位置：依賴注入的組裝集中在 <a href="/blog/ddd/knowledge-cards/composition-root/" data-link-title="Composition Root" data-link-desc="依賴組裝、路由註冊該集中在哪、組裝斷裂去哪檢查時使用。composition root 是應用程式唯一的組裝起點——DI、路由、事件接線的集中處。">composition root</a>（組裝根、依賴注入唯一的組裝起點），路由表以單點註冊靠攏它，UI 事件的接線散佈在各畫面。位置不同、同屬組裝層的責任：把功能單元接到使用者按得到的入口上。使用者入口之外、機器觸發的組裝位置（事件訂閱、排程任務）有同一種證言缺口、判準同樣適用；本章聚焦使用者入口。</p>
<p>本模組（<a href="/blog/ddd/" data-link-title="DDD 領域驅動設計指南" data-link-desc="領域模型的理論與判準層：一袋欄位還是領域模型、什麼時候值得建 entity、不變式該落在哪一層強制、狀態轉換怎麼留下稽核軌跡、建構路徑怎麼設計。語言無關，實作限制路由到各語言模組。">DDD 領域驅動設計指南</a>）的源頭句「讓違反規則的路徑走不通」守的是領域內部的規則；本章守它的對偶面——讓正確的路徑確實走得通。domain 模型全對、系統不可用，是分層架構特有的失效形態：每一層各自正確、層與層之間沒接上。</p>
<h2 id="一個專案的五個問題">一個專案的五個問題</h2>
<p>這個失效形態先用一個完整案例展開——本章後面的每個判準與工具都從它歸納而來。一個專案的單元測試全數通過、實機測試找出五個問題（完整記錄與 Flutter / Riverpod 的實作細節見 <a href="/blog/work-log/flutter_composition_root_wiring_gap/" data-link-title="測試全綠、功能失聯：五個 runtime 問題與組裝層的接線缺口" data-link-desc="113 張票收尾全綠的版本，實機測試找出五個問題：路由指向佔位頁、provider 佔位 throw、按鈕空 callback，加上兩個平台語意差異。單元測試的 mock override 是正當的測試 seam、同時是遮蔽接線斷裂的來源——記下三層共振的機制、反向追溯設計文件的結果、以及修補時規格層／測試層／發版層的分層落點。">測試全綠、功能失聯</a>）：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>現象</th>
          <th>斷裂點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>注入項佔位</td>
          <td>注入項停在拋「requires override」的佔位、production 一解析就連鎖報錯</td>
          <td>DI 組裝</td>
      </tr>
      <tr>
          <td>路由佔位</td>
          <td>入口按鈕彈出「功能開發中」、路由表把入口指向佔位頁——功能本體已全部完成</td>
          <td>路由表</td>
      </tr>
      <tr>
          <td>空 callback</td>
          <td>按鈕的事件處理是空實作、點擊沒有任何反應</td>
          <td>UI 事件接線</td>
      </tr>
      <tr>
          <td>初始化順序</td>
          <td>框架初始化與例外攔截散在不同範圍、非同步例外可能逃出攔截</td>
          <td>平台語意</td>
      </tr>
      <tr>
          <td>資料庫開啟失敗</td>
          <td>一條資料庫設定指令在目標平台的執行語意不同、開啟直接失敗、持久化全面不可用</td>
          <td>平台語意</td>
      </tr>
  </tbody>
</table>
<p>前三個是同一種形狀：功能單元全部存在、對應的測試全部通過，斷的是「把功能接到入口」的那一段——依賴注入沒接上真實實作、路由表沒指向真實頁面、按鈕沒接上導航。後兩個是另一種形狀：程式碼在開發機的測試環境（host 環境）行為正確、在目標平台的語意下失效。兩種形狀的對策不同，平台差異的處置收在「邊界」一節；本章的主線是前三個。</p>
<h2 id="分層的承諾與它的影子">分層的承諾與它的影子</h2>
<p>分層架構（以及 ports &amp; adapters、即六角形架構）的核心承諾是 domain 不依賴框架、可以脫離 infrastructure 單獨測試。兌現承諾的機制是 <a href="/blog/ddd/knowledge-cards/test-seam/" data-link-title="Test Seam" data-link-desc="測試想把真實依賴換成替身、該從哪裡換時使用。seam 是不修改程式本體就能替換其中一段行為的位置——介面加注入點是物件導向最常見的形式。">測試 seam</a>：seam 是不修改程式本體就能替換其中一段行為的位置——port 加上注入點、測試時把真實依賴換成 mock。seam 工作得越好、domain 的測試越乾淨，這是 DDD 教材反覆強調的部分。</p>
<p>承諾有一道影子、而且跟承諾出自同一個機制：mock 換掉的正是組裝。本章用「證言」指一個測試通過時能證明的事——證言的範圍由測試的執行環境決定、mock 環境的綠燈證明的是 mock 環境下的行為。於是「組裝有沒有完成」這件事、在以 seam 為基礎的測試套件裡無從證明。兩類測試各自的證言範圍：</p>
<table>
  <thead>
      <tr>
          <th>測試</th>
          <th>執行環境</th>
          <th>證明什麼</th>
          <th>對組裝的證言</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行為測試</td>
          <td>mock／override 注入</td>
          <td>功能邏輯正確</td>
          <td>零</td>
      </tr>
      <tr>
          <td>接線測試</td>
          <td>組裝路徑零 override、真實路由表與依賴</td>
          <td>port 插上了 adapter</td>
          <td>測試層唯一來源</td>
      </tr>
  </tbody>
</table>
<p>行為測試維持 mock 注入、驗的是功能邏輯。上述案例的測試全落在這一類、全綠是誠實的：功能邏輯確實正確、綠燈在它的證言範圍內沒有失真——失真發生在把這個綠燈讀成「功能可用」的那一步。</p>
<p>接線測試補的正是缺掉的那份證言、下一節展開。</p>
<h2 id="接線測試組裝證言的來源">接線測試：組裝證言的來源</h2>
<p><a href="/blog/ddd/knowledge-cards/wiring-test/" data-link-title="Wiring Test" data-link-desc="行為測試全綠、還想知道組裝有沒有完成時使用。接線測試在組裝路徑零 override 的環境解析每個注入項、走真實路由表，只驗「port 插上了 adapter」。">接線測試</a>（wiring test）在組裝路徑零 override 的環境驗證組裝完成：逐一解析每個注入項、用真實路由表逐條導航、對每顆按鈕實際觸發並斷言導航發生。它只驗「插上了沒」、功能邏輯留給行為測試——兩類測試各守自己的證言範圍、彼此替代不了。</p>
<p>它在既有測試光譜上的位置，可以從兩個熟悉的參照點定出來。第一個參照點是端對端測試：E2E 在目標平台同時驗行為與接線、代價是裝置需求與執行不穩定；接線測試把驗證範圍收窄到「接上了沒」、換取在 host 環境快速且確定地執行。第二個參照點是 DI 容器的自驗證：部分容器提供「啟動時逐一解析全部註冊項」的機制、專門攔沒被提供實作的註冊項；接線測試是這個機制的推廣——解析之外、把路由與 UI callback 的接線納進同一種證言。</p>
<p>零 override 有明確的邊界：它指的是組裝路徑——domain 到入口之間不替換任何一段。最外圈的 infrastructure（遠端服務這類）可以在邊界替換、不影響組裝的證言。單機的 local-first 應用做得到全量零 override；有遠端依賴的專案、零 override 的範圍就是組裝路徑本身。</p>
<p>該專案的修復批次先補了這批接線測試、對目標行為斷言、修復前確定性紅燈，修復以測試變綠為驗收點。日常的判讀分界在專案層級：override 出現在個別測試裡是正當用法；整個專案找不到任何一個「無 override 環境解析注入項」的測試、組裝層才算沒有證言。</p>
<h2 id="佔位合法的中間態最靜默的失效">佔位：合法的中間態、最靜默的失效</h2>
<p><a href="/blog/ddd/knowledge-cards/placeholder/" data-link-title="Placeholder" data-link-desc="「開發中」頁、空 callback、拋出例外的注入項該怎麼管理時使用。佔位是先立介面後補實作的合法中間態——它的失效讓測試綠燈、比文件層約束更靜默。">佔位</a>是「先立介面、實作後補」的合法開發中間態：指向「開發中」頁的路由、空的事件 callback、拋「requires override」的注入項。先把介面立起來、實作後補，這個節奏本身沒有問題；問題在佔位的失效形態。</p>
<p><a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a> 記過文件層約束的失效是靜默的；佔位的失效比文件層再隱蔽一級——它讓測試綠燈。型別層看佔位是合法構件、行為測試的 override 讓它永遠沒被觸發、只有零 override 的解析會撞上它。文件層約束至少留下「規則寫在那裡、沒人遵守」的對照證據；佔位連對照證據都沒有、驗收讀到的只有通過。</p>
<p>佔位因此需要一個獨立的攔截點。這個案例的處置是把攔截放進發版前置檢查：出現過的三種形態（佔位路由、佔位注入項、空 callback）都是靜態可掃描的——佔位頁的型別名、「requires override」的拋出語句、空函式體都 grep 得到——掃到即警告、由人判斷是刻意中間態還是漏網。</p>
<p>回傳假值的佔位是掃描的死角：hardcoded 假資料、回空集合的 stub，在掃描與接線測試眼中都是正常構件——注入項解析得了、導航也會發生。這一類要靠行為斷言（對回傳內容驗證、假資料過不了斷言）或發版前的實機冒煙走查攔截；走查的三類敏感點在發版層一段收束、完整清單的住址在 work-log 記錄。同族的另一個死角是「插錯」：注入項插上的是真實作、只是不該是它（例如測試用實作被註冊進 production 組態）——解析成功、接線測試綠燈。接線測試的保證天花板在這裡很清楚：它證明「有插、插得起來」；插上的實作對不對、屬行為與整合測試的證言。</p>
<h2 id="可達性作為組裝層的不變式">可達性作為組裝層的不變式</h2>
<p>「use case 描述的每個入口在 production 環境可達」是一條 <a href="/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">不變式</a>。不變式的落點判準（完整推導見 <a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a>）把約束分成文件、型別、執行三層：寫在文件裡靠人記得並自律、做進型別讓錯誤的寫法在編譯期被擋下、放在執行層讓違反在 runtime 當場被拒絕。把判準套在可達性上、答案是多層都有位置、單獨任何一層都不夠：</p>
<p><strong>文件層</strong>：use case 的成功保證補上可達性條款——路由指向真實頁面、callback 已接線、注入項在無 override 環境可解析。這層的作用是把組裝責任寫進驗收範圍：它定義「什麼叫完成」。文件層失效靜默、單獨存在時只是願望；怎麼把這層寫得可檢核、「use case 的完成定義」一節展開。</p>
<p><strong>型別層</strong>：組裝完成性用型別表達的空間有限、而且空間大小由生態決定。在編譯期生成組裝碼的 DI 生態、缺實作是編譯錯誤、型別層接得住這條不變式；runtime 解析的容器把「這個注入項沒人提供實作」推遲到執行期、編譯器看不見。路由表同理：以字串鍵查表的路由生態、斷鏈也是執行期才暴露。用 runtime 容器與字串路由的專案、型別層在這條不變式上是輔助而非主力。</p>
<p><strong>執行層</strong>：接線測試——違反當場紅燈、失效點集中。刻意不可達的入口（feature flag 關閉、分階段發布）不算違反：以旗標開啟的組態跑接線測試、或明列豁免清單——豁免要有記錄、讓「刻意」與「遺漏」在清單上分得開。</p>
<p><strong>發版層</strong>（對應強制層次一章補充的 CI 檢查層）：佔位掃描加實機冒煙走查。前者警示靜態可掃的佔位、由人判讀；後者是發版前在目標平台人工走一輪的清單——啟動健康、use case 主路徑走查、平台敏感點——攔 host 測試構不到的平台語意差異。</p>
<h2 id="use-case-的完成定義">use case 的完成定義</h2>
<p>組裝斷裂的上游在設計文件——這一節是文件層那一列的展開：可達性條款要寫得進 use case、use case 本身先要能回答裝配問題。</p>
<p>修復批次拿五個問題反向追溯提案與 use case 文件、確認每個問題在文件裡的對應條目長什麼樣。結果分成兩型：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>設計文件對應</th>
          <th>缺口型態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>注入項佔位</td>
          <td>提案定義了完整的功能方法與 UI 形態</td>
          <td>有能力定義、驗收不含可達性</td>
      </tr>
      <tr>
          <td>路由佔位</td>
          <td>use case 寫了「使用者點擊按鈕」</td>
          <td>有行為描述、接線無人認領</td>
      </tr>
      <tr>
          <td>空 callback</td>
          <td>use case 寫了操作步驟</td>
          <td>callback 實作不在驗收內</td>
      </tr>
      <tr>
          <td>兩個平台問題</td>
          <td>全文無對應</td>
          <td>平台層細節、設計文件構不到</td>
      </tr>
  </tbody>
</table>
<p>追溯出一個一致的形狀：提案思考「能力」（系統要有哪些功能）、use case 思考「行為」（使用者做什麼、系統回應什麼），兩者之間的「裝配」——把能力組起來、給行為一個入口的責任——沒有歸屬。三個組裝斷裂全部落在這條縫裡。</p>
<p>把縫補起來的做法是讓 use case 撰寫承擔對提案的壓力測試：寫 use case 時逐一回答四個檢核問句、填不出來的問句直接指出對應的設計缺口。問句從五個問題歸納而來、是起點集、新的斷裂形態出現時往上加。</p>
<p><strong>名詞可定位</strong>——文中每個畫面、按鈕、服務，能填出「位於哪個畫面、經哪條路由、由誰供給」。填不出來代表提案只設計了能力、沒設計裝配：案例裡注入項佔位對應的功能、提案寫滿了功能方法與 UI 形態、按鈕跟頁面由誰放上去沒有一個字。</p>
<p><strong>路徑連通</strong>——步驟一的入口能從應用程式啟動畫面一路追到。追不出這條路、入口接線就無人認領：路由佔位問題的 use case 描述了使用者「點擊按鈕」、按鈕到路由到頁面這條鏈的歸屬、文件裡翻不到。直達入口（web 的 URL、深連結、外部喚起）另立一類：它們繞過啟動鏈、每個直達入口各自追一條可達性。</p>
<p><strong>狀態完備</strong>——涉及的每個畫面、狀態機的每個狀態（空、正常、錯誤、權限與登入態）下入口可見性都有答案。這一問附帶一個操作型式：前置條件反向展開。use case 的前置條件每一條都隱含一個「不滿足時會怎樣」的狀態分支。案例裡一條前置條件寫「存在至少一筆資料」——實機上使用者的資料是空的、對應按鈕只存在於正常狀態分支、使用者的回報是「功能不見了」。把前置條件當成狀態枚舉的線索用、空狀態的畫面在設計期就被逼著給出答案；它與 <a href="/blog/ddd/state-transition-and-audit-trail/" data-link-title="狀態轉換與稽核軌跡" data-link-desc="領域方法作為唯一變更路徑：判準是「變更有沒有需要一起完成的伴隨動作」。含唯一路徑與建議路徑的分界、稽核軌跡出洞的靜默機制與凍結作為稽核端點。">狀態轉換與稽核軌跡</a> 談的領域狀態機互為投影——領域的前置條件投影到畫面層、就是「這個狀態下入口在哪」的答案。</p>
<p><strong>環境差異</strong>——涉及平台 API 的步驟、「測試環境與目標平台行為一致嗎」有答案。答案為否或不確定的步驟、標記「需實機驗證」、餵給發版前的冒煙走查清單：兩個平台問題在設計文件裡無處可防、能做的是在設計期把「這一步只有實機能驗」標出來、讓漏網的位置從使用者手上移到發版前的清單上。</p>
<p>任何一問填不出來、代表對應的設計缺口還停在提案層、退回補齊再進實作。</p>
<h2 id="邊界組裝可達之外的失效">邊界：組裝可達之外的失效</h2>
<p>缺口分類先於對策。五個問題的兩種形狀對應兩組修法：組裝斷裂修規格與測試（可達性條款、接線測試）、平台差異修驗證流程（設計期標記、冒煙走查）。混用的代價是把「補測試」當成萬靈丹——平台語意差異是 host 測試補不到的那一類、案例裡那條資料庫設定指令的語意差異、開發機的測試環境重現不出來、要目標平台實際執行才暴露。</p>
<p>本章的作用域同樣要誠實標明：可達性守的是「入口接上了」；接上之後的行為正確性屬行為測試、資料在層間流動的正確性屬整合測試的其他主題。組裝層的證言與六角形內部的證言互補、彼此替代不了：層內的設計判準（<a href="/blog/ddd/data-bag-vs-domain-model/" data-link-title="資料袋與領域模型" data-link-desc="判斷一個型別該是一袋欄位還是有行為的領域模型：判準是「有沒有不允許任意組合的欄位」。含判準用錯時規則退化成建議的機制、以及資料袋起步後升級的演化訊號。">資料袋與領域模型</a>、<a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a>）決定六角形內部的品質、<a href="/blog/ddd/construction-path-design/" data-link-title="建構路徑設計" data-link-desc="工廠表達力不足時缺陷如何被逃生口吸收——逃生口讓正確的修法變不必要、以語意錯誤在下游復發。含原始值官方出口的穩態邊界、封裝擺盪的判讀。">建構路徑設計</a> 收物件怎麼被建出來、本章把同一個問題抬到應用程式層。層內的模型設計得再好、組裝層沒有證言、使用者拿到的仍然可能是一片接不通的綠燈。</p>
<h2 id="下一步">下一步</h2>
<ul>
<li>層內規則的落點選擇（文件／型別／執行三層判準的源頭）：<a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a></li>
<li>前置條件投影回領域層的狀態機設計：<a href="/blog/ddd/state-transition-and-audit-trail/" data-link-title="狀態轉換與稽核軌跡" data-link-desc="領域方法作為唯一變更路徑：判準是「變更有沒有需要一起完成的伴隨動作」。含唯一路徑與建議路徑的分界、稽核軌跡出洞的靜默機制與凍結作為稽核端點。">狀態轉換與稽核軌跡</a></li>
<li>Flutter / Riverpod 的實作細節（override 判讀分界、接線測試與修復的執行記錄、佔位掃描與冒煙清單）：<a href="/blog/work-log/flutter_composition_root_wiring_gap/" data-link-title="測試全綠、功能失聯：五個 runtime 問題與組裝層的接線缺口" data-link-desc="113 張票收尾全綠的版本，實機測試找出五個問題：路由指向佔位頁、provider 佔位 throw、按鈕空 callback，加上兩個平台語意差異。單元測試的 mock override 是正當的測試 seam、同時是遮蔽接線斷裂的來源——記下三層共振的機制、反向追溯設計文件的結果、以及修補時規格層／測試層／發版層的分層落點。">測試全綠、功能失聯</a></li>
<li>同一失效形態的畫面層檢查法（路由存在但不可達）：<a href="/blog/ux-design/01-screen-state-machine/route-reachability/" data-link-title="路由可達性檢查" data-link-desc="Router 定義的路由 vs UI 實際可達的路由 — 路由存在但 UI 不可達等於死程式碼的 UX 版本">路由可達性檢查</a></li>
</ul>
]]></content:encoded></item></channel></rss>