<?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>方法論 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E6%96%B9%E6%B3%95%E8%AB%96/</link><description>Recent content in 方法論 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 15 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E6%96%B9%E6%B3%95%E8%AB%96/index.xml" rel="self" type="application/rss+xml"/><item><title>知識載體責任分配方法論 - rules/agents/skills 各該裝什麼</title><link>https://tarrragon.github.io/blog/record/%E7%9F%A5%E8%AD%98%E8%BC%89%E9%AB%94%E8%B2%AC%E4%BB%BB%E5%88%86%E9%85%8D%E6%96%B9%E6%B3%95%E8%AB%96-rules/agents/skills-%E5%90%84%E8%A9%B2%E8%A3%9D%E4%BB%80%E9%BA%BC/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%9F%A5%E8%AD%98%E8%BC%89%E9%AB%94%E8%B2%AC%E4%BB%BB%E5%88%86%E9%85%8D%E6%96%B9%E6%B3%95%E8%AB%96-rules/agents/skills-%E5%90%84%E8%A9%B2%E8%A3%9D%E4%BB%80%E9%BA%BC/</guid><description>&lt;h2 id="核心概念">核心概念&lt;/h2>
&lt;p>知識寫入框架前，依「&lt;strong>受眾 x 形態&lt;/strong>」二軸決定載體。載體錯置有兩種代價：寫進自動載入層 → token 污染（attention 稀釋 + 45k 預算耗盡）；困在專案 memory → 跨專案失傳。本方法論是頂層地圖；各載體的細部規範（如有）路由至 Reference 所列文件。&lt;/p>
&lt;p>&lt;strong>Scope&lt;/strong>：本地圖涵蓋 LLM context 載體（人與 AI 閱讀的知識）；專案產物層（&lt;code>docs/&lt;/code> / &lt;code>src/&lt;/code>）不屬本地圖，劃分見 &lt;code>framework-asset-separation.md&lt;/code>；機器讀取層（&lt;code>config/*.yaml&lt;/code>、hook 引用的凍結錨點）另計。memory 行由受眾軸「僅本專案」唯一決定，不需形態軸。&lt;/p>
&lt;p>&lt;strong>代理人定義 vs skill 的歸屬判準&lt;/strong>：一段知識可能落在代理人定義或 skill、不易區分時，以「該知識是否隨執行者改變」為判準，不憑直覺擇一。&lt;/p>
&lt;ul>
&lt;li>屬&lt;strong>代理人定義&lt;/strong>的知識回答「你是誰、你能做什麼、你偏好怎麼做」——身份定位、授權邊界、設計偏好。識別測試：換一個代理人來執行，這段內容就應該不同。本質是人格與授權。&lt;/li>
&lt;li>屬 &lt;strong>skill&lt;/strong> 的知識回答「這件事怎麼做」——可重複執行的流程步驟。識別測試：任何角色觸發都應得到同一份流程，與執行者是誰無關。本質是可重複流程。&lt;/li>
&lt;/ul>
&lt;p>兩者衝突時，對該知識套用識別測試「換一個代理人，內容會不會變」：會變則歸代理人定義；不會變、任何角色執行都應一致則歸 skill。&lt;/p>
&lt;h2 id="載體地圖受眾-x-載入時機-x-形態">載體地圖（受眾 x 載入時機 x 形態）&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>載體&lt;/th>
 &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;code>CLAUDE.md&lt;/code>&lt;/td>
 &lt;td>所有角色&lt;/td>
 &lt;td>每回合自動&lt;/td>
 &lt;td>專案身份、開發指令、專案級技術選型、路由&lt;/td>
 &lt;td>框架通用知識（→ &lt;code>.claude/&lt;/code>，否則無法 sync）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>rules/core/&lt;/code>&lt;/td>
 &lt;td>所有角色&lt;/td>
 &lt;td>每回合自動&lt;/td>
 &lt;td>行為禁令速查 + 路由（與 CLAUDE.md 同屬 file-size-guardian 45k 量測集合；MEMORY.md 每回合注入但不在量測集合內）&lt;/td>
 &lt;td>論證 / 流程 / 案例（→ &lt;code>references/&lt;/code>、&lt;code>error-patterns/&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>pm-rules/&lt;/code>&lt;/td>
 &lt;td>僅 PM&lt;/td>
 &lt;td>情境觸發按需&lt;/td>
 &lt;td>調度流程 SOP（派發、驗收、決策樹、skip-gate）&lt;/td>
 &lt;td>代理人執行知識（→ agents / skills）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>agents/AGENT_PRELOAD.md&lt;/code>&lt;/td>
 &lt;td>全體代理人&lt;/td>
 &lt;td>派發時 @ 注入&lt;/td>
 &lt;td>代理人通用行為禁令（ticket 操作、git 限制、工具選擇、嵌套協議）&lt;/td>
 &lt;td>單一代理人偏好（→ 各 agent 定義）、PM 流程（→ pm-rules）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>agents/&amp;lt;name&amp;gt;.md&lt;/code>&lt;/td>
 &lt;td>單一代理人&lt;/td>
 &lt;td>派發時載入&lt;/td>
 &lt;td>身份定位、三區塊（允許產出 / 禁止行為 / 適用情境）、設計偏好（命名習慣、技術手法傾向、文法語氣）、分工路由與升級條件&lt;/td>
 &lt;td>→ 見「代理人定義內容規範」節&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>skills/&lt;/code>&lt;/td>
 &lt;td>觸發者（角色無關）&lt;/td>
 &lt;td>觸發時漸進揭露&lt;/td>
 &lt;td>可重複執行的工作流、方法、CLI 工具（TDD、寫作、ticket、worktree）&lt;/td>
 &lt;td>身份偏好（→ agents）、專案設定（→ CLAUDE.md）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>methodologies/&lt;/code>&lt;/td>
 &lt;td>主動查閱者&lt;/td>
 &lt;td>按需&lt;/td>
 &lt;td>30 秒理念複習清單（核心概念 + 步驟 + 檢查清單）&lt;/td>
 &lt;td>完整流程 / 範例 / 錯誤處理（→ skills）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>references/&lt;/code>&lt;/td>
 &lt;td>執行特定動作者&lt;/td>
 &lt;td>按需&lt;/td>
 &lt;td>技術參考、規則 substance（auto-load stub 的完整版）&lt;/td>
 &lt;td>每回合禁令（→ rules/core stub）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>error-patterns/&lt;/code>&lt;/td>
 &lt;td>ticket 前查詢者&lt;/td>
 &lt;td>按需&lt;/td>
 &lt;td>失敗案例（症狀 / 根因 / 解法 / 預防）&lt;/td>
 &lt;td>規則正文（規則只放一行路由指向 PC/IMP）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>memory（專案層）&lt;/td>
 &lt;td>本專案 PM&lt;/td>
 &lt;td>MEMORY.md 每回合&lt;/td>
 &lt;td>專案特定活教訓的單行索引&lt;/td>
 &lt;td>已固化內容（升級即搬家）、跨專案原則（四問升級後外移）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>templates/&lt;/code>、&lt;code>.claude/&lt;/code> root 歷史遺留檔&lt;/td>
 &lt;td>（未分類）&lt;/td>
 &lt;td>不自動載入&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>依本地圖二軸重分配（templates 內容須與對應規範同步，否則新實例從模板長出舊形態）；盤點另由 ticket 追蹤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.claude/README.md&lt;/code>&lt;/td>
 &lt;td>框架瀏覽者&lt;/td>
 &lt;td>不自動載入&lt;/td>
 &lt;td>框架頂層導覽：目錄結構、各載體用途、入口索引&lt;/td>
 &lt;td>規範 substance（→ rules / references）、流程方法（→ skills）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.claude/CHANGELOG.md&lt;/code>&lt;/td>
 &lt;td>框架維護者&lt;/td>
 &lt;td>不自動載入&lt;/td>
 &lt;td>框架變更記錄（sync 歷史、版本演進）&lt;/td>
 &lt;td>當前規範內容（→ 對應載體；CHANGELOG 只記「變了什麼」不記「規範是什麼」）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.claude/README-subtree-sync.md&lt;/code>&lt;/td>
 &lt;td>執行 sync-pull / sync-push 者&lt;/td>
 &lt;td>不自動載入&lt;/td>
 &lt;td>同步機制操作說明：設計原理、方案比較、衝突處理&lt;/td>
 &lt;td>同步以外的框架知識（→ 對應載體）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.claude/terminology-dictionary.md&lt;/code>&lt;/td>
 &lt;td>所有角色（撰寫文字時）&lt;/td>
 &lt;td>經 &lt;code>.claude/rules/core/language-constraints.md&lt;/code> 的 &lt;code>@&lt;/code> 引用實質載入&lt;/td>
 &lt;td>用語規範對照表：禁用詞 / 正確用語 / 台灣用語&lt;/td>
 &lt;td>語言規則正文（→ &lt;code>.claude/rules/core/language-constraints.md&lt;/code>，本檔僅承載對照資料）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="執行步驟">執行步驟&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>受眾是誰&lt;/strong>？（所有角色 / 僅 PM / 全體代理人 / 單一代理人 / 動作觸發者 / 僅本專案）→ 縮小候選載體。「動作觸發者」統括地圖表受眾欄的按需情境詞（觸發者 / 主動查閱者 / 執行特定動作者 / 任務前查詢者）&lt;/li>
&lt;li>&lt;strong>形態是什麼&lt;/strong>？（行為禁令 / 調度流程 / 身份偏好 / 工作流方法 / 理念清單 / 技術參考 / 失敗案例 / 專案設定）→ 確定載體&lt;/li>
&lt;li>候選屬&lt;strong>自動載入層&lt;/strong>（CLAUDE.md / rules/ / MEMORY.md）？→ 過預算閘門；規範類知識的閘門是必要性否決（「這是否每回合都需要？」否則外移按需層）+ 形態降為「禁令 + 路由」，專案設定 / 指令等事實類的閘門是體積與專案特定性約束（精簡陳述、不含框架通用知識），不適用必要性否決&lt;/li>
&lt;li>skill / methodology / rule 三選一拿不準 → &lt;code>framework-meta-methodology.md&lt;/code> 決策樹&lt;/li>
&lt;li>寫完 grep 概念詞，盤點與既有規範的指令方向矛盾，並對齊執法強度（PC-V1-006）&lt;/li>
&lt;/ol>
&lt;h2 id="代理人定義內容規範">代理人定義內容規範&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>該裝&lt;/th>
 &lt;th>不該裝（外移路由）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>身份定位與核心使命&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>三區塊：允許產出 / 禁止行為 / 適用情境&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計偏好：命名習慣、技術手法傾向、文法語氣&lt;/td>
 &lt;td>專案級技術選型（→ CLAUDE.md；代理人帶多方案知識，依專案設定選用）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多方案技術知識庫（framework-asset-separation 的「框架寫法」段，深度以支撐選用傾向為度）&lt;/td>
 &lt;td>步驟化操作流程（→ 對應 skill，流程與人格解耦）；知識庫展開成教學長文（→ references/）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分工路由與升級條件（與誰分工、何時上報）&lt;/td>
 &lt;td>操作流程步驟（→ 對應 skill）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>品質標準的章節路由（如 quality-common 指定章節，語意錨點）&lt;/td>
 &lt;td>品質清單全文（複製即漂移，單一來源失效）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>錯誤模式的一行路由（「詳見 IMP-XXX」）&lt;/td>
 &lt;td>錯誤案例全文（error-pattern 才是案例的家）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="檢查清單">檢查清單&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 受眾 x 形態二軸定位完成，不是「順手寫在開啟中的檔案」？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 自動載入層寫入已過預算閘門；規範類形態已降為禁令 + 路由（事實類過閘門即可）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 代理人定義新增內容屬「偏好 / 邊界」而非「流程 / 方法」？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 重複內容用路由取代複製（單一來源）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 概念詞 grep 矛盾盤點 + 執法強度對齊完成（PC-V1-006）？&lt;/li>
&lt;/ul>
&lt;h2 id="reference">Reference&lt;/h2>
&lt;ul>
&lt;li>&lt;code>.claude/methodologies/framework-meta-methodology.md&lt;/code> — skill / methodology / rule 三分決策樹 + 30 秒標準（形態軸的細分）&lt;/li>
&lt;li>&lt;code>.claude/references/framework-asset-separation.md&lt;/code> — 框架資產 vs 專案產物、專案設定 vs 代理人知識、Skill Hook 雙層&lt;/li>
&lt;li>&lt;code>.claude/references/auto-load-stub-conventions.md&lt;/code> — 自動載入層 stub 構成 + 外移 SOP + 預算驗證&lt;/li>
&lt;li>&lt;code>.claude/rules/core/agent-definition-standard.md&lt;/code> — 代理人三區塊結構標準&lt;/li>
&lt;li>&lt;code>.claude/rules/README.md&lt;/code> — 自動載入預算原則（每回合必要性自問）&lt;/li>
&lt;li>&lt;code>.claude/pm-rules/pm-quality-baseline.md&lt;/code> 規則 7 — memory 升級四問 + 升級目的地預算閘門 + 升級即搬家&lt;/li>
&lt;li>&lt;code>.claude/README.md&lt;/code>「同步機制」章 — 寫作類 skill（compositional-writing / multi-round-review）內容 SSOT 在 blog repo，框架端為回流副本；依地圖判定「寫作方法 → skills/」後，內容修改應到上游 repo 執行&lt;/li>
&lt;li>&lt;code>.claude/skills/skill-design-guide/SKILL.md&lt;/code> — skills 載體的細部規範（官方規格、frontmatter、漸進揭露結構）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;strong>Last Updated&lt;/strong>: 2026-06-15
&lt;strong>Version&lt;/strong>: 1.8.0 — 「代理人定義 vs skill 歸屬判準」改寫：去除「一句話判定」總結框架，改為含明確識別測試（換一個執行者內容是否改變）的判準段落。方法論作為框架核心規則供 AI 開發時判斷，內容須明確而可套用，不採壓縮式總結（避免單句總結遮蔽判準細節導致 AI 判斷失準）
&lt;strong>Version&lt;/strong>: 1.7.0 — root 錯置檔重分配（1.0.0-W8-023.2，第 2/4 批）：4 檔（&lt;code>agent-collaboration.md&lt;/code> 794 / &lt;code>decision-workflows.md&lt;/code> 116 / &lt;code>quick-ref-agent-dispatch-recovery.md&lt;/code> 202 / &lt;code>thinking-process.md&lt;/code> 271）逐檔讀內容後&lt;strong>全數 flag superseded/obsolete&lt;/strong>（campaign 規則 3，零搬移零連結手術）：&lt;code>agent-collaboration&lt;/code> 與 &lt;code>analyses/archived/&lt;/code> 同名 794 行副本 near-identical 且內容已被 &lt;code>methodologies/tdd-collaboration-flow.md&lt;/code> + agent 定義覆蓋；&lt;code>decision-workflows&lt;/code> 五情境已被 &lt;code>pm-rules/skip-gate&lt;/code>+&lt;code>incident-response&lt;/code>+&lt;code>decision-tree&lt;/code> 覆蓋；&lt;code>quick-ref-agent-dispatch-recovery&lt;/code> 所述 &lt;code>agent_dispatch_recovery.py&lt;/code> hook 已不存在；&lt;code>thinking-process&lt;/code> 為 2025-12-01 一次性 session 快照非知識載體。本批 0 檔搬移，故不加 map 行，留 PM follow-up 清理（inbound 連結多在 .3/.4 批檔群）
&lt;strong>Version&lt;/strong>: 1.6.0 — root 錯置檔重分配（1.0.0-W8-023.1，第 1/4 批）：&lt;code>hook-system-reference.md&lt;/code>（Hook 事件索引 / 技術參考）、&lt;code>code-smell-checklist.md&lt;/code>（Code Smell 檢測清單 / 技術參考）依二軸（受眾＝動作觸發者、形態＝技術參考）歸入既有 &lt;code>references/&lt;/code> 載體列（line 22），故不另加 map 行；superseded 副本 &lt;code>code-quality-examples.md&lt;/code>（已遷 &lt;code>docs/&lt;/code>，DOC-010 W10-102）與 &lt;code>document-responsibilities.md&lt;/code>（DEPRECATED，已被 &lt;code>five-document-system-methodology.md&lt;/code> + &lt;code>doc-flow/references/document-responsibilities.md&lt;/code> 取代）flag 不併入，留 PM follow-up
&lt;strong>Version&lt;/strong>: 1.5.0 — 載體地圖補列 4 個 legit root 資產各一行歸屬（README 框架導覽 / CHANGELOG 變更記錄 / README-subtree-sync 同步機制 / terminology-dictionary 用語規範表，後者經 language-constraints &lt;code>@&lt;/code> 引用實質載入）（1.0.0-W8-022）
&lt;strong>Version&lt;/strong>: 1.4.0 — multi-round-review Round 4（實例分配演練）修正：步驟 1 補受眾詞彙映射橋（六選項 vs 地圖表受眾欄斷層）、步驟 3 事實類閘門判準明文化（體積與專案特定性約束，非必要性否決）。8 條盲跑 6 條乾淨落點，停止訊號達成收斂
&lt;strong>Version&lt;/strong>: 1.3.0 — multi-round-review Round 3 修正：Scope 句（LLM context 載體限定 + 機器讀取層另計 + memory 受眾軸唯一決定）、rules/core 列量測集合精確化（MEMORY.md 不在 guardian 集合）、規範表補「多方案技術知識庫」劃界列（與 framework-asset-separation「框架寫法」段對齊）、地圖補 templates / root 遺留行、Reference 補 skill-design-guide
&lt;strong>Version&lt;/strong>: 1.2.0 — multi-round-review Round 2 修正：檢查清單與步驟 3/5 的 R1 劃界同步（清單漂移）、步驟 5 拆動作解歧義、地圖欄名補形態軸、定位句「（如有）」、Reference 補寫作 skill SSOT 例外路由
&lt;strong>Version&lt;/strong>: 1.1.0 — multi-round-review Round 1 修正：步驟 3 形態約束劃界（規範類 vs 事實類）、步驟 5 補執法強度對齊、章名對齊 methodology 標準結構、rules/core 列預算範圍精確化、agents 列改路由至專節
&lt;strong>Version&lt;/strong>: 1.0.0 — 初始建立：框架知識載體的頂層責任地圖（受眾 x 形態二軸），整合 W7 token 收斂三層防護與既有分離原則；代理人定義內容規範首次權威化（人格與授權 vs 可重複流程）&lt;/p></description><content:encoded><![CDATA[<h2 id="核心概念">核心概念</h2>
<p>知識寫入框架前，依「<strong>受眾 x 形態</strong>」二軸決定載體。載體錯置有兩種代價：寫進自動載入層 → token 污染（attention 稀釋 + 45k 預算耗盡）；困在專案 memory → 跨專案失傳。本方法論是頂層地圖；各載體的細部規範（如有）路由至 Reference 所列文件。</p>
<p><strong>Scope</strong>：本地圖涵蓋 LLM context 載體（人與 AI 閱讀的知識）；專案產物層（<code>docs/</code> / <code>src/</code>）不屬本地圖，劃分見 <code>framework-asset-separation.md</code>；機器讀取層（<code>config/*.yaml</code>、hook 引用的凍結錨點）另計。memory 行由受眾軸「僅本專案」唯一決定，不需形態軸。</p>
<p><strong>代理人定義 vs skill 的歸屬判準</strong>：一段知識可能落在代理人定義或 skill、不易區分時，以「該知識是否隨執行者改變」為判準，不憑直覺擇一。</p>
<ul>
<li>屬<strong>代理人定義</strong>的知識回答「你是誰、你能做什麼、你偏好怎麼做」——身份定位、授權邊界、設計偏好。識別測試：換一個代理人來執行，這段內容就應該不同。本質是人格與授權。</li>
<li>屬 <strong>skill</strong> 的知識回答「這件事怎麼做」——可重複執行的流程步驟。識別測試：任何角色觸發都應得到同一份流程，與執行者是誰無關。本質是可重複流程。</li>
</ul>
<p>兩者衝突時，對該知識套用識別測試「換一個代理人，內容會不會變」：會變則歸代理人定義；不會變、任何角色執行都應一致則歸 skill。</p>
<h2 id="載體地圖受眾-x-載入時機-x-形態">載體地圖（受眾 x 載入時機 x 形態）</h2>
<table>
  <thead>
      <tr>
          <th>載體</th>
          <th>受眾</th>
          <th>載入時機</th>
          <th>裝什麼（形態）</th>
          <th>不裝什麼（→ 正確去處）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>CLAUDE.md</code></td>
          <td>所有角色</td>
          <td>每回合自動</td>
          <td>專案身份、開發指令、專案級技術選型、路由</td>
          <td>框架通用知識（→ <code>.claude/</code>，否則無法 sync）</td>
      </tr>
      <tr>
          <td><code>rules/core/</code></td>
          <td>所有角色</td>
          <td>每回合自動</td>
          <td>行為禁令速查 + 路由（與 CLAUDE.md 同屬 file-size-guardian 45k 量測集合；MEMORY.md 每回合注入但不在量測集合內）</td>
          <td>論證 / 流程 / 案例（→ <code>references/</code>、<code>error-patterns/</code>）</td>
      </tr>
      <tr>
          <td><code>pm-rules/</code></td>
          <td>僅 PM</td>
          <td>情境觸發按需</td>
          <td>調度流程 SOP（派發、驗收、決策樹、skip-gate）</td>
          <td>代理人執行知識（→ agents / skills）</td>
      </tr>
      <tr>
          <td><code>agents/AGENT_PRELOAD.md</code></td>
          <td>全體代理人</td>
          <td>派發時 @ 注入</td>
          <td>代理人通用行為禁令（ticket 操作、git 限制、工具選擇、嵌套協議）</td>
          <td>單一代理人偏好（→ 各 agent 定義）、PM 流程（→ pm-rules）</td>
      </tr>
      <tr>
          <td><code>agents/&lt;name&gt;.md</code></td>
          <td>單一代理人</td>
          <td>派發時載入</td>
          <td>身份定位、三區塊（允許產出 / 禁止行為 / 適用情境）、設計偏好（命名習慣、技術手法傾向、文法語氣）、分工路由與升級條件</td>
          <td>→ 見「代理人定義內容規範」節</td>
      </tr>
      <tr>
          <td><code>skills/</code></td>
          <td>觸發者（角色無關）</td>
          <td>觸發時漸進揭露</td>
          <td>可重複執行的工作流、方法、CLI 工具（TDD、寫作、ticket、worktree）</td>
          <td>身份偏好（→ agents）、專案設定（→ CLAUDE.md）</td>
      </tr>
      <tr>
          <td><code>methodologies/</code></td>
          <td>主動查閱者</td>
          <td>按需</td>
          <td>30 秒理念複習清單（核心概念 + 步驟 + 檢查清單）</td>
          <td>完整流程 / 範例 / 錯誤處理（→ skills）</td>
      </tr>
      <tr>
          <td><code>references/</code></td>
          <td>執行特定動作者</td>
          <td>按需</td>
          <td>技術參考、規則 substance（auto-load stub 的完整版）</td>
          <td>每回合禁令（→ rules/core stub）</td>
      </tr>
      <tr>
          <td><code>error-patterns/</code></td>
          <td>ticket 前查詢者</td>
          <td>按需</td>
          <td>失敗案例（症狀 / 根因 / 解法 / 預防）</td>
          <td>規則正文（規則只放一行路由指向 PC/IMP）</td>
      </tr>
      <tr>
          <td>memory（專案層）</td>
          <td>本專案 PM</td>
          <td>MEMORY.md 每回合</td>
          <td>專案特定活教訓的單行索引</td>
          <td>已固化內容（升級即搬家）、跨專案原則（四問升級後外移）</td>
      </tr>
      <tr>
          <td><code>templates/</code>、<code>.claude/</code> root 歷史遺留檔</td>
          <td>（未分類）</td>
          <td>不自動載入</td>
          <td>—</td>
          <td>依本地圖二軸重分配（templates 內容須與對應規範同步，否則新實例從模板長出舊形態）；盤點另由 ticket 追蹤</td>
      </tr>
      <tr>
          <td><code>.claude/README.md</code></td>
          <td>框架瀏覽者</td>
          <td>不自動載入</td>
          <td>框架頂層導覽：目錄結構、各載體用途、入口索引</td>
          <td>規範 substance（→ rules / references）、流程方法（→ skills）</td>
      </tr>
      <tr>
          <td><code>.claude/CHANGELOG.md</code></td>
          <td>框架維護者</td>
          <td>不自動載入</td>
          <td>框架變更記錄（sync 歷史、版本演進）</td>
          <td>當前規範內容（→ 對應載體；CHANGELOG 只記「變了什麼」不記「規範是什麼」）</td>
      </tr>
      <tr>
          <td><code>.claude/README-subtree-sync.md</code></td>
          <td>執行 sync-pull / sync-push 者</td>
          <td>不自動載入</td>
          <td>同步機制操作說明：設計原理、方案比較、衝突處理</td>
          <td>同步以外的框架知識（→ 對應載體）</td>
      </tr>
      <tr>
          <td><code>.claude/terminology-dictionary.md</code></td>
          <td>所有角色（撰寫文字時）</td>
          <td>經 <code>.claude/rules/core/language-constraints.md</code> 的 <code>@</code> 引用實質載入</td>
          <td>用語規範對照表：禁用詞 / 正確用語 / 台灣用語</td>
          <td>語言規則正文（→ <code>.claude/rules/core/language-constraints.md</code>，本檔僅承載對照資料）</td>
      </tr>
  </tbody>
</table>
<h2 id="執行步驟">執行步驟</h2>
<ol>
<li><strong>受眾是誰</strong>？（所有角色 / 僅 PM / 全體代理人 / 單一代理人 / 動作觸發者 / 僅本專案）→ 縮小候選載體。「動作觸發者」統括地圖表受眾欄的按需情境詞（觸發者 / 主動查閱者 / 執行特定動作者 / 任務前查詢者）</li>
<li><strong>形態是什麼</strong>？（行為禁令 / 調度流程 / 身份偏好 / 工作流方法 / 理念清單 / 技術參考 / 失敗案例 / 專案設定）→ 確定載體</li>
<li>候選屬<strong>自動載入層</strong>（CLAUDE.md / rules/ / MEMORY.md）？→ 過預算閘門；規範類知識的閘門是必要性否決（「這是否每回合都需要？」否則外移按需層）+ 形態降為「禁令 + 路由」，專案設定 / 指令等事實類的閘門是體積與專案特定性約束（精簡陳述、不含框架通用知識），不適用必要性否決</li>
<li>skill / methodology / rule 三選一拿不準 → <code>framework-meta-methodology.md</code> 決策樹</li>
<li>寫完 grep 概念詞，盤點與既有規範的指令方向矛盾，並對齊執法強度（PC-V1-006）</li>
</ol>
<h2 id="代理人定義內容規範">代理人定義內容規範</h2>
<table>
  <thead>
      <tr>
          <th>該裝</th>
          <th>不該裝（外移路由）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>身份定位與核心使命</td>
          <td>—</td>
      </tr>
      <tr>
          <td>三區塊：允許產出 / 禁止行為 / 適用情境</td>
          <td>—</td>
      </tr>
      <tr>
          <td>設計偏好：命名習慣、技術手法傾向、文法語氣</td>
          <td>專案級技術選型（→ CLAUDE.md；代理人帶多方案知識，依專案設定選用）</td>
      </tr>
      <tr>
          <td>多方案技術知識庫（framework-asset-separation 的「框架寫法」段，深度以支撐選用傾向為度）</td>
          <td>步驟化操作流程（→ 對應 skill，流程與人格解耦）；知識庫展開成教學長文（→ references/）</td>
      </tr>
      <tr>
          <td>分工路由與升級條件（與誰分工、何時上報）</td>
          <td>操作流程步驟（→ 對應 skill）</td>
      </tr>
      <tr>
          <td>品質標準的章節路由（如 quality-common 指定章節，語意錨點）</td>
          <td>品質清單全文（複製即漂移，單一來源失效）</td>
      </tr>
      <tr>
          <td>錯誤模式的一行路由（「詳見 IMP-XXX」）</td>
          <td>錯誤案例全文（error-pattern 才是案例的家）</td>
      </tr>
  </tbody>
</table>
<h2 id="檢查清單">檢查清單</h2>
<ul>
<li><input disabled="" type="checkbox"> 受眾 x 形態二軸定位完成，不是「順手寫在開啟中的檔案」？</li>
<li><input disabled="" type="checkbox"> 自動載入層寫入已過預算閘門；規範類形態已降為禁令 + 路由（事實類過閘門即可）？</li>
<li><input disabled="" type="checkbox"> 代理人定義新增內容屬「偏好 / 邊界」而非「流程 / 方法」？</li>
<li><input disabled="" type="checkbox"> 重複內容用路由取代複製（單一來源）？</li>
<li><input disabled="" type="checkbox"> 概念詞 grep 矛盾盤點 + 執法強度對齊完成（PC-V1-006）？</li>
</ul>
<h2 id="reference">Reference</h2>
<ul>
<li><code>.claude/methodologies/framework-meta-methodology.md</code> — skill / methodology / rule 三分決策樹 + 30 秒標準（形態軸的細分）</li>
<li><code>.claude/references/framework-asset-separation.md</code> — 框架資產 vs 專案產物、專案設定 vs 代理人知識、Skill Hook 雙層</li>
<li><code>.claude/references/auto-load-stub-conventions.md</code> — 自動載入層 stub 構成 + 外移 SOP + 預算驗證</li>
<li><code>.claude/rules/core/agent-definition-standard.md</code> — 代理人三區塊結構標準</li>
<li><code>.claude/rules/README.md</code> — 自動載入預算原則（每回合必要性自問）</li>
<li><code>.claude/pm-rules/pm-quality-baseline.md</code> 規則 7 — memory 升級四問 + 升級目的地預算閘門 + 升級即搬家</li>
<li><code>.claude/README.md</code>「同步機制」章 — 寫作類 skill（compositional-writing / multi-round-review）內容 SSOT 在 blog repo，框架端為回流副本；依地圖判定「寫作方法 → skills/」後，內容修改應到上游 repo 執行</li>
<li><code>.claude/skills/skill-design-guide/SKILL.md</code> — skills 載體的細部規範（官方規格、frontmatter、漸進揭露結構）</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-06-15
<strong>Version</strong>: 1.8.0 — 「代理人定義 vs skill 歸屬判準」改寫：去除「一句話判定」總結框架，改為含明確識別測試（換一個執行者內容是否改變）的判準段落。方法論作為框架核心規則供 AI 開發時判斷，內容須明確而可套用，不採壓縮式總結（避免單句總結遮蔽判準細節導致 AI 判斷失準）
<strong>Version</strong>: 1.7.0 — root 錯置檔重分配（1.0.0-W8-023.2，第 2/4 批）：4 檔（<code>agent-collaboration.md</code> 794 / <code>decision-workflows.md</code> 116 / <code>quick-ref-agent-dispatch-recovery.md</code> 202 / <code>thinking-process.md</code> 271）逐檔讀內容後<strong>全數 flag superseded/obsolete</strong>（campaign 規則 3，零搬移零連結手術）：<code>agent-collaboration</code> 與 <code>analyses/archived/</code> 同名 794 行副本 near-identical 且內容已被 <code>methodologies/tdd-collaboration-flow.md</code> + agent 定義覆蓋；<code>decision-workflows</code> 五情境已被 <code>pm-rules/skip-gate</code>+<code>incident-response</code>+<code>decision-tree</code> 覆蓋；<code>quick-ref-agent-dispatch-recovery</code> 所述 <code>agent_dispatch_recovery.py</code> hook 已不存在；<code>thinking-process</code> 為 2025-12-01 一次性 session 快照非知識載體。本批 0 檔搬移，故不加 map 行，留 PM follow-up 清理（inbound 連結多在 .3/.4 批檔群）
<strong>Version</strong>: 1.6.0 — root 錯置檔重分配（1.0.0-W8-023.1，第 1/4 批）：<code>hook-system-reference.md</code>（Hook 事件索引 / 技術參考）、<code>code-smell-checklist.md</code>（Code Smell 檢測清單 / 技術參考）依二軸（受眾＝動作觸發者、形態＝技術參考）歸入既有 <code>references/</code> 載體列（line 22），故不另加 map 行；superseded 副本 <code>code-quality-examples.md</code>（已遷 <code>docs/</code>，DOC-010 W10-102）與 <code>document-responsibilities.md</code>（DEPRECATED，已被 <code>five-document-system-methodology.md</code> + <code>doc-flow/references/document-responsibilities.md</code> 取代）flag 不併入，留 PM follow-up
<strong>Version</strong>: 1.5.0 — 載體地圖補列 4 個 legit root 資產各一行歸屬（README 框架導覽 / CHANGELOG 變更記錄 / README-subtree-sync 同步機制 / terminology-dictionary 用語規範表，後者經 language-constraints <code>@</code> 引用實質載入）（1.0.0-W8-022）
<strong>Version</strong>: 1.4.0 — multi-round-review Round 4（實例分配演練）修正：步驟 1 補受眾詞彙映射橋（六選項 vs 地圖表受眾欄斷層）、步驟 3 事實類閘門判準明文化（體積與專案特定性約束，非必要性否決）。8 條盲跑 6 條乾淨落點，停止訊號達成收斂
<strong>Version</strong>: 1.3.0 — multi-round-review Round 3 修正：Scope 句（LLM context 載體限定 + 機器讀取層另計 + memory 受眾軸唯一決定）、rules/core 列量測集合精確化（MEMORY.md 不在 guardian 集合）、規範表補「多方案技術知識庫」劃界列（與 framework-asset-separation「框架寫法」段對齊）、地圖補 templates / root 遺留行、Reference 補 skill-design-guide
<strong>Version</strong>: 1.2.0 — multi-round-review Round 2 修正：檢查清單與步驟 3/5 的 R1 劃界同步（清單漂移）、步驟 5 拆動作解歧義、地圖欄名補形態軸、定位句「（如有）」、Reference 補寫作 skill SSOT 例外路由
<strong>Version</strong>: 1.1.0 — multi-round-review Round 1 修正：步驟 3 形態約束劃界（規範類 vs 事實類）、步驟 5 補執法強度對齊、章名對齊 methodology 標準結構、rules/core 列預算範圍精確化、agents 列改路由至專節
<strong>Version</strong>: 1.0.0 — 初始建立：框架知識載體的頂層責任地圖（受眾 x 形態二軸），整合 W7 token 收斂三層防護與既有分離原則；代理人定義內容規範首次權威化（人格與授權 vs 可重複流程）</p>
]]></content:encoded></item><item><title>開發記錄</title><link>https://tarrragon.github.io/blog/record/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/</guid><description>&lt;p>這個資料夾收錄&lt;strong>方法論記錄&lt;/strong> — 寫法是中性 frame 的「某個工作模式 / 流程 / 原則是什麼、怎麼用」、不一定有具體 case 觸發。&lt;/p>
&lt;p>內容大致分六類：&lt;/p>
&lt;p>&lt;strong>自察與認知方法論&lt;/strong> — 工作前 / 工作中的自我檢視框架。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="5w1h-self-awareness-methodology/">5W1H 自察方法論&lt;/a>&lt;/li>
&lt;li>&lt;a href="ai-task-avoidance-detection-methodology/">AI 任務迴避偵測方法論&lt;/a>&lt;/li>
&lt;li>&lt;a href="%e5%a4%a7%e8%a6%8f%e6%a8%a1%e9%87%8d%e6%a7%8b%e6%96%b9%e6%b3%95%e8%ab%96/">大規模重構方法論&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>設計判斷與選型&lt;/strong> — 技術決策框架，避免過度設計與設計瑕疵的判斷標準（如 YAGNI 的真實適用條件、成本不對稱性下的選擇邏輯）。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="yagni-boundary-three-axes/">YAGNI 的真實適用條件&lt;/a>&lt;/li>
&lt;li>&lt;a href="saas-selection-interview-methodology/">SaaS 選型訪談方法論&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>敏捷 / 工程流程&lt;/strong> — 敏捷實作、重構流程、文件分層。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="agile-programing-methodology/">敏捷編程方法論&lt;/a>&lt;/li>
&lt;li>&lt;a href="agile-refactor-methodology/">敏捷重構方法論&lt;/a>&lt;/li>
&lt;li>&lt;a href="5-layer-doc-system/">5 層文件系統&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>寫作 / 溝通標準&lt;/strong> — 驗收條件、寫作規範。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="acceptance-criteria-methodology/">驗收條件方法論&lt;/a>&lt;/li>
&lt;li>&lt;a href="writing-guidelines/">經驗分享文章的寫作準則&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>文件與 API 設計&lt;/strong> — function 文件分層、測試命名作為 spec、commit message vs source code doc 的職責邊界、型別取代 doc 等表達設計議題。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="function-doc-layered-design/">函式文件分層設計&lt;/a>&lt;/li>
&lt;li>&lt;a href="types-replacing-docs/">型別取代 doc 的收益曲線&lt;/a>&lt;/li>
&lt;li>&lt;a href="test-naming-as-documentation/">測試命名作為文件&lt;/a>&lt;/li>
&lt;li>&lt;a href="commit-message-vs-source-doc/">Commit message vs source code doc&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>AI 協作工具評估&lt;/strong> — Claude Code / Codex / MCP server 等 AI 協作工具的設計拆解、能力邊界、選型對照。包含個別工具的 deep-dive 跟「同題不同工具」的實測。例：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="mcp-three-way-workflow-and-dart-experiment/">三 MCP 工作流與 Dart 實測&lt;/a>：cbm / codegraph / serena 的職責分工與三刀流（附三個工具各自的 deep-dive：&lt;a href="mcp-codebase-memory-deep-dive/">cbm&lt;/a> / &lt;a href="mcp-codegraph-deep-dive/">codegraph&lt;/a> / &lt;a href="mcp-serena-deep-dive/">serena&lt;/a>）&lt;/li>
&lt;li>&lt;a href="lsp-first-development-methodology/">LSP-first 開發方法論&lt;/a>：LSP 路線跟其他 code intelligence 路線的取捨&lt;/li>
&lt;li>&lt;a href="background-agent-parallel-research/">Background agent 並行研究&lt;/a>：用 Claude Code background agent 平行做研究的工作流&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="跟其他資料夾的邊界">跟其他資料夾的邊界&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>議題&lt;/th>
 &lt;th>該放&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>從具體 case 抽出可重用的工程原則&lt;/td>
 &lt;td>&lt;code>report/&lt;/code>（case-driven、編號連續）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工作中遇到的具體事件 / 工具事故&lt;/td>
 &lt;td>&lt;code>work-log/&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>blog 本身的設計 / 規範&lt;/td>
 &lt;td>&lt;code>posts/&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>中性 frame 的方法論說明（不綁特定 case）&lt;/td>
 &lt;td>&lt;strong>本資料夾&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>跟 &lt;code>report/&lt;/code> 的區別&lt;/strong>：record 是「方法論本身怎麼用」（教學 / 中性說明）、report 是「從某個 case 抽出來的原則」（事後檢討 / case-driven）。同一個議題若先有方法論再有 case、方法論寫 record、case 抽出的原則寫 report；若是先出問題再抽原則、直接寫 report。&lt;/p>
&lt;hr>
&lt;p>底下自動列出本資料夾的所有文章、依日期排序。&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾收錄<strong>方法論記錄</strong> — 寫法是中性 frame 的「某個工作模式 / 流程 / 原則是什麼、怎麼用」、不一定有具體 case 觸發。</p>
<p>內容大致分六類：</p>
<p><strong>自察與認知方法論</strong> — 工作前 / 工作中的自我檢視框架。例：</p>
<ul>
<li><a href="5w1h-self-awareness-methodology/">5W1H 自察方法論</a></li>
<li><a href="ai-task-avoidance-detection-methodology/">AI 任務迴避偵測方法論</a></li>
<li><a href="%e5%a4%a7%e8%a6%8f%e6%a8%a1%e9%87%8d%e6%a7%8b%e6%96%b9%e6%b3%95%e8%ab%96/">大規模重構方法論</a></li>
</ul>
<p><strong>設計判斷與選型</strong> — 技術決策框架，避免過度設計與設計瑕疵的判斷標準（如 YAGNI 的真實適用條件、成本不對稱性下的選擇邏輯）。例：</p>
<ul>
<li><a href="yagni-boundary-three-axes/">YAGNI 的真實適用條件</a></li>
<li><a href="saas-selection-interview-methodology/">SaaS 選型訪談方法論</a></li>
</ul>
<p><strong>敏捷 / 工程流程</strong> — 敏捷實作、重構流程、文件分層。例：</p>
<ul>
<li><a href="agile-programing-methodology/">敏捷編程方法論</a></li>
<li><a href="agile-refactor-methodology/">敏捷重構方法論</a></li>
<li><a href="5-layer-doc-system/">5 層文件系統</a></li>
</ul>
<p><strong>寫作 / 溝通標準</strong> — 驗收條件、寫作規範。例：</p>
<ul>
<li><a href="acceptance-criteria-methodology/">驗收條件方法論</a></li>
<li><a href="writing-guidelines/">經驗分享文章的寫作準則</a></li>
</ul>
<p><strong>文件與 API 設計</strong> — function 文件分層、測試命名作為 spec、commit message vs source code doc 的職責邊界、型別取代 doc 等表達設計議題。例：</p>
<ul>
<li><a href="function-doc-layered-design/">函式文件分層設計</a></li>
<li><a href="types-replacing-docs/">型別取代 doc 的收益曲線</a></li>
<li><a href="test-naming-as-documentation/">測試命名作為文件</a></li>
<li><a href="commit-message-vs-source-doc/">Commit message vs source code doc</a></li>
</ul>
<p><strong>AI 協作工具評估</strong> — Claude Code / Codex / MCP server 等 AI 協作工具的設計拆解、能力邊界、選型對照。包含個別工具的 deep-dive 跟「同題不同工具」的實測。例：</p>
<ul>
<li><a href="mcp-three-way-workflow-and-dart-experiment/">三 MCP 工作流與 Dart 實測</a>：cbm / codegraph / serena 的職責分工與三刀流（附三個工具各自的 deep-dive：<a href="mcp-codebase-memory-deep-dive/">cbm</a> / <a href="mcp-codegraph-deep-dive/">codegraph</a> / <a href="mcp-serena-deep-dive/">serena</a>）</li>
<li><a href="lsp-first-development-methodology/">LSP-first 開發方法論</a>：LSP 路線跟其他 code intelligence 路線的取捨</li>
<li><a href="background-agent-parallel-research/">Background agent 並行研究</a>：用 Claude Code background agent 平行做研究的工作流</li>
</ul>
<hr>
<h2 id="跟其他資料夾的邊界">跟其他資料夾的邊界</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>該放</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>從具體 case 抽出可重用的工程原則</td>
          <td><code>report/</code>（case-driven、編號連續）</td>
      </tr>
      <tr>
          <td>工作中遇到的具體事件 / 工具事故</td>
          <td><code>work-log/</code></td>
      </tr>
      <tr>
          <td>blog 本身的設計 / 規範</td>
          <td><code>posts/</code></td>
      </tr>
      <tr>
          <td>中性 frame 的方法論說明（不綁特定 case）</td>
          <td><strong>本資料夾</strong></td>
      </tr>
  </tbody>
</table>
<p><strong>跟 <code>report/</code> 的區別</strong>：record 是「方法論本身怎麼用」（教學 / 中性說明）、report 是「從某個 case 抽出來的原則」（事後檢討 / case-driven）。同一個議題若先有方法論再有 case、方法論寫 record、case 抽出的原則寫 report；若是先出問題再抽原則、直接寫 report。</p>
<hr>
<p>底下自動列出本資料夾的所有文章、依日期排序。</p>
]]></content:encoded></item><item><title>SaaS 選型訪談方法論 - 從使用者操作推導到技術選型</title><link>https://tarrragon.github.io/blog/record/saas-selection-interview-methodology/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/saas-selection-interview-methodology/</guid><description>&lt;p>專案初始化的選型決策，常見的起手式是直接列工具名單：選哪個資料庫、要上 k8s 嗎、queue 用哪家。這個順序跳過了選型真正的依據——產品要提供使用者哪些操作、領域邊界怎麼切、哪種失敗的代價最高。SaaS 選型訪談方法論把順序反過來：從使用者操作推導功能、從功能推導領域結構、最後才到技術選型，並把整段推導包裝成一份可重複執行的結構化訪談協議。&lt;/p></description><content:encoded><![CDATA[<p>專案初始化的選型決策，常見的起手式是直接列工具名單：選哪個資料庫、要上 k8s 嗎、queue 用哪家。這個順序跳過了選型真正的依據——產品要提供使用者哪些操作、領域邊界怎麼切、哪種失敗的代價最高。SaaS 選型訪談方法論把順序反過來：從使用者操作推導功能、從功能推導領域結構、最後才到技術選型，並把整段推導包裝成一份可重複執行的結構化訪談協議。</p>
<h2 id="方法論要解的問題">方法論要解的問題</h2>
<p>這套方法論的核心命題是成本不對稱：設計問題拖到開發中途才浮現，修正的代價遠高於在訪談階段多問十題。沒想清楚的操作、沒切清楚的領域邊界、沒被告知的防護缺口，最後都會在開發中途以重工的形式收費。訪談協議的責任是逼這些設計問題在寫第一行程式之前、於已知需求的範圍內浮現——因此訪談問題的數量刻意不設上限，問漏才是成本；節奏上每輪三到五題讓受訪者好消化，輪數跟著覆蓋率走。</p>
<p>協議同時內建「產品名攔截」：受訪者開口指定產品（「我要用 MongoDB」「直接上 k8s」）時，先回到需求確認背後動機（schema 變動頻繁？團隊熟悉度？），需求成立後產品可以直接採納。產品名是選型的輸出，由需求與領域模型推導出來，而非訪談的輸入。</p>
<h2 id="推導鏈從定錨到決策記錄">推導鏈：從定錨到決策記錄</h2>
<p>整條推導鏈的設計邏輯是：每一站的產出都是下一站的輸入，技術問題永遠錨在前面站點的具體產物上，避免「對空氣選型」。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">定錨 → 交付形態 gate → 操作盤點（BDD）→ domain / event 切分（DDD）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    → 核心問題與維度展開（每個維度就地過防護底線）→ 決策記錄 + scaffold</span></span></code></pre></div><p><strong>定錨</strong>。訪談從建立規模假設開始，定錨問題覆蓋七個面向：</p>
<ul>
<li>產品形態（B2B / B2C / 內部工具）——決定合規壓力與流量不確定性的預設方向</li>
<li>動機（營收、學習、解決自己的營運問題）——決策記錄理由欄的錨點，「自建換學習」「託管換速度」的權重都從這裡來</li>
<li>租戶模型——多租戶把租戶隔離升級成儲存與安全維度的第一級問題</li>
<li>預期規模（首年用戶與資料量級）——決定容量維度是否展開</li>
<li>團隊能力（人數、production on-call 經驗）——維運經驗低時預設 managed 服務，每個自管元件都要回答「誰半夜處理它」</li>
<li>每週可投入時數——一週十小時跟全職的選型分母不同</li>
<li>上線時程與迭代節奏——時程緊時選型偏保守，團隊熟悉度的權重高於技術新穎度</li>
</ul>
<p>定錨答案是後續每個推薦的合法依據——「因為團隊兩人且無維運經驗，推薦 managed 資料庫」這類理由都要能回指到定錨。</p>
<p><strong>交付形態 gate</strong>。定錨之後、進入任何技術討論之前，先誠實回答「這個產品現在值得自建嗎」。需求落在現成平台的標準域時，訪談走縮減流程：完整推導跳過，但仍產出託管縮減記錄——平台選擇與理由、可遷出保險狀態、升級自建的 tripwire、防護底線總表的適用項，託管結論同樣留下可追溯的決策記錄。這一站的判讀邏輯單獨成節，見「交付形態 gate」。</p>
<p><strong>操作盤點（BDD）</strong>。自建成立後，枚舉所有操作主體（含管理者、客服、訪客、機器角色）與其全部操作，每個操作寫行為情境——Given / When / Then 至少一條主情境加一條失敗情境。誤操作風險成對設計：前端引導（確認對話框、預設值、防呆）與後端防護（驗證、權限、idempotency）一一對應，從介面一路串到伺服器側。行為情境寫不出來，代表那個操作的需求還沒成形——在盤點階段攔下，比開發中途才發現便宜得多。</p>
<p><strong>Domain / event 切分（DDD）</strong>。操作清單沿固定方向轉成領域骨架：operation → command → 唯一歸屬 domain → event。推導從操作出發而非資料表出發——從資料表出發會切出 CRUD 式的偽領域（UserManager、OrderManager），從操作出發才會浮現真正的業務領域與領域間需要交換的事實。切分只用兩個原則：</p>
<ul>
<li><strong>SRP</strong>：一個 domain 一個變更理由、一個 event 一個事實。判讀問題包括「這兩個概念會因為同一個業務原因一起改嗎」「這個規則改變時誰說了算」——組織的決策邊界是領域邊界最誠實的線索。</li>
<li><strong>OCP</strong>：每個 domain 區分公開面（別的 domain 需要知道的：event schema、查詢介面）與內部面（表結構、狀態機、內部規則）。公開面是 contract、變更要盤點訂閱者；內部面可自由改。判讀問題是「Order domain 的表加一個欄位，要通知誰」——理想答案是「不用通知任何人」。</li>
</ul>
<p>LSP / ISP / DIP（里氏替換、介面隔離、依賴反轉）三個原則刻意留給實作階段：初始化階段還沒有 class 階層、沒有 interface 簇、沒有依賴注入結構，提前套用會把訪談拖進實作細節，模糊掉「邊界在哪」這個此階段的核心問題。</p>
<p><strong>核心問題與維度展開</strong>。領域骨架立好後，依序確認需求類型、流量形狀、資料生命週期、失敗代價、成本模型、定位與備援、安全邊界。每題附答案路由：訊號決定哪些技術維度要展開（快取、非同步佇列、容量），哪些維度任何 production 服務都逃不掉（儲存、部署、安全、觀測、可靠性）。</p>
<p>展開時問題錨在領域骨架上——「Order domain 的不可丟 event 用什麼機制送」，而非抽象的「要不要 queue」。每站也帶反向問，因為受訪者描述的是想要的功能，沒想到的東西藏在失敗面：「使用者做完馬上後悔怎麼辦」「凌晨三點服務掛了誰會知道」。</p>
<p><strong>防護底線</strong>。每個維度附一份防護底線清單——底線的定義是「缺了它、第一次事故的代價會遠超過 day one 建立它的成本」的項目，例如 secret 管理、備份加至少一次還原驗證、物件層級授權、部署可回滾。底線跟選型的差別在答案空間：選型問題容許多個合理答案並存，底線項目只有「已納入」「已告知後延後」「不適用」三種合法狀態，每一項的狀態都要寫進決策記錄。延後必須轉成記錄：告知代價、記下延後理由、附具體的重評條件（「上線前」「第一個付費客戶前」），「之後再說」這種無期限的口頭妥協會被協議擋下。目的是讓六個月後接手的人能回答「當初為什麼沒做」，把口頭妥協變成可追溯的決策。底線在訪談中經過兩次——維度展開時就地逐項過，決策記錄的總表再核對一次；雙重核對是刻意設計，防止任何一條在長流程中被沉默跳過。</p>
<p><strong>決策記錄</strong>。訪談收斂成一份決策記錄：操作風險表、domain map、event catalog、每項技術選型的理由 / 防護狀態 / <a href="/blog/backend/knowledge-cards/tripwire/" data-link-title="Tripwire" data-link-desc="說明風險決策在條件變化時如何自動回到評估流程">tripwire</a> 三欄齊備、防護底線總表、規模成長 tripwire 總表。被淘汰的次選項留名字——淘汰原因消失（例如團隊長出維運能力）就是重評入口。scaffold 建議是決策記錄的下游：先確認決策、再產生檔案，決策改了 scaffold 跟著重生。</p>
<h2 id="交付形態-gate先勸退不必要的自建">交付形態 gate：先勸退不必要的自建</h2>
<p>一套為自建選型而生的訪談協議，第一個正式任務是勸退不必要的自建。自建的前提是差異化在軟體本身；差異化在商品、內容或服務品質的業務，託管平台、<a href="/blog/backend/knowledge-cards/baas/" data-link-title="BaaS（Backend as a Service）" data-link-desc="說明把認證、資料庫、檔案儲存、推播打包成現成模組、由前端 SDK 直連的後端交付形態">BaaS</a>、垂直 SaaS 可能是成本上更誠實的起點。跳過這個判斷，等於用整套自建流程回答一個本來就該用現成平台解的需求。交付形態光譜的完整論述（各形態的能力邊界、遷出代價、可遷出保險）見 <a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">交付形態選型：託管平台、BaaS 與自建的邊界</a>，訪談協議把它操作化成 gate。</p>
<p>gate 的第一個澄清問是身分問題：這個軟體是要賣的產品，還是經營業務的工具？「給健身教練用的課表系統」有兩種身分——要賣給眾多教練的產品（市場上的垂直 SaaS 是競爭對手，走自建），或教練管理自己學員的工具（同一批垂直 SaaS 正是該優先評估的託管候選）。同一句需求描述，兩種身分的結論相反。</p>
<p>回答「先自用、之後想賣」的雙重身分，用時間判準裁決：<strong>付費外部用戶離現在多遠</strong>。協議把裁決寫成兩條規則：外部買家已有承諾或近期有交付時程的，按「要賣的產品」走完整訪談，自家使用記為首租戶（dogfooding）；外部用戶還停在想像、目前唯一用戶是自己的，託管先行，平台野心轉成升級自建的 tripwire——協議給的觸發例是第一個付費外部用戶出現，或客製需求超出託管平台 API 的承載範圍。</p>
<p>gate 的收尾原則是尊重知情決定：受訪者聽完託管對照後仍選自建時，訪談照常繼續，勸退只做一次；選自建的動機（練手、控制權、長期成本）原樣記進決策記錄，讓未來重評者能還原當時的權重。</p>
<h2 id="用合成-dry-run-驗證協議">用合成 dry-run 驗證協議</h2>
<p>方法論本身也需要驗證。這套協議以一組結構化文件存在——訪談流程、各站的判讀表、決策記錄模板都是 agent 可直接執行的文件，驗證因此可以採用合成 dry-run：讓 agent 同時扮演訪談者與 persona 受訪者，從定錨到決策記錄完整走一遍協議，過程中記錄機械性斷點。斷點分五類：</p>
<ul>
<li><strong>路由斷裂</strong>：協議指向的段落不存在，或前後站接不上</li>
<li><strong>gate 矛盾</strong>：兩條判讀規則同時命中且結論相反，協議沒給裁決方式</li>
<li><strong>重複提問</strong>：同一風險在不同階段被當成新問題重新開放問</li>
<li><strong>資訊斷流</strong>：前站的產出（操作清單、event catalog）沒被後站引用</li>
<li><strong>判準缺席</strong>：協議要求做判讀，但沒給可操作的判讀條件</li>
</ul>
<p>dry-run 收尾時實際試填決策記錄模板，量測每個欄位填得出來的比例——填不出來的欄位就是協議在某一站漏問了。</p>
<p>第一輪驗證跑了兩個 persona：一個雙重身分的健身房管理 SaaS（自用兼想賣），一個 solo 開發者的付費電子報（最該被勸退自建的案例）。合計記錄 13 個斷點，在兩個 persona 覆蓋的路徑上路由斷裂為零——協議的檔案結構與觸發路由都接得起來；決策記錄試填率約 85-90%，缺口集中在交付形態相關欄位：gate 判讀依據寫不出來、混合形態的記錄格式沒有規格、自建動機沒有欄位可放。影響最大的斷點是兩個 persona 從不同方向撞上同一個 gate 矛盾：判讀表兩列規則同時命中且方向相反——健身房的「都是」讓垂直 SaaS 列與自建列並立，付費電子報既符合「託管平台標準域」也符合「產品本身是軟體」——協議當時沒有 tie-breaker，在最該被勸退的案例上這個斷點被評為阻斷級；前文的時間判準正是這次修復補上的。矛盾發生在協議最核心的判斷上，且在真實使用者撞到之前被攔下。兩場 dry-run 最終收斂出 8 項修復回寫協議，包括雙重身分的時間判準、混合形態的決策記錄規格、訪談問句去重、跨 domain 寫入的一致性判讀條件。</p>
<p>合成 dry-run 的邊界也要誠實：它抓得到機械性斷點，抓不到真實受訪者的不耐與理解斷層。agent 扮演的 persona 永遠有耐心答完所有問題、永遠聽得懂術語；真實使用者在第四輪提問時的疲乏、對「event catalog」一詞的困惑，要等真人訪談才會浮現。另一層盲點是同源：persona 由產生協議的同一個模型扮演，回答分布偏向協議作者想像得到的案例，超出這個想像的需求形態，結構上不會出現在合成驗證裡。合成驗證的定位是上線前的結構檢查，取代不了真實使用回饋。</p>
<h2 id="適用邊界與下一步">適用邊界與下一步</h2>
<p>完整協議的固定成本是數輪訪談加一份決策記錄，值得跑的情境有明確輪廓：</p>
<ul>
<li>產品會進 production，有真實使用者與失敗代價——失敗代價是核心問題階段的主要輸入，沒有它整條推導鏈失去分水嶺</li>
<li>團隊規模或交接需求讓「當初為什麼這樣選」值得被記錄——決策記錄的三種讀者（確認結論的現在使用者、回溯理由的未來維護者、等訊號的重評者）至少存在兩種</li>
<li>領域邊界有切分價值——操作主體超過一種、跨領域協作存在，domain / event 切分的產出才有承載對象</li>
</ul>
<p>反過來，快速原型與純內部腳本跑完整協議是過度設計——原型的目的是丟棄式驗證假設，設計決策活不過原型本身；這類情境取協議的最小子集就夠：定錨（確認規模假設）加防護底線裡的 secret 管理一條。另一類邊界是需求不可知：產品會進 production、但要提供哪些操作得靠市場驗證才知道的探索型產品，協議能整理的只有已知部分——它解「已知需求沒想清楚」的成本，解不了「需求本身未知」；這類情境先走薄切片迭代，需求穩定後再回來補完整訪談。</p>
<p>判讀方式可以收成一個問題：這個專案的設計決策，六個月後有人需要回答「當初為什麼這樣選」嗎？需要，完整協議的固定成本就划算；無此需求，取子集。</p>
<p>下一步路由：</p>
<ul>
<li>交付形態光譜與各形態的遷出代價：<a href="/blog/backend/00-service-selection/delivery-mode-selection/" data-link-title="0.21 交付形態選型：從全託管到自建的光譜與邊界" data-link-desc="在進入資料庫、快取與部署選型之前、先判斷服務該用託管平台（Wix / Shopify / Google Sites）、辦公生態自動化（Apps Script）、BaaS（Firebase）、半託管 CMS（WordPress）還是自建、並為日後遷往自建保留可遷出路徑">交付形態選型</a></li>
<li>判定自建後的服務選型順序：<a href="/blog/backend/00-service-selection/backend-demand-taxonomy/" data-link-title="0.0 後端需求分類地圖" data-link-desc="先從需求形狀辨識狀態、讀取、非同步、即時、診斷、交付與可靠性問題">後端需求分類地圖</a></li>
<li>不可丟 event 的執行層機制：<a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> 與 <a href="/blog/backend/knowledge-cards/outbox-pattern/" data-link-title="Outbox Pattern" data-link-desc="說明資料庫狀態變更與事件發布如何透過 outbox 維持一致">outbox pattern</a> 知識卡</li>
</ul>]]></content:encoded></item><item><title>Codex 與 Claude Code Statusline 相容設計方法</title><link>https://tarrragon.github.io/blog/record/codex-%E8%88%87-claude-code-statusline-%E7%9B%B8%E5%AE%B9%E8%A8%AD%E8%A8%88%E6%96%B9%E6%B3%95/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/codex-%E8%88%87-claude-code-statusline-%E7%9B%B8%E5%AE%B9%E8%A8%AD%E8%A8%88%E6%96%B9%E6%B3%95/</guid><description>&lt;h2 id="問題錨點">問題錨點&lt;/h2>
&lt;p>Statusline 相容設計的核心責任是把「資料輸入契約」和「畫面渲染邏輯」分開。Claude Code 已經提供 command-backed statusline，會把 session JSON 丟進命令的 stdin；Codex 目前公開的設定則是 &lt;code>tui.status_line&lt;/code> 字串項目陣列，契約停在內建 footer item 的排列與選擇。&lt;/p>
&lt;p>這個差異讓「同一個 statusline 工具同時支援兩邊」要從輸入契約對齊開始。真正要做的是先建立一層輸入正規化：Claude JSON、Codex 既有或未來 JSON、手動測試 JSON 都先轉成同一個內部狀態，再交給同一套 renderer。&lt;/p>
&lt;h2 id="case-first-觀察">Case-first 觀察&lt;/h2>
&lt;p>Case-first 查詢的目的，是先看社群實際卡在哪裡，再決定要改工具還是改使用方式。本次查詢到的案例集中在 OpenAI Codex repo issue 與官方文件，顯示需求已經存在，但 Codex 的 command-backed statusline 仍屬提案或缺口。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Case&lt;/th>
 &lt;th>觀察&lt;/th>
 &lt;th>判讀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://code.claude.com/docs/en/statusline">Claude Code status line 官方文件&lt;/a>&lt;/td>
 &lt;td>Claude Code 的 statusline command 會從 stdin 收到 JSON，stdout 的每一行會顯示成 status area。&lt;/td>
 &lt;td>Claude 端是穩定可用的 producer，工具可依賴 &lt;code>model&lt;/code>、&lt;code>workspace&lt;/code>、&lt;code>context_window&lt;/code>、&lt;code>rate_limits&lt;/code> 這類欄位。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://developers.openai.com/codex/config-reference">OpenAI Codex config reference&lt;/a>&lt;/td>
 &lt;td>&lt;code>tui.status_line&lt;/code> 的型別是 &lt;code>array&amp;lt;string&amp;gt;&lt;/code> 或 &lt;code>null&lt;/code>，用途是排列 footer status-line item identifiers。&lt;/td>
 &lt;td>Codex 端目前公開契約屬於內建項目清單。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://github.com/openai/codex/issues/17827">openai/codex #17827&lt;/a>&lt;/td>
 &lt;td>使用者明確要求 Codex 加入類似 Claude Code 的 &lt;code>statusLine.command&lt;/code>。&lt;/td>
 &lt;td>社群已把 Claude Code statusline 當成對照基準，混用痛點是真實需求。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://github.com/openai/codex/issues/20043">openai/codex #20043&lt;/a>&lt;/td>
 &lt;td>提案列出 Codex 既有 &lt;code>status_line&lt;/code> picker，並要求外部 command 模式、ANSI 顏色與 stdin JSON。&lt;/td>
 &lt;td>未來若 Codex 採納此類設計，statusline 工具需要同時支援 Codex 風格 JSON 與 Claude 欄位。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://github.com/openai/codex/issues/20244">openai/codex #20244&lt;/a>&lt;/td>
 &lt;td>另一個使用者提出 command-backed item 或 persistent banner，並被標為 #17827 的 duplicate。&lt;/td>
 &lt;td>重複 issue 表示需求已經多次出現；相容設計應預留 Codex command input，讓後續定案只需要調整 mapper。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://github.com/openai/codex/issues/21324">openai/codex #21324&lt;/a>&lt;/td>
 &lt;td>使用者在 local branch 實作 context/token usage 狀態項目與進度條。&lt;/td>
 &lt;td>Codex 社群也在補足使用量可視化，但路徑偏向內建 item，和 Claude 的外部 renderer 是兩種不同擴充模型。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="wrap-判讀">WRAP 判讀&lt;/h2>
&lt;p>Anchor Check：目標是讓 &lt;code>cc-statusline&lt;/code> 的核心能力可被兩種工具共用。使用者真正需要的是少維護一套 statusline 邏輯，並在 Codex 具備 command-backed 入口時保留既有 renderer。&lt;/p>
&lt;p>Step 0 資料充足度：足以做工具內部改造，尚不足以宣稱 Codex TUI 目前能直接執行 &lt;code>cc-statusline&lt;/code>。官方文件只保證 &lt;code>tui.status_line&lt;/code> 是字串陣列；社群 issue 裡的 command JSON 仍是提案階段。&lt;/p>
&lt;p>Widen Options：可選方案有三種。&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>A：只用 Codex 內建 &lt;code>tui.status_line&lt;/code>&lt;/td>
 &lt;td>不改 &lt;code>cc-statusline&lt;/code>，在 Codex 設定內建項目。&lt;/td>
 &lt;td>只需要模型、目錄、git branch、context 這類內建欄位時可用。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B：改 &lt;code>cc-statusline&lt;/code> 成雙 schema renderer&lt;/td>
 &lt;td>保留 Claude JSON，新增 Codex / generic JSON normalization。&lt;/td>
 &lt;td>希望同一套 renderer 服務 Claude、未來 Codex command hook、tmux / wrapper 測試時最划算。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>C：Fork 兩套工具&lt;/td>
 &lt;td>Claude 一套、Codex 一套，各自用不同資料模型。&lt;/td>
 &lt;td>只有在兩邊 UI 契約長期分歧且需求完全不同時才合理。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Reality Test：目前 Codex 的公開設定停在內建 item 排列，所以 B 的立即價值是讓工具「具備 Codex 相容輸入能力」。反向驗證是：若未來 Codex 最終採用完全不同的 command JSON，B 的 normalization 層仍只需新增一個 mapper，renderer 可維持同一套。&lt;/p></description><content:encoded><![CDATA[<h2 id="問題錨點">問題錨點</h2>
<p>Statusline 相容設計的核心責任是把「資料輸入契約」和「畫面渲染邏輯」分開。Claude Code 已經提供 command-backed statusline，會把 session JSON 丟進命令的 stdin；Codex 目前公開的設定則是 <code>tui.status_line</code> 字串項目陣列，契約停在內建 footer item 的排列與選擇。</p>
<p>這個差異讓「同一個 statusline 工具同時支援兩邊」要從輸入契約對齊開始。真正要做的是先建立一層輸入正規化：Claude JSON、Codex 既有或未來 JSON、手動測試 JSON 都先轉成同一個內部狀態，再交給同一套 renderer。</p>
<h2 id="case-first-觀察">Case-first 觀察</h2>
<p>Case-first 查詢的目的，是先看社群實際卡在哪裡，再決定要改工具還是改使用方式。本次查詢到的案例集中在 OpenAI Codex repo issue 與官方文件，顯示需求已經存在，但 Codex 的 command-backed statusline 仍屬提案或缺口。</p>
<table>
  <thead>
      <tr>
          <th>Case</th>
          <th>觀察</th>
          <th>判讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://code.claude.com/docs/en/statusline">Claude Code status line 官方文件</a></td>
          <td>Claude Code 的 statusline command 會從 stdin 收到 JSON，stdout 的每一行會顯示成 status area。</td>
          <td>Claude 端是穩定可用的 producer，工具可依賴 <code>model</code>、<code>workspace</code>、<code>context_window</code>、<code>rate_limits</code> 這類欄位。</td>
      </tr>
      <tr>
          <td><a href="https://developers.openai.com/codex/config-reference">OpenAI Codex config reference</a></td>
          <td><code>tui.status_line</code> 的型別是 <code>array&lt;string&gt;</code> 或 <code>null</code>，用途是排列 footer status-line item identifiers。</td>
          <td>Codex 端目前公開契約屬於內建項目清單。</td>
      </tr>
      <tr>
          <td><a href="https://github.com/openai/codex/issues/17827">openai/codex #17827</a></td>
          <td>使用者明確要求 Codex 加入類似 Claude Code 的 <code>statusLine.command</code>。</td>
          <td>社群已把 Claude Code statusline 當成對照基準，混用痛點是真實需求。</td>
      </tr>
      <tr>
          <td><a href="https://github.com/openai/codex/issues/20043">openai/codex #20043</a></td>
          <td>提案列出 Codex 既有 <code>status_line</code> picker，並要求外部 command 模式、ANSI 顏色與 stdin JSON。</td>
          <td>未來若 Codex 採納此類設計，statusline 工具需要同時支援 Codex 風格 JSON 與 Claude 欄位。</td>
      </tr>
      <tr>
          <td><a href="https://github.com/openai/codex/issues/20244">openai/codex #20244</a></td>
          <td>另一個使用者提出 command-backed item 或 persistent banner，並被標為 #17827 的 duplicate。</td>
          <td>重複 issue 表示需求已經多次出現；相容設計應預留 Codex command input，讓後續定案只需要調整 mapper。</td>
      </tr>
      <tr>
          <td><a href="https://github.com/openai/codex/issues/21324">openai/codex #21324</a></td>
          <td>使用者在 local branch 實作 context/token usage 狀態項目與進度條。</td>
          <td>Codex 社群也在補足使用量可視化，但路徑偏向內建 item，和 Claude 的外部 renderer 是兩種不同擴充模型。</td>
      </tr>
  </tbody>
</table>
<h2 id="wrap-判讀">WRAP 判讀</h2>
<p>Anchor Check：目標是讓 <code>cc-statusline</code> 的核心能力可被兩種工具共用。使用者真正需要的是少維護一套 statusline 邏輯，並在 Codex 具備 command-backed 入口時保留既有 renderer。</p>
<p>Step 0 資料充足度：足以做工具內部改造，尚不足以宣稱 Codex TUI 目前能直接執行 <code>cc-statusline</code>。官方文件只保證 <code>tui.status_line</code> 是字串陣列；社群 issue 裡的 command JSON 仍是提案階段。</p>
<p>Widen Options：可選方案有三種。</p>
<table>
  <thead>
      <tr>
          <th>選項</th>
          <th>策略</th>
          <th>適用條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>A：只用 Codex 內建 <code>tui.status_line</code></td>
          <td>不改 <code>cc-statusline</code>，在 Codex 設定內建項目。</td>
          <td>只需要模型、目錄、git branch、context 這類內建欄位時可用。</td>
      </tr>
      <tr>
          <td>B：改 <code>cc-statusline</code> 成雙 schema renderer</td>
          <td>保留 Claude JSON，新增 Codex / generic JSON normalization。</td>
          <td>希望同一套 renderer 服務 Claude、未來 Codex command hook、tmux / wrapper 測試時最划算。</td>
      </tr>
      <tr>
          <td>C：Fork 兩套工具</td>
          <td>Claude 一套、Codex 一套，各自用不同資料模型。</td>
          <td>只有在兩邊 UI 契約長期分歧且需求完全不同時才合理。</td>
      </tr>
  </tbody>
</table>
<p>Reality Test：目前 Codex 的公開設定停在內建 item 排列，所以 B 的立即價值是讓工具「具備 Codex 相容輸入能力」。反向驗證是：若未來 Codex 最終採用完全不同的 command JSON，B 的 normalization 層仍只需新增一個 mapper，renderer 可維持同一套。</p>
<p>Attain Distance：B 的長期成本最低，因為 statusline 最容易變動的是輸入欄位名稱，最穩定的是使用者想看的資訊：專案、環境、輸入法、模型、context、rate limit、git worktree。把欄位差異收斂在 normalization 層，能避免每加入一個工具就複製一次畫面邏輯。</p>
<p>Prepare to be Wrong：若 Codex 不採納外部 command statusline，這次改造仍可用於手動測試、tmux status、其他 wrapper，且不影響 Claude Code 原始入口。若 Codex 採納但欄位名稱不同，新增 mapper 即可。</p>
<p>Tripwire：當 OpenAI Codex 文件把 <code>tui.status_line</code> 從 <code>array&lt;string&gt;</code> 擴充為 command 或 table schema 時，重新檢查 <code>cc-statusline</code> 的 Codex mapper。若 Codex issue #17827 關閉並附帶實作 PR，也應重新校準欄位名稱。</p>
<h2 id="實作策略">實作策略</h2>
<p>相容設計的正確切點是輸入正規化層。<code>cc-statusline</code> 應維持一個內部狀態模型，並接受多種外部 payload：</p>
<table>
  <thead>
      <tr>
          <th>外部 payload</th>
          <th>正規化規則</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Code</td>
          <td>直接讀 <code>model.display_name</code>、<code>workspace.project_dir</code>、<code>context_window.used_percentage</code>、<code>rate_limits</code>。</td>
      </tr>
      <tr>
          <td>Codex proposed / generic</td>
          <td>接受 <code>model</code> 字串、<code>cwd</code> / <code>project_root</code>、<code>context.used_percent</code> / <code>context.remaining_percent</code>、<code>limits.five_hour</code> / <code>limits.weekly</code>。</td>
      </tr>
      <tr>
          <td>手動測試 payload</td>
          <td>只要能提供模型與目錄，就輸出可讀 statusline；缺 rate limit 時自動省略。</td>
      </tr>
  </tbody>
</table>
<p>這個切點保留了 Claude Code 既有功能，因為原本的欄位不需要改名，也不需要改設定檔。新增行為只在非 Claude payload 進來時啟動，屬於向後相容的讀取能力。</p>
<h2 id="操作路由">操作路由</h2>
<p>現在可立即使用的路由是 Claude Code 原設定：在 <code>~/.claude/settings.json</code> 裡設定 <code>statusLine.command</code> 指向 <code>cc-statusline</code>。這條路由使用官方支援的 stdin JSON，適合日常使用。</p>
<p>Codex 目前可立即使用的路由是內建 footer item：在 <code>~/.codex/config.toml</code> 設定 <code>tui.status_line = [...]</code>。這條路由使用 Codex 內建 renderer，能顯示 Codex 已支援的內建狀態。</p>
<p>未來 Codex 若支援 command-backed statusline，路由應該指向同一個 <code>cc-statusline</code> binary。工具端已經能接受 Codex / generic JSON 時，設定層只要補 command 指向，不需要重寫 renderer。</p>
<h2 id="實測記錄2026-05-14">實測記錄（2026-05-14）</h2>
<p>這次排查的核心責任是先確認「工具本身可用」還是「接入路由不對」。先把 binary 行為跟 TUI 設定拆開檢查，才能避免把路由問題誤判成程式 bug。</p>
<h3 id="觀察">觀察</h3>
<ul>
<li><code>cc-statusline</code> 程式已支援 generic/Codex-style payload，手動餵 JSON 可正確輸出模型與 context 資訊。</li>
<li><code>~/.claude/settings.json</code> 使用 <code>statusLine.command</code> 指向 <code>/Users/mac-eric/go/bin/cc-statusline</code>，Claude Code 路由成立。</li>
<li><code>~/.codex/config.toml</code> 的 <code>tui.status_line</code> 是內建 item 陣列，這條路由不會執行外部 binary。</li>
<li>Codex 內建 footer 的實際輸出已觀察到：<code>gpt-5.3-codex medium · Context 100% left · ~/project/blog</code>。</li>
</ul>
<h3 id="判讀">判讀</h3>
<p>Codex 端「沒有生效」的主因是契約邊界：<code>tui.status_line</code> 只負責排列內建欄位，不負責執行 command。<code>cc-statusline</code> 的 renderer 相容能力屬於預留未來入口，不會在現有 Codex 內建 footer 流程自動觸發。</p>
<h3 id="操作">操作</h3>
<p>為了讓 Codex 內建 footer 至少顯示模型與 context 資訊，已調整：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tui</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">status_line</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;model-with-reasoning&#34;</span><span class="p">,</span> <span class="s2">&#34;context-remaining&#34;</span><span class="p">,</span> <span class="s2">&#34;current-dir&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">status_line_use_colors</span> <span class="p">=</span> <span class="kc">true</span></span></span></code></pre></div><p>這個設定可讓 Codex 使用內建項目顯示 <code>model-with-reasoning</code> 與 context remaining；格式由 Codex 內建 renderer 決定，不等同 <code>cc-statusline</code> 的自訂輸出字串。</p>
<h3 id="驗證指令">驗證指令</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;%s\n&#39;</span> <span class="s1">&#39;{&#34;model&#34;:&#34;gpt-5.3-codex&#34;,&#34;reasoning_effort&#34;:&#34;medium&#34;,&#34;project_root&#34;:&#34;~/project/blog&#34;,&#34;context&#34;:{&#34;remaining_percent&#34;:100}}&#39;</span> <span class="p">|</span> /Users/mac-eric/go/bin/cc-statusline</span></span></code></pre></div><p>預期結果是主行包含 <code>gpt-5.3-codex medium</code>，context 顯示為 <code>Context 100% left</code>。這一步驗證的是 binary 能力，不是 Codex 內建 footer contract。</p>
<h2 id="檢查清單">檢查清單</h2>
<ul>
<li>Claude Code 原本的 JSON payload 仍能輸出相同欄位。</li>
<li>Codex / generic payload 不造成 parse error。</li>
<li><code>model</code> 同時支援 object 與 string。</li>
<li><code>context</code> 同時支援 used percentage 與 remaining percentage。</li>
<li>rate limit 缺席時只省略對應 segment，不影響專案、模型、git worktree。</li>
<li>README 明確標示 Codex 目前限制，避免讀者以為 Codex 已能直接執行外部 statusline command。</li>
</ul>
]]></content:encoded></item><item><title>測試全過但有 Bug</title><link>https://tarrragon.github.io/blog/record/%E6%B8%AC%E8%A9%A6%E5%85%A8%E9%81%8E%E4%BD%86%E6%9C%89-bug/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E6%B8%AC%E8%A9%A6%E5%85%A8%E9%81%8E%E4%BD%86%E6%9C%89-bug/</guid><description>&lt;blockquote>
&lt;p>2026-03，開發線上點單多廚房印表機列印功能。
寫了 28 個測試，全部通過，上實機後陸續發現四個 Bug。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="發生了什麼事">發生了什麼事&lt;/h2>
&lt;p>功能的核心流程：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">收到追加點單
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> → 把品項分派到對應的廚房印表機
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> → 組裝收據內容（標題、品名+數量、備註等）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> → 逐行列印
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> → 單行文字（標題、桌號）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> → 多欄表格（品名 + 數量）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上實機後，四個 Bug 依序出現——因為它們在同一條執行路徑的不同深度，每修一個，程式才能走到下一個：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Bug 1（印表機內部元件未初始化）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ 修好，程式碼走得更遠
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">Bug 2（品項分派邏輯錯誤，全部送到同一台）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓ 修好，兩台印表機都有被呼叫
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">Bug 3（多欄表格的欄位寬度不符合規定）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ 修好，表格列印通過驗證，繼續往下
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">Bug 4（空行觸發第三方 library 的越界錯誤）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Bug&lt;/th>
 &lt;th>出了什麼事&lt;/th>
 &lt;th>測試沒抓到的原因&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Bug 1: 印表機元件未初始化&lt;/td>
 &lt;td>模擬印表機的初始化漏掉了內部元件&lt;/td>
 &lt;td>只測了「送出資料」，沒測「組裝列印指令」這個步驟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bug 2: 品項全分到同一台&lt;/td>
 &lt;td>分派邏輯找到第一台可用的印表機就全部送過去&lt;/td>
 &lt;td>手動構造預期結果，分派邏輯沒有被執行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bug 3: 欄位寬度錯誤&lt;/td>
 &lt;td>欄位比例（3:1=4）不符合 library 要求的總和 12&lt;/td>
 &lt;td>模擬的收據內容只有純文字行，沒有多欄表格行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bug 4: 空行越界錯誤&lt;/td>
 &lt;td>第三方 library 沒有處理空字串&lt;/td>
 &lt;td>被 Bug 3 擋住，程式從未執行到這一行&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼一次只能發現一個-bug">為什麼一次只能發現一個 Bug&lt;/h2>
&lt;p>四個 Bug 都在同一條執行路徑上，只是深度不同。程式走到第一個錯誤就中斷了，後面的都被遮蔽。&lt;/p>
&lt;p>而測試沒有發現這些問題，是因為測試沒有走過這條完整路徑。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">真實路徑： 接收訂單 → 組裝收據 → 列印中心 → 表格列印/文字列印 → 印表機底層
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">測試路徑： 接收訂單 → 中斷（手動構造結果，後面都沒跑）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">後來補了模擬元件：接收訂單 → 模擬收據產生器 → 列印中心 → 模擬印表機 → 底層
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ↑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> 但模擬的收據只有純文字行
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> → 文字列印有被覆蓋，表格列印沒有
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> → Bug 3, 4 仍然隱藏&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這次的問題在測試覆蓋的路徑深度。&lt;/p>
&lt;hr>
&lt;h2 id="回顧這次遇到的五個事故">回顧：這次遇到的五個事故&lt;/h2>
&lt;h3 id="1-測試的是手動構造的結果不是程式的行為">1. 測試的是「手動構造的結果」，不是「程式的行為」&lt;/h3>
&lt;p>&lt;strong>怎麼發現的：&lt;/strong> 實機上兩台廚房印表機只有一台收到品項，另一台完全沒動。但測試裡「品項分派邏輯」的測試案例是通過的。&lt;/p>
&lt;p>&lt;strong>怎麼找到原因的：&lt;/strong> 回頭看測試程式碼，發現測試裡的分派結果是手動寫死的，不是由分派邏輯算出來的：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;品名長度奇數分配到第一台&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="kd">final&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OnlineOrderPrintResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nl">itemPrinterMapping:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;item-1&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;kitchen-2&amp;#39;&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="c1">// 寫死的值
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="n">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">applyPrintResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">record&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">kitchenItemPrintJobs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;item-1&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">printerId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;kitchen-2&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個測試驗證的是「把結果存進去再讀出來，資料有沒有一致」，但品項分派的程式碼從頭到尾沒有被執行過。測試名稱寫的是分派邏輯，實際測的是資料儲存。&lt;/p>
&lt;p>&lt;strong>怎麼修的：&lt;/strong> 改成從入口方法開始呼叫，讓品項分派的邏輯實際跑一遍。跑完之後 Bug 2 就出現了——分派邏輯的 fallback 條件寫錯，所有品項都被送到同一台。&lt;/p>
&lt;hr>
&lt;h3 id="2-只測了子類別自己的方法沒測從父類別繼承的方法">2. 只測了子類別自己的方法，沒測從父類別繼承的方法&lt;/h3>
&lt;p>&lt;strong>怎麼發現的：&lt;/strong> 實機上廚房印表機列印全部失敗，log 顯示內部元件未初始化的錯誤，但測試裡模擬印表機的初始化和列印測試都是通過的。&lt;/p>
&lt;p>&lt;strong>怎麼找到原因的：&lt;/strong> 比對測試和實際程式碼的呼叫路徑。測試裡呼叫的是模擬印表機自己覆寫的「送出資料」方法（改成什麼都不做），但實際列印時上層呼叫的是從父類別繼承的「組裝列印指令」方法，這個方法內部依賴一個需要初始化的元件。測試覆蓋到的方法，和實際執行路徑走到的方法不是同一個。&lt;/p>
&lt;p>&lt;strong>怎麼修的：&lt;/strong> 在測試中加入對繼承方法的測試——初始化後呼叫「組裝列印指令」確認不報錯，以及未初始化時呼叫確認會拋出錯誤。同時修正模擬印表機的初始化方法，補上內部元件的建立。&lt;/p>
&lt;hr>
&lt;h3 id="3-斷言只檢查有沒有沒檢查對不對">3. 斷言只檢查「有沒有」，沒檢查「對不對」&lt;/h3>
&lt;p>&lt;strong>怎麼發現的：&lt;/strong> 修完 Bug 1 和 2 之後重跑測試，整合測試通過了。但看 log 發現廚房 1 和廚房 2 的列印結果都是 &lt;code>false&lt;/code>（失敗），和測試通過的結果矛盾。&lt;/p>
&lt;p>&lt;strong>怎麼找到原因的：&lt;/strong> 回頭看斷言，發現寫的是 &lt;code>containsKey&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">kitchenResults&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">containsKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;kitchen-1&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">isTrue&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個斷言只檢查「有沒有這台印表機的結果」，不管結果是成功還是失敗。列印在 try-catch 裡失敗後回傳 &lt;code>false&lt;/code>，但 key 存在，所以斷言通過。&lt;/p>
&lt;p>&lt;strong>怎麼修的：&lt;/strong> 改成直接檢查值 &lt;code>expect(result.kitchenResults['kitchen-1'], isTrue)&lt;/code>。改完之後測試立刻失敗，顯示列印結果確實是 &lt;code>false&lt;/code>。這才發現測試環境缺少收據產生器的依賴，列印路徑在組裝收據的步驟就斷了，被 try-catch 吞掉回傳 &lt;code>false&lt;/code>。&lt;/p>
&lt;hr>
&lt;h3 id="4-模擬元件的回傳資料只覆蓋了部分分支">4. 模擬元件的回傳資料只覆蓋了部分分支&lt;/h3>
&lt;p>&lt;strong>怎麼發現的：&lt;/strong> 修完 Bug 1、2，也修正了斷言（坑 3）之後，測試全過，列印結果也都是 &lt;code>true&lt;/code>。但上實機測試時，廚房印表機仍然全部失敗，log 顯示「欄位寬度總和必須等於 12」。&lt;/p>
&lt;p>&lt;strong>怎麼找到原因的：&lt;/strong> 測試環境和實機的差異在於收據的內容。實機用的是真實的廚房收據模板，包含多欄表格（品名+數量）。測試用的是模擬的收據產生器，只回傳一行純文字：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">class&lt;/span> &lt;span class="nc">FakeReceiptBuilderService&lt;/span> &lt;span class="kd">extends&lt;/span> &lt;span class="n">ReceiptBuilderService&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">Future&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">ReceiptLine&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">buildReceiptLines&lt;/span>&lt;span class="p">(...)&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ReceiptLine&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">singleLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">)];&lt;/span> &lt;span class="c1">// 只有標題
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>純文字走的是「文字列印」，多欄表格走的是「表格列印」——這是兩條不同的分支。模擬的資料只觸發了文字列印，表格列印從未被測試執行過。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>2026-03，開發線上點單多廚房印表機列印功能。
寫了 28 個測試，全部通過，上實機後陸續發現四個 Bug。</p></blockquote>
<hr>
<h2 id="發生了什麼事">發生了什麼事</h2>
<p>功能的核心流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">收到追加點單
</span></span><span class="line"><span class="ln">2</span><span class="cl">  → 把品項分派到對應的廚房印表機
</span></span><span class="line"><span class="ln">3</span><span class="cl">  → 組裝收據內容（標題、品名+數量、備註等）
</span></span><span class="line"><span class="ln">4</span><span class="cl">  → 逐行列印
</span></span><span class="line"><span class="ln">5</span><span class="cl">    → 單行文字（標題、桌號）
</span></span><span class="line"><span class="ln">6</span><span class="cl">    → 多欄表格（品名 + 數量）</span></span></code></pre></div><p>上實機後，四個 Bug 依序出現——因為它們在同一條執行路徑的不同深度，每修一個，程式才能走到下一個：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Bug 1（印表機內部元件未初始化）
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ 修好，程式碼走得更遠
</span></span><span class="line"><span class="ln">3</span><span class="cl">Bug 2（品項分派邏輯錯誤，全部送到同一台）
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ↓ 修好，兩台印表機都有被呼叫
</span></span><span class="line"><span class="ln">5</span><span class="cl">Bug 3（多欄表格的欄位寬度不符合規定）
</span></span><span class="line"><span class="ln">6</span><span class="cl">  ↓ 修好，表格列印通過驗證，繼續往下
</span></span><span class="line"><span class="ln">7</span><span class="cl">Bug 4（空行觸發第三方 library 的越界錯誤）</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Bug</th>
          <th>出了什麼事</th>
          <th>測試沒抓到的原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bug 1: 印表機元件未初始化</td>
          <td>模擬印表機的初始化漏掉了內部元件</td>
          <td>只測了「送出資料」，沒測「組裝列印指令」這個步驟</td>
      </tr>
      <tr>
          <td>Bug 2: 品項全分到同一台</td>
          <td>分派邏輯找到第一台可用的印表機就全部送過去</td>
          <td>手動構造預期結果，分派邏輯沒有被執行</td>
      </tr>
      <tr>
          <td>Bug 3: 欄位寬度錯誤</td>
          <td>欄位比例（3:1=4）不符合 library 要求的總和 12</td>
          <td>模擬的收據內容只有純文字行，沒有多欄表格行</td>
      </tr>
      <tr>
          <td>Bug 4: 空行越界錯誤</td>
          <td>第三方 library 沒有處理空字串</td>
          <td>被 Bug 3 擋住，程式從未執行到這一行</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼一次只能發現一個-bug">為什麼一次只能發現一個 Bug</h2>
<p>四個 Bug 都在同一條執行路徑上，只是深度不同。程式走到第一個錯誤就中斷了，後面的都被遮蔽。</p>
<p>而測試沒有發現這些問題，是因為測試沒有走過這條完整路徑。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">真實路徑：  接收訂單 → 組裝收據 → 列印中心 → 表格列印/文字列印 → 印表機底層
</span></span><span class="line"><span class="ln">2</span><span class="cl">測試路徑：  接收訂單 → 中斷（手動構造結果，後面都沒跑）
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">後來補了模擬元件：接收訂單 → 模擬收據產生器 → 列印中心 → 模擬印表機 → 底層
</span></span><span class="line"><span class="ln">5</span><span class="cl">                                  ↑
</span></span><span class="line"><span class="ln">6</span><span class="cl">                           但模擬的收據只有純文字行
</span></span><span class="line"><span class="ln">7</span><span class="cl">                           → 文字列印有被覆蓋，表格列印沒有
</span></span><span class="line"><span class="ln">8</span><span class="cl">                           → Bug 3, 4 仍然隱藏</span></span></code></pre></div><p>這次的問題在測試覆蓋的路徑深度。</p>
<hr>
<h2 id="回顧這次遇到的五個事故">回顧：這次遇到的五個事故</h2>
<h3 id="1-測試的是手動構造的結果不是程式的行為">1. 測試的是「手動構造的結果」，不是「程式的行為」</h3>
<p><strong>怎麼發現的：</strong> 實機上兩台廚房印表機只有一台收到品項，另一台完全沒動。但測試裡「品項分派邏輯」的測試案例是通過的。</p>
<p><strong>怎麼找到原因的：</strong> 回頭看測試程式碼，發現測試裡的分派結果是手動寫死的，不是由分派邏輯算出來的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;品名長度奇數分配到第一台&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="n">OnlineOrderPrintResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nl">itemPrinterMapping:</span> <span class="p">{</span><span class="s1">&#39;item-1&#39;</span><span class="o">:</span> <span class="s1">&#39;kitchen-2&#39;</span><span class="p">},</span> <span class="c1">// 寫死的值
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="n">record</span><span class="p">.</span><span class="n">applyPrintResult</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">kitchenItemPrintJobs</span><span class="p">[</span><span class="s1">&#39;item-1&#39;</span><span class="p">]</span><span class="o">!</span><span class="p">.</span><span class="n">printerId</span><span class="p">,</span> <span class="s1">&#39;kitchen-2&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>這個測試驗證的是「把結果存進去再讀出來，資料有沒有一致」，但品項分派的程式碼從頭到尾沒有被執行過。測試名稱寫的是分派邏輯，實際測的是資料儲存。</p>
<p><strong>怎麼修的：</strong> 改成從入口方法開始呼叫，讓品項分派的邏輯實際跑一遍。跑完之後 Bug 2 就出現了——分派邏輯的 fallback 條件寫錯，所有品項都被送到同一台。</p>
<hr>
<h3 id="2-只測了子類別自己的方法沒測從父類別繼承的方法">2. 只測了子類別自己的方法，沒測從父類別繼承的方法</h3>
<p><strong>怎麼發現的：</strong> 實機上廚房印表機列印全部失敗，log 顯示內部元件未初始化的錯誤，但測試裡模擬印表機的初始化和列印測試都是通過的。</p>
<p><strong>怎麼找到原因的：</strong> 比對測試和實際程式碼的呼叫路徑。測試裡呼叫的是模擬印表機自己覆寫的「送出資料」方法（改成什麼都不做），但實際列印時上層呼叫的是從父類別繼承的「組裝列印指令」方法，這個方法內部依賴一個需要初始化的元件。測試覆蓋到的方法，和實際執行路徑走到的方法不是同一個。</p>
<p><strong>怎麼修的：</strong> 在測試中加入對繼承方法的測試——初始化後呼叫「組裝列印指令」確認不報錯，以及未初始化時呼叫確認會拋出錯誤。同時修正模擬印表機的初始化方法，補上內部元件的建立。</p>
<hr>
<h3 id="3-斷言只檢查有沒有沒檢查對不對">3. 斷言只檢查「有沒有」，沒檢查「對不對」</h3>
<p><strong>怎麼發現的：</strong> 修完 Bug 1 和 2 之後重跑測試，整合測試通過了。但看 log 發現廚房 1 和廚房 2 的列印結果都是 <code>false</code>（失敗），和測試通過的結果矛盾。</p>
<p><strong>怎麼找到原因的：</strong> 回頭看斷言，發現寫的是 <code>containsKey</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">kitchenResults</span><span class="p">.</span><span class="n">containsKey</span><span class="p">(</span><span class="s1">&#39;kitchen-1&#39;</span><span class="p">),</span> <span class="n">isTrue</span><span class="p">);</span></span></span></code></pre></div><p>這個斷言只檢查「有沒有這台印表機的結果」，不管結果是成功還是失敗。列印在 try-catch 裡失敗後回傳 <code>false</code>，但 key 存在，所以斷言通過。</p>
<p><strong>怎麼修的：</strong> 改成直接檢查值 <code>expect(result.kitchenResults['kitchen-1'], isTrue)</code>。改完之後測試立刻失敗，顯示列印結果確實是 <code>false</code>。這才發現測試環境缺少收據產生器的依賴，列印路徑在組裝收據的步驟就斷了，被 try-catch 吞掉回傳 <code>false</code>。</p>
<hr>
<h3 id="4-模擬元件的回傳資料只覆蓋了部分分支">4. 模擬元件的回傳資料只覆蓋了部分分支</h3>
<p><strong>怎麼發現的：</strong> 修完 Bug 1、2，也修正了斷言（坑 3）之後，測試全過，列印結果也都是 <code>true</code>。但上實機測試時，廚房印表機仍然全部失敗，log 顯示「欄位寬度總和必須等於 12」。</p>
<p><strong>怎麼找到原因的：</strong> 測試環境和實機的差異在於收據的內容。實機用的是真實的廚房收據模板，包含多欄表格（品名+數量）。測試用的是模擬的收據產生器，只回傳一行純文字：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">class</span> <span class="nc">FakeReceiptBuilderService</span> <span class="kd">extends</span> <span class="n">ReceiptBuilderService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">ReceiptLine</span><span class="o">&gt;&gt;</span> <span class="n">buildReceiptLines</span><span class="p">(...)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">ReceiptLine</span><span class="p">.</span><span class="n">singleLine</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">title</span><span class="p">)];</span> <span class="c1">// 只有標題
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>純文字走的是「文字列印」，多欄表格走的是「表格列印」——這是兩條不同的分支。模擬的資料只觸發了文字列印，表格列印從未被測試執行過。</p>
<ul>
<li>純文字列印有被覆蓋 → 沒問題</li>
<li>多欄表格列印沒有被觸發 → Bug 3 仍然隱藏</li>
<li>空行列印沒有被觸發 → Bug 4 仍然隱藏</li>
</ul>
<p><strong>怎麼修的：</strong> 在印表機的表格列印方法中加入自動正規化，將欄位比例換算為符合 library 要求的總和 12。這是適配層的修復，所有收據模板都不需要修改。</p>
<hr>
<h3 id="5-第三方-library-的地雷前面都做對了才踩到">5. 第三方 library 的地雷——前面都做對了才踩到</h3>
<p><strong>怎麼發現的：</strong> 修完 Bug 3 之後再上實機，廚房印表機仍然失敗，但錯誤訊息不同了——從「欄位寬度總和必須等於 12」變成「RangeError: Valid value range is empty: 0」。</p>
<p><strong>怎麼找到原因的：</strong> 從 log 看到列印標題和桌號成功（兩次資料送出），在第三行（空行）就失敗了。追蹤到第三方 library 的原始碼，發現它在解析文字時會取第一個字元來判斷是否為中文字，但沒有處理空字串的情況，直接對空字串取 <code>text[0]</code> 導致越界。</p>
<p>這個問題一直存在，但之前 Bug 3 擋在前面（程式在表格列印就失敗了，走不到後面的空行列印），前三個 Bug 都修好之後，執行路徑才真正打通，觸發了這個潛在問題。</p>
<p>從另一個角度看，能走到 Bug 4 代表前面的修復都是有效的。</p>
<p><strong>怎麼修的：</strong> 在呼叫 library 之前加了空字串的前置檢查，遇到空字串時改用換行指令代替，繞過 library 的問題。</p>
<hr>
<h3 id="6-try-catch-的範圍太大把程式碼-bug-和硬體故障混在一起處理">6. try-catch 的範圍太大，把程式碼 bug 和硬體故障混在一起處理</h3>
<p><strong>怎麼發現的：</strong> 回顧整個除錯過程，Bug 1、3、4 都有一個共同特徵——錯誤被 try-catch 吞掉，回傳 <code>false</code>，沒有任何明顯的異常。在測試中，缺少依賴的情況也被同樣的 try-catch 吞掉，測試照樣通過。</p>
<p><strong>怎麼找到原因的：</strong> 看列印方法的 catch 區塊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">_printKitchenReceipt</span><span class="p">(...)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1">// 組裝收據資料
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>    <span class="c1">// 組裝收據行
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="c1">// 呼叫印表機列印
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">debugPrint</span><span class="p">(</span><span class="s1">&#39;failed: </span><span class="si">$</span><span class="n">e</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>catch (e)</code> 攔截了所有錯誤，不區分類型。但這裡面混了兩種性質不同的錯誤：</p>
<ul>
<li><strong>印表機故障</strong>（連線逾時、無紙、裝置離線）→ 執行期的預期狀況，應該攔截，回傳失敗讓 UI 顯示重印按鈕</li>
<li><strong>程式碼 bug</strong>（未初始化的元件、欄位寬度不合法、空字串越界）→ 開發階段就該被發現的問題，不應該被靜默吞掉</li>
</ul>
<p>四個 Bug 裡有三個屬於後者，全部被同一個 <code>catch (e)</code> 攔住，在開發和測試階段都沒有任何異常跡象。</p>
<p><strong>怎麼修的：</strong> 做了三件事：</p>
<ol>
<li>定義 <code>PrinterException</code>，專門代表印表機硬體/連線錯誤</li>
<li>在列印中心（IO 邊界）把印表機拋出的 <code>Exception</code> 包成 <code>PrinterException</code>，但不攔截 <code>Error</code>（程式碼 bug）</li>
<li>列印方法改為 <code>on PrinterException catch</code>，只處理印表機故障</li>
</ol>
<p>改動前後的對比：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 改動前：所有錯誤都被吞掉
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">lines</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">receiptBuilder</span><span class="p">.</span><span class="n">buildReceiptLines</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">template</span><span class="p">);</span>  <span class="c1">// ← 出錯也被吞
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="kd">await</span> <span class="n">printCenter</span><span class="p">.</span><span class="n">printReceiptLines</span><span class="p">(</span><span class="nl">lines:</span> <span class="n">lines</span><span class="p">,</span> <span class="nl">printer:</span> <span class="n">printer</span><span class="p">);</span>   <span class="c1">// ← 出錯也被吞
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 改動後：資料準備不在 try 裡，只攔截印表機錯誤
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">lines</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">receiptBuilder</span><span class="p">.</span><span class="n">buildReceiptLines</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">template</span><span class="p">);</span>  <span class="c1">// ← 出錯直接拋出
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="kd">await</span> <span class="n">printCenter</span><span class="p">.</span><span class="n">printReceiptLines</span><span class="p">(</span><span class="nl">lines:</span> <span class="n">lines</span><span class="p">,</span> <span class="nl">printer:</span> <span class="n">printer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">}</span> <span class="n">on</span> <span class="n">PrinterException</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// ← 只攔截印表機故障
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>  <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>改完之後，測試裡缺少依賴的情況不再被吞掉——之前有一組測試預期列印結果是 <code>false</code>（因為缺收據產生器被 catch 吞掉），現在補上依賴後預期改為 <code>true</code>，測試驗證的是真正的列印行為。</p>
<hr>
<h2 id="從這次經驗歸納的測試方法">從這次經驗歸納的測試方法</h2>
<h3 id="一從呼叫路徑出發而非從程式碼結構出發">一、從呼叫路徑出發，而非從程式碼結構出發</h3>
<p>這次犯的最大錯誤是按照「這個 class 有哪些方法」來分配測試，結果每個方法各自通過，但串在一起就出問題。後來改成按「使用者操作觸發了什麼路徑」來規劃：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">使用者操作                    要測試的完整路徑
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">─────────                    ──────────────
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">追加點餐送出     →  handler.printAppendedOrder
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                      → _buildItemPrinterMapping  ← 分派邏輯
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">                      → buildReceiptLines          ← 收據組裝
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                      → printReceiptLines           ← 實際列印
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                      → printText / printRow        ← 印表機操作
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">點擊重印按鈕     →  retryItemKitchenPrint
</span></span><span class="line"><span class="ln">10</span><span class="cl">                      → printAppendedOrder(kitchenItemIds: {itemId})</span></span></code></pre></div><p>按路徑規劃之後，每個測試案例都會走過完整的鏈路，中間環節的問題自然會被觸發。</p>
<h3 id="二整合測試與單元測試的分工">二、整合測試與單元測試的分工</h3>
<p>這次的六個坑裡，有四個（坑 1、3、4、6）屬於「元件之間銜接」的問題，單元測試各自通過但串接失敗。回頭看分工：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">                    單元測試                     整合測試
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">─────────────────────────────────────────────────────────
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">測什麼？          單一方法的輸入輸出             多個元件串接的結果
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">假設什麼？        其他元件是正確的               驗證元件之間的銜接
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">能抓到什麼 Bug？  演算法邏輯錯誤                 初始化遺漏、依賴缺失、
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                                                介面不匹配、狀態傳遞錯誤
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">本案例中          KitchenPrinterConfig           printAppendedOrder +
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                  .handlesProduct                PrintCenter + FakePrinter +
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                  → 匹配邏輯正確                 ReceiptBuilderService
</span></span><span class="line"><span class="ln">10</span><span class="cl">                                                → 端到端路徑正確</span></span></code></pre></div><p>這次的經驗是：功能涉及多個元件協作時，只有單元測試是不夠的。整合測試才能抓到元件之間的銜接問題。</p>
<h3 id="三替-try-catch-設計專門的測試">三、替 try-catch 設計專門的測試</h3>
<p>try-catch 在這次經驗裡反覆出現——Bug 1、3、4 被它吞掉，坑 3 的斷言因為它而失效，坑 6 則是根本性的設計問題。</p>
<p>回顧後歸納的三個對策：</p>
<ul>
<li><strong>斷言成功路徑的值</strong>：不只檢查「沒拋錯」，要檢查回傳值是 <code>true</code>。坑 3 就是因為只檢查 key 存在，沒檢查值</li>
<li><strong>提供完整的依賴</strong>：讓 try 區塊能完整執行，而非依賴 catch 來「通過」測試。坑 3 的根因就是缺少收據產生器</li>
<li><strong>寫專門的失敗測試</strong>：故意製造失敗條件（如模擬印表機拋出 <code>PrinterException</code>），驗證錯誤處理行為符合預期</li>
</ul>
<h3 id="四fake--mock-的設計原則">四、Fake / Mock 的設計原則</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                    Fake（假實作）               Mock（模擬物件）
</span></span><span class="line"><span class="ln">2</span><span class="cl">─────────────────────────────────────────────────────────
</span></span><span class="line"><span class="ln">3</span><span class="cl">適用場景          需要跑通完整路徑               只需驗證互動次數/參數
</span></span><span class="line"><span class="ln">4</span><span class="cl">本案例            FakeReceiptBuilderService      不適用（需要驗證端到端結果）
</span></span><span class="line"><span class="ln">5</span><span class="cl">                  FakePrinterAdapter</span></span></code></pre></div><p>這次 <code>FakePrinterAdapter</code> 的設計漏掉了父類別繼承方法依賴的內部狀態（坑 2），<code>FakeReceiptBuilderService</code> 的回傳資料只覆蓋了部分分支（坑 4）。後來整理出設計 Fake 時的確認項目：</p>
<ul>
<li>繼承/實作的方法中，有哪些是上層呼叫者實際會用到的？</li>
<li>這些方法依賴哪些內部狀態（如 <code>late</code> 變數）？</li>
<li>Fake 的初始化是否正確建立了這些內部狀態？</li>
<li>Fake 回傳的資料是否足以讓下游所有分支都被觸發？</li>
</ul>
<hr>
<h2 id="之後可以改善的地方">之後可以改善的地方</h2>
<h3 id="寫測試時">寫測試時</h3>
<ul>
<li>測試應從入口方法開始驅動，讓中間的邏輯實際執行，避免手動構造中間結果</li>
<li>使用模擬子類別時，確認上層實際呼叫到的繼承方法也有被測試覆蓋</li>
<li>斷言驗證值本身，而非只驗證存在性（<code>containsKey</code> → 直接檢查值）</li>
<li>設計模擬元件的回傳資料時，先確認下游有哪些分支，確認回傳資料能觸發這些分支</li>
<li>回傳資料中加入邊界值——空字串、空列表等</li>
</ul>
<h3 id="修-bug-時">修 Bug 時</h3>
<ul>
<li>修完後確認有測試會實際走到修改的程式碼，否則測試通過不代表修改生效</li>
<li>沿著執行路徑往下看——之前被擋住的程式碼現在可以執行了，那些區段可能存在未發現的問題</li>
<li>如果修改讓新的資料流入第三方 library，檢查那些資料是否有 edge case</li>
</ul>
<h3 id="設計-try-catch-時">設計 try-catch 時</h3>
<ul>
<li>區分「預期的執行期錯誤」和「程式碼 bug」，只攔截前者</li>
<li>定義專用的 exception 類型（如 <code>PrinterException</code>），在 IO 邊界包裝，上層只 catch 這個類型</li>
<li>資料準備、邏輯運算等步驟不要放在 try-catch 裡面，讓錯誤直接拋出</li>
</ul>
<h3 id="自我檢查清單">自我檢查清單</h3>
<p>寫完測試後可以對照的問題：</p>
<ol>
<li>這個測試有走過真實的呼叫路徑嗎？還是只測了資料搬運？</li>
<li>斷言是驗證「值」還是只驗證「存在」？</li>
<li>所有依賴都有提供嗎？缺少的依賴會不會被 try-catch 吞掉？</li>
<li>模擬子類別覆寫的方法之外，繼承的方法有被測到嗎？</li>
<li>模擬元件回傳的資料有觸發下游的所有分支嗎？</li>
<li>邊界值（空字串、空列表、null）有出現在測試資料中嗎？</li>
<li>try-catch 的範圍是否只包含 IO 操作？資料準備和邏輯運算是否在 try 外面？</li>
<li>有寫反向測試（故意觸發錯誤）來確認理解了 Bug 的根因嗎？</li>
</ol>
<hr>
<h2 id="本案例的最終測試結構">本案例的最終測試結構</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">28 tests
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">無廚房印表機時的基本行為（4 tests）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  └── 基本的 handler 行為，不需要廚房印表機
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">OnlineOrderRecord 模型（7 tests）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  └── 單元測試：狀態管理、applyPrintResult
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">FakePrinterAdapter（6 tests）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├── init / sendBytes — 基本功能
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├── printText after init — 驗證內部元件初始化（抓 Bug 1）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  └── printText without init — 反向驗證
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">KitchenPrinterConfig（2 tests）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  └── 單元測試：品名匹配邏輯
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">品項分派邏輯 — 整合測試（4 tests）     ← 全部重寫
</span></span><span class="line"><span class="ln">18</span><span class="cl">  ├── 2 台空 mapping → odd/even 分配     抓 Bug 2
</span></span><span class="line"><span class="ln">19</span><span class="cl">  ├── 1 台空 mapping → fallback
</span></span><span class="line"><span class="ln">20</span><span class="cl">  ├── 明確 mapping 優先匹配
</span></span><span class="line"><span class="ln">21</span><span class="cl">  └── kitchenItemIds 篩選
</span></span><span class="line"><span class="ln">22</span><span class="cl">  （全部透過 printAppendedOrder 驅動，有 FakeReceiptBuilderService）
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">PrintCenter 廚房印表機管理（5 tests）
</span></span><span class="line"><span class="ln">25</span><span class="cl">  └── 註冊、移除、初始化、向後兼容</span></span></code></pre></div><hr>
<h2 id="最終的修復">最終的修復</h2>
<table>
  <thead>
      <tr>
          <th>Bug</th>
          <th>修復方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bug 1</td>
          <td>模擬印表機的初始化方法補上內部元件的建立</td>
      </tr>
      <tr>
          <td>Bug 2</td>
          <td>品項分派的 fallback 邏輯改為：唯一一台無對應表 → 全部給它，多台 → 依規則分配</td>
      </tr>
      <tr>
          <td>Bug 3</td>
          <td>多欄表格列印前，自動將欄位比例正規化為符合 library 要求的總和 12</td>
      </tr>
      <tr>
          <td>Bug 4</td>
          <td>文字列印前加入空字串檢查，遇到空字串改用換行指令繞過 library 的問題</td>
      </tr>
      <tr>
          <td>設計改善</td>
          <td>定義 <code>PrinterException</code>，列印方法改為只攔截印表機故障，程式碼 bug 不再被靜默吞掉</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>經驗分享文章的寫作準則</title><link>https://tarrragon.github.io/blog/record/%E7%B6%93%E9%A9%97%E5%88%86%E4%BA%AB%E6%96%87%E7%AB%A0%E7%9A%84%E5%AF%AB%E4%BD%9C%E6%BA%96%E5%89%87/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%B6%93%E9%A9%97%E5%88%86%E4%BA%AB%E6%96%87%E7%AB%A0%E7%9A%84%E5%AF%AB%E4%BD%9C%E6%BA%96%E5%89%87/</guid><description>&lt;p>整理自 2026-03 測試經驗記錄的多次修改過程。
每一條都是實際犯過的寫作錯誤，附上修改前後的對比。&lt;/p></description><content:encoded><![CDATA[<p>整理自 2026-03 測試經驗記錄的多次修改過程。
每一條都是實際犯過的寫作錯誤，附上修改前後的對比。</p>
<h2 id="1-語氣經驗分享不是教人">1. 語氣：經驗分享，不是教人</h2>
<p>不要用「你應該」「要記得」這種由上往下的口氣。這是在記錄自己的經歷，不是在寫教材。</p>
<table>
  <thead>
      <tr>
          <th>修改前</th>
          <th>修改後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>教訓：測試應該驅動被測程式碼的真實路徑</td>
          <td>後來改成從入口方法開始呼叫，Bug 隨即被發現</td>
      </tr>
      <tr>
          <td>教訓：覆寫子類別時，要測試上層呼叫者實際會用到的方法</td>
          <td>測試覆蓋到的方法，和實際執行路徑走到的方法不是同一個</td>
      </tr>
      <tr>
          <td>關鍵原則：如果你的功能涉及多個元件協作，只寫單元測試是不夠的</td>
          <td>（直接移除，用案例本身說明）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="2-語氣陳述事實不帶情緒">2. 語氣：陳述事實，不帶情緒</h2>
<p>避免用強調語氣、反問、或帶有自責/誇張的措辭。平鋪直敘地描述發生了什麼。</p>
<table>
  <thead>
      <tr>
          <th>修改前</th>
          <th>修改後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>我到底哪裡搞砸了</td>
          <td>經驗記錄</td>
      </tr>
      <tr>
          <td>炸了四次</td>
          <td>陸續發現四個 Bug</td>
      </tr>
      <tr>
          <td>說到底，不是測試寫得不夠多，是測試走的路不夠深</td>
          <td>這次的問題在測試覆蓋的路徑深度</td>
      </tr>
      <tr>
          <td>我寫了一個永遠會過的斷言</td>
          <td>這個斷言無法區分成功和失敗</td>
      </tr>
      <tr>
          <td>真正會爆的那個完全沒碰</td>
          <td>測試覆蓋到的方法和實際執行路徑使用的方法不同</td>
      </tr>
      <tr>
          <td>但我偷懶了</td>
          <td>但模擬的回傳資料只有一行純文字</td>
      </tr>
      <tr>
          <td>其他路根本沒碰</td>
          <td>其他分支沒有被覆蓋到</td>
      </tr>
      <tr>
          <td>測試全過是假象</td>
          <td>測試通過不代表修改生效</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="3-用功能描述不要用函式名稱">3. 用功能描述，不要用函式名稱</h2>
<p>文章的讀者不一定看過這份程式碼。如果只寫函式名稱，只有自己看得懂。函式名稱可以出現在程式碼範例裡，但前後的解說要用功能語言。</p>
<table>
  <thead>
      <tr>
          <th>修改前</th>
          <th>修改後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>_buildItemPrinterMapping</code> 沒有被執行</td>
          <td>品項分派的程式碼沒有被執行</td>
      </tr>
      <tr>
          <td><code>applyPrintResult</code> 的資料儲存功能</td>
          <td>把結果存進去再讀出來，資料有沒有一致</td>
      </tr>
      <tr>
          <td>只測了覆寫的 <code>sendBytes</code>，沒測繼承的 <code>printText</code></td>
          <td>只測了「送出資料」，沒測「組裝列印指令」這個步驟</td>
      </tr>
      <tr>
          <td><code>generator</code> 未初始化</td>
          <td>印表機內部元件未初始化</td>
      </tr>
      <tr>
          <td><code>_getLexemes('')</code> 對 <code>text[0]</code> 越界</td>
          <td>解析文字時沒有處理空字串，直接取第一個字元導致越界</td>
      </tr>
      <tr>
          <td><code>GeneralPrinterAdapter.printText</code> 加了空字串檢查</td>
          <td>在呼叫 library 之前加了空字串的前置檢查</td>
      </tr>
  </tbody>
</table>
<p>程式碼區塊裡的函式名稱不需要替換——那是具體的範例，讀者看到程式碼會理解。要替換的是程式碼區塊外的描述文字。</p>
<hr>
<h2 id="4-敘事角度不是每件事都是犯錯">4. 敘事角度：不是每件事都是「犯錯」</h2>
<p>有些問題不是自己造成的，不需要用自責的角度來寫。例如第三方 library 的 bug 本來就存在，能觸發它代表前面的工作都做對了。</p>
<table>
  <thead>
      <tr>
          <th>修改前</th>
          <th>修改後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>修完一個 Bug 我就以為沒事了</td>
          <td>第三方 library 的地雷——前面都做對了才踩到</td>
      </tr>
      <tr>
          <td>我沒想到修完之後下游還有問題</td>
          <td>前三個 Bug 都修好之後，執行路徑才真正打通，觸發了這個潛在問題</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="5-每個案例要有脈絡發現--找因--修復">5. 每個案例要有脈絡：發現 → 找因 → 修復</h2>
<p>只說「問題是 X，改成 Y」不夠。讀者需要知道完整的脈絡：是怎麼意識到有問題的、怎麼一步步定位原因的、最後怎麼修的。</p>
<h3 id="修改前">修改前</h3>
<blockquote>
<p>測試名稱是「品項分派邏輯」，但分派邏輯沒有被執行。後來改成從入口方法開始呼叫，Bug 隨即被發現。</p></blockquote>
<h3 id="修改後">修改後</h3>
<blockquote>
<p><strong>怎麼發現的：</strong> 實機上兩台廚房印表機只有一台收到品項，另一台完全沒動。但測試裡的分派測試是通過的。</p>
<p><strong>怎麼找到原因的：</strong> 回頭看測試程式碼，發現測試裡的分派結果是手動寫死的，不是由分派邏輯算出來的。測試驗證的是「把結果存進去再讀出來」，但品項分派的程式碼從頭到尾沒有被執行過。</p>
<p><strong>怎麼修的：</strong> 改成從入口方法開始呼叫，讓品項分派的邏輯實際跑一遍。跑完之後 Bug 就出現了——分派邏輯的 fallback 條件寫錯，所有品項都被送到同一台。</p></blockquote>
<hr>
<h2 id="6-不要用特定產品sdk-名稱">6. 不要用特定產品/SDK 名稱</h2>
<p>具體的產品名稱（如某個 SDK 的名字）讓文章變成只適用於特定情境。著重描述問題的結構，而非特定技術。</p>
<table>
  <thead>
      <tr>
          <th>修改前</th>
          <th>修改後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>imin SDK 不驗證 width 總和</td>
          <td>POS 主機不驗證欄位寬度總和</td>
      </tr>
      <tr>
          <td>imin vs ESC/POS 有不同的 width 規則</td>
          <td>兩種印表機對同一個參數的限制不同</td>
      </tr>
  </tbody>
</table>
<p>例外：第三方 library 的名稱可以出現在「最終修復」等技術細節區段，因為那是給自己未來查閱用的。</p>
<hr>
<h2 id="檢查清單">檢查清單</h2>
<p>寫完經驗分享文章後，對照這些問題：</p>
<ol>
<li>有沒有「你應該」「要記得」「教訓是」這類由上往下的用語？→ 改成描述自己做了什麼、發現了什麼</li>
<li>有沒有情緒化的措辭（搞砸、偷懶、炸了、根本沒）？→ 改成陳述事實</li>
<li>程式碼區塊外的函式名稱，不看程式碼的人能理解嗎？→ 用功能描述替代</li>
<li>每個案例有完整的脈絡嗎？（怎麼發現 → 怎麼找因 → 怎麼修）</li>
<li>有沒有把不是自己造成的問題寫成自己的錯？→ 用適當的角度描述</li>
<li>有沒有出現特定產品/SDK 名稱？→ 除了技術細節區段外，用功能描述替代</li>
</ol>]]></content:encoded></item><item><title>5W1H 自覺決策方法論：系統化決策框架</title><link>https://tarrragon.github.io/blog/record/5w1h-%E8%87%AA%E8%A6%BA%E6%B1%BA%E7%AD%96%E6%96%B9%E6%B3%95%E8%AB%96%E7%B3%BB%E7%B5%B1%E5%8C%96%E6%B1%BA%E7%AD%96%E6%A1%86%E6%9E%B6/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/5w1h-%E8%87%AA%E8%A6%BA%E6%B1%BA%E7%AD%96%E6%96%B9%E6%B3%95%E8%AB%96%E7%B3%BB%E7%B5%B1%E5%8C%96%E6%B1%BA%E7%AD%96%E6%A1%86%E6%9E%B6/</guid><description>&lt;p>和 AI 協作開發的早期，我們遇到一個讓人沮喪的問題：相同的功能一再被重複實作。某個模組已經有 &lt;code>BookValidator&lt;/code>，但另一個任務裡 AI 又建了一個新驗證類別，因為它沒先問「這功能是否已存在？」更糟的是，遇到棘手問題時，我們（包括 AI）會下意識找「簡單的方法」迴避難題，用臨時解法掩蓋設計缺陷。&lt;/p>
&lt;p>這才建立了 5W1H 自覺決策方法論：在建立任何任務之前，必須強制回答六個問題。不是「可以回答」，而是「必須回答」，任何一個缺失都阻止任務建立。&lt;/p></description><content:encoded><![CDATA[<p>和 AI 協作開發的早期，我們遇到一個讓人沮喪的問題：相同的功能一再被重複實作。某個模組已經有 <code>BookValidator</code>，但另一個任務裡 AI 又建了一個新驗證類別，因為它沒先問「這功能是否已存在？」更糟的是，遇到棘手問題時，我們（包括 AI）會下意識找「簡單的方法」迴避難題，用臨時解法掩蓋設計缺陷。</p>
<p>這才建立了 5W1H 自覺決策方法論：在建立任何任務之前，必須強制回答六個問題。不是「可以回答」，而是「必須回答」，任何一個缺失都阻止任務建立。</p>
<h2 id="為什麼直覺判斷不夠用">為什麼直覺判斷不夠用</h2>
<p>直覺判斷的門檻太低。任何模糊的想法都可能直接變成任務被付諸實作，等發現重複了或架構位置錯了，才知道白花了時間。</p>
<p>5W1H 框架在任務建立的那一刻設置強制思考關卡。只有通過六個問題，任務才能被建立。</p>
<h2 id="六個問題六個層次的自覺">六個問題，六個層次的自覺</h2>
<h3 id="who責任歸屬防止重複實作">Who：責任歸屬，防止重複實作</h3>
<p>Who 問的不只是「誰來做」，更重要的是「有沒有人已經做過了」。在我們的協作模式中，Who 必須明確標記執行者和分派者兩個角色，格式如：「<code>parsley-flutter-developer</code>（執行者）由 <code>rosemary-project-manager</code>（分派者）分派」。</p>
<p>這個格式強制一個邊界：主線程不應自己寫程式碼，職責是設計任務、分派任務、驗收結果。如果任務標記為主線程執行但類型是程式碼實作，系統直接阻止。</p>
<p>執行 Who 前要先做四步檢查：搜尋現有 Domain 是否有相同功能，確認既有 Service 是否已實作，查看測試覆蓋是否涵蓋，最後才決定是重用還是新建。Domain 中已有相同功能就禁止新建，責任不明就禁止執行。這兩條規則消滅了大部分重複實作。</p>
<h3 id="what功能定義單一職責的守門員">What：功能定義，單一職責的守門員</h3>
<p>What 要求明確說出具體的輸入輸出、業務行為和異常處理。合格的 What 必須能一句話描述完且無法再拆分。</p>
<p>「書籍處理——包含驗證、儲存、查詢等」不合格。「驗證輸入字串是否符合 ISBN-10 或 ISBN-13 格式，輸入 <code>String isbn</code>，輸出 <code>ValidationResult</code>，空字串拋出 <code>ValidationError</code>」才是。</p>
<p>職責不清就禁止執行，與既有功能重疊就整合，包含多個不相關職責就拆分。</p>
<h3 id="when觸發時機副作用要完整識別">When：觸發時機，副作用要完整識別</h3>
<p>When 要求明確說出觸發事件，不是「書籍資料需要驗證的時候」，而是「使用者完成 ISBN 條碼掃描，觸發 <code>ISBNScannedEvent</code>，副作用包含觸發書籍資訊查詢和更新 UI 狀態，整合到 <code>ScanTask</code> 聚合根的事件系統」。</p>
<p>副作用未識別是 When 的紅線。連鎖反應沒有提前想清楚，後來就是難以追蹤的 bug。</p>
<h3 id="where架構位置確保層級正確">Where：架構位置，確保層級正確</h3>
<p>Where 決定功能在 Clean Architecture 中的位置。不是「在需要的地方執行」，而是「Domain Layer，<code>BookValidator</code> 位於 <code>Book Aggregate</code>，由 <code>AddBookUseCase</code> 呼叫，呼叫路徑是 UI → UseCase → Domain → Validator」。</p>
<p>位置錯誤就重新定位，不能為了方便就地執行。跨層級混亂就重新架構，不妥協。</p>
<h3 id="why動機驗證識別逃避性開發">Why：動機驗證，識別逃避性開發</h3>
<p>Why 是六個問題中最重要的，因為它直接對抗最常見的開發陷阱——逃避。</p>
<p>合格的 Why 必須有需求文件編號，能說明具體的使用者價值和場景。「系統需要更多驗證」不合格，它沒有需求依據，而且可能只是為了逃避更難的問題。</p>
<p>遇到這些語言會立即阻止：「先做簡單的」（逃避複雜問題）、「順便加個功能」（缺乏需求）、「優化一下效能」（可能在迴避功能問題）。</p>
<h3 id="how實作策略任務類型的強制標記">How：實作策略，任務類型的強制標記</h3>
<p>How 要求明確實作策略，並強制標記任務類型，格式是 <code>[Task Type: XXX]</code>。六種類型：Implementation（程式碼實作，必須由執行代理人執行）、Dispatch（任務分派，主線程執行）、Review（驗收，主線程執行）、Documentation、Analysis、Planning。</p>
<p>這個標記讓 Who 和 How 的合規性可以被自動檢查：How 標記為 Implementation 但執行者是主線程，系統直接阻止；Dispatch 類型的任務執行者是代理人，同樣阻止。</p>
<p>「先建立基本功能之後再加測試」是直接違規，任何臨時解法都禁止。</p>
<h2 id="who-和-how-的聯動檢查">Who 和 How 的聯動檢查</h2>
<p>正確組合：Implementation 搭配執行代理人，Dispatch/Review 搭配主線程。違規組合：Implementation 搭配主線程（主線程不應寫程式碼），Dispatch 搭配代理人，缺少執行者/分派者標記。</p>
<p>這個機制解決了一個真實痛點：主線程在時間壓力下直接修改程式碼，繞過代理人執行的設計。有了 How 的任務類型標記和 Who 的格式要求，這種行為在任務建立階段就被攔截。</p>
<h2 id="逃避語言清單">逃避語言清單</h2>
<p>框架持續更新一份逃避語言清單，分五類：</p>
<ul>
<li>品質妥協：「太複雜」「先將就」「暫時性修正」「症狀緩解」、&ldquo;workaround&rdquo;、&ldquo;hack&rdquo;、&ldquo;quick fix&rdquo;</li>
<li>簡化妥協：「更簡單的方法」「簡化處理」——通常意味著在迴避真正的設計問題</li>
<li>擱置問題：「架構問題先不管」「技術債務之後處理」——發現了卻不解決，最危險</li>
<li>測試妥協：「簡化測試」「基本測試即可」</li>
<li>模糊詞彙：沒有具體指標的「優化」「自動」</li>
</ul>
<p>偵測到任何逃避語言，系統立即阻止並要求修正。</p>
<h2 id="hook-系統讓規則有牙齒">Hook 系統：讓規則有牙齒</h2>
<p>框架的執行依賴 Hook 系統。TodoWrite 工具使用前，Hook 檢查 5W1H 完整性；任何一個 W 或 H 缺失，操作立即阻止；發現逃避語言，進入修復模式。</p>
<p>修復機制：阻止操作 → 明確說明缺失哪個項目 → 要求補充 → 再次驗證。</p>
<h2 id="這個框架改變了什麼">這個框架改變了什麼</h2>
<p>最明顯的感受是對話品質變了。以前任務可能只有「實作書籍驗證功能」；現在必須帶著完整的 Who（誰來做、是否已存在）、What（具體輸入輸出）、When（何時觸發、有哪些副作用）、Where（架構層）、Why（需求來源）、How（任務類型和策略）才能被建立。</p>
<p>很多模糊的想法在回答六個問題的過程中就自然消失了——它們根本過不了關。</p>]]></content:encoded></item><item><title>BDD 測試方法論</title><link>https://tarrragon.github.io/blog/record/bdd-%E6%B8%AC%E8%A9%A6%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/bdd-%E6%B8%AC%E8%A9%A6%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>三個月的重構週期結束後，我們檢視了測試套件，發現一個令人沮喪的問題：每次修改內部實作，即使業務邏輯完全沒變，也需要跟著修改大量測試。一個 Repository 實作替換，導致二十幾個測試需要逐一調整。&lt;/p>
&lt;p>這不是測試該有的樣子。問題根源在於測試耦合了實作細節，而非行為。&lt;/p></description><content:encoded><![CDATA[<p>三個月的重構週期結束後，我們檢視了測試套件，發現一個令人沮喪的問題：每次修改內部實作，即使業務邏輯完全沒變，也需要跟著修改大量測試。一個 Repository 實作替換，導致二十幾個測試需要逐一調整。</p>
<p>這不是測試該有的樣子。問題根源在於測試耦合了實作細節，而非行為。</p>
<h2 id="bdd-的核心定位">BDD 的核心定位</h2>
<p>BDD 是 TDD 的演進，它要求測試描述系統的「行為」而非「實作」。</p>
<p>行為是使用者視角觀察到的系統反應；實作是程式內部的技術細節。這個區別看起來簡單，實際撰寫測試時卻很容易模糊。</p>
<p>BDD 解決三個問題：</p>
<p><strong>測試維護成本高</strong>。傳統單元測試緊密耦合實作細節，重構時即使行為沒變，測試仍需大量修改。BDD 讓重構時測試保持穩定。</p>
<p><strong>需求追溯困難</strong>。測試充滿技術細節，無法對應業務需求。Given-When-Then 場景即是需求文件，測試即規格。</p>
<p><strong>溝通成本高</strong>。開發、測試和業務人員用不同語言描述系統行為。BDD 統一使用業務語言，建立共通溝通基礎。</p>
<p>我們的分工是：Clean Architecture 定義架構分層，TDD 四階段流程定義開發節奏，BDD 定義測試內容和撰寫規範。</p>
<h2 id="given-when-then-結構">Given-When-Then 結構</h2>
<p>Given 描述系統的初始狀態，必須明確完整，只包含與此場景相關的資料。常見錯誤是前置條件模糊，或包含大量無關測試資料。</p>
<p>When 描述使用者執行的操作，必須是單一動作，使用業務語言。「呼叫 Repository 的 save 方法」是技術術語；「使用者提交訂單」是業務語言。一個 When 不能包含多個動作。</p>
<p>Then 描述執行後的狀態變化或結果，必須是可觀察的行為。「Repository 的 save 方法被呼叫一次」是實作細節；「訂單成功儲存並回傳訂單編號」是可觀察的行為。</p>
<p>判斷行為還是實作的方法很簡單：使用者能否觀察到？改變實作會影響這個結果嗎？產品經理需要關心嗎？都是「能觀察、不影響、需要關心」就是行為，反之是實作細節。</p>
<h2 id="行為測試和實作測試的差異">行為測試和實作測試的差異</h2>
<p>測試實作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;OrderRepository.save should call database.insert&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="n">repository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="n">verify</span><span class="p">(</span><span class="n">database</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="s1">&#39;orders&#39;</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">toJson</span><span class="p">()));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>這個測試關注「如何儲存」，替換資料庫或重構儲存邏輯就會失敗。</p>
<p>測試行為：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單 - 訂單成功儲存&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="c1">// Given: 使用者已選擇商品並填寫完整資訊
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">validOrder</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="c1">// When: 使用者提交訂單
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">submitOrderUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="c1">// Then: 系統確認訂單已儲存
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">orderId</span><span class="p">,</span> <span class="n">isNotEmpty</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>這個測試關注「訂單是否成功儲存」，重構儲存機制不會影響結果。</p>
<p>測試描述的視角同樣重要。從技術元件角度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;當 Repository 回傳 null 時 UseCase 拋出例外&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span></span></span></code></pre></div><p>從使用者視角：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單失敗 - 商品庫存不足&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// Given: 商品庫存為 0
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="c1">// When: 使用者嘗試提交訂單
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="c1">// Then: 系統回應「庫存不足」錯誤
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h2 id="分層測試策略">分層測試策略</h2>
<p>BDD 不適用所有架構層級，每層特性不同，測試策略也不同。</p>
<p><strong>UseCase 層</strong>是 BDD 的核心應用層，代表完整的使用者操作流程，必須使用 Given-When-Then 結構，涵蓋所有業務場景。</p>
<p><strong>Domain 層</strong>包含核心業務規則、值物件驗證和實體不變量，需要細緻的邊界條件測試，單元測試更適合。</p>
<p><strong>Behavior 層</strong>負責 ViewModel 轉換和事件處理，只有複雜轉換邏輯需要獨立測試，簡單轉換由 UseCase 層覆蓋即可。</p>
<p><strong>UI 層</strong>測試成本高，只測試關鍵互動路徑，使用整合測試。</p>
<p><strong>Interface 層</strong>只定義契約，沒有實作邏輯，不需要測試。</p>
<h2 id="mock-策略">Mock 策略</h2>
<p>核心原則：只 Mock 外層依賴，不 Mock 內層邏輯。</p>
<p>外層依賴（Repository、Service、Event Publisher）透過 Interface 進行 Mock，隔離外部系統。內層邏輯（Domain Entity、Value Object）必須使用真實物件，確保測試涵蓋真實業務邏輯。</p>
<p>正確寫法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="c1">// Mock Repository（外層依賴）
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">mockRepository</span> <span class="o">=</span> <span class="n">MockOrderRepository</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">when</span><span class="p">(</span><span class="n">mockRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">any</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">SaveResult</span><span class="p">.</span><span class="n">success</span><span class="p">(</span><span class="s1">&#39;order-123&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="c1">// 使用真實的 Domain Entity（內層邏輯）
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">100</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="n">useCase</span> <span class="o">=</span> <span class="n">SubmitOrderUseCase</span><span class="p">(</span><span class="nl">repository:</span> <span class="n">mockRepository</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">orderId</span><span class="p">,</span> <span class="s1">&#39;order-123&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>錯誤寫法是 Mock Domain Entity：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kd">final</span> <span class="n">mockOrder</span> <span class="o">=</span> <span class="n">MockOrder</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="n">when</span><span class="p">(</span><span class="n">mockOrder</span><span class="p">.</span><span class="n">validate</span><span class="p">()).</span><span class="n">thenReturn</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="c1">// 沒有測試到任何真實業務邏輯
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h2 id="與-tdd-階段整合">與 TDD 階段整合</h2>
<p><strong>階段一（功能設計）</strong>：從需求識別使用者行為場景。「使用者可以提交訂單」需要提取多個場景：成功提交、庫存不足失敗、金額無效失敗等，每個場景涵蓋正常流程、異常流程和邊界條件。</p>
<p><strong>階段二（測試設計）</strong>：將行為場景轉換為可執行的測試程式碼，先建立結構，設置 Mock，再依 Given-When-Then 填入邏輯。</p>
<p><strong>階段三（實作策略）</strong>：測試先行。先完成所有測試場景並確認失敗（Red），才開始實作 UseCase 讓測試通過（Green）。</p>
<p><strong>階段四（重構優化）</strong>：重構時，行為測試必須保持穩定。重構導致測試需要修改，代表測試耦合了實作。</p>
<p>判斷重構品質的標準很清楚：替換 Repository 實作、改變演算法，不應讓測試失敗；改變業務規則、調整可觀察的錯誤訊息，才應讓測試失敗。</p>
<h2 id="常見挑戰">常見挑戰</h2>
<h3 id="測試覆蓋率盲點">測試覆蓋率盲點</h3>
<p>BDD 強調測試「重要行為」，可能讓某些程式碼未被覆蓋。混合策略解決這個問題：UseCase 層 100% BDD 測試，Domain 層複雜邏輯 100% 單元測試，整體維持 80% 程式碼覆蓋率目標。</p>
<h3 id="學習曲線">學習曲線</h3>
<p>從「測試實作」轉向「測試行為」需要思維轉換，初期容易寫出「假行為測試」（實際上還是在測試實作）。建立範例庫和測試模板很有幫助：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">test</span><span class="p">(</span><span class="s1">&#39;[業務場景描述] - 成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="c1">// Given: [前置條件]
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">input</span> <span class="o">=</span> <span class="p">[</span><span class="err">準備測試資料</span><span class="p">];</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="p">[</span><span class="err">設置</span> <span class="n">Mock</span> <span class="err">行為</span><span class="p">];</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="c1">// When: [觸發動作]
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// Then: [預期結果]
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="n">expect</span><span class="p">([</span><span class="err">驗證業務結果</span><span class="p">]);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h3 id="邊界條件容易被忽略">邊界條件容易被忽略</h3>
<p>業務場景描述容易遺漏技術性的邊界條件（null、異常、極端值）。每個 UseCase 最少需要：一個正常流程、兩個異常流程、三個邊界條件。建立技術性測試檢查清單並在 Code Review 重點確認。</p>
<h3 id="測試設置複雜度">測試設置複雜度</h3>
<p>UseCase 層的 BDD 測試需要 Mock 多個依賴，建立 Test Helper 和 Builder Pattern 減少重複：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">class</span> <span class="nc">UseCaseTestHelper</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="kd">static</span> <span class="n">MockOrderRepository</span> <span class="n">createMockRepository</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kd">required</span> <span class="n">SaveResult</span> <span class="n">saveResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="kd">final</span> <span class="n">mock</span> <span class="o">=</span> <span class="n">MockOrderRepository</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">when</span><span class="p">(</span><span class="n">mock</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">any</span><span class="p">)).</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">saveResult</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">mock</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kd">class</span> <span class="nc">OrderBuilder</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kt">int</span> <span class="n">_amount</span> <span class="o">=</span> <span class="m">100</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kt">String</span> <span class="n">_userId</span> <span class="o">=</span> <span class="s1">&#39;user-001&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="n">OrderBuilder</span> <span class="n">withAmount</span><span class="p">(</span><span class="kt">int</span> <span class="n">amount</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">_amount</span> <span class="o">=</span> <span class="n">amount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="n">Order</span> <span class="n">build</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="n">_amount</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="n">_userId</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="行為粒度">行為粒度</h3>
<p>粒度太粗，失敗時難以定位；太細則接近單元測試，失去 BDD 優勢。採用「一個 UseCase 等於一個核心行為」的原則：UseCase 代表完整業務流程，名稱以動詞開頭（Submit, Cancel, Query），所有測試場景屬於同一個業務流程。</p>
<h3 id="業務需求變更">業務需求變更</h3>
<p>需求變更時測試場景仍需更新。集中管理業務規則常數減少影響範圍：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">class</span> <span class="nc">OrderBusinessRules</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kd">static</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">freeShippingThreshold</span> <span class="o">=</span> <span class="m">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kd">static</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">maxOrderAmount</span> <span class="o">=</span> <span class="m">100000</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="kd">static</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">minOrderAmount</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="完整範例">完整範例</h2>
<p>以「使用者提交訂單」為例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">  1</span><span class="cl"><span class="n">group</span><span class="p">(</span><span class="s1">&#39;SubmitOrderUseCase&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">  <span class="n">late</span> <span class="n">MockOrderRepository</span> <span class="n">mockRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">  <span class="n">late</span> <span class="n">MockInventoryService</span> <span class="n">mockInventoryService</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">  <span class="n">late</span> <span class="n">MockEventPublisher</span> <span class="n">mockEventPublisher</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">  <span class="n">late</span> <span class="n">SubmitOrderUseCase</span> <span class="n">useCase</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">
</span></span><span class="line"><span class="ln">  7</span><span class="cl">  <span class="n">setUp</span><span class="p">(()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">mockRepository</span> <span class="o">=</span> <span class="n">MockOrderRepository</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">mockInventoryService</span> <span class="o">=</span> <span class="n">MockInventoryService</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">mockEventPublisher</span> <span class="o">=</span> <span class="n">MockEventPublisher</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">useCase</span> <span class="o">=</span> <span class="n">SubmitOrderUseCase</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">      <span class="nl">repository:</span> <span class="n">mockRepository</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">      <span class="nl">inventoryService:</span> <span class="n">mockInventoryService</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">      <span class="nl">eventPublisher:</span> <span class="n">mockEventPublisher</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;正常流程&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">      <span class="c1">// Given: 使用者已選擇商品且填寫完整資訊
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">100</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="nl">items:</span> <span class="p">[</span><span class="n">OrderItem</span><span class="p">(</span><span class="nl">productId:</span> <span class="s1">&#39;prod-001&#39;</span><span class="p">,</span> <span class="nl">quantity:</span> <span class="m">2</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="nl">shippingAddress:</span> <span class="n">Address</span><span class="p">(</span><span class="nl">city:</span> <span class="s1">&#39;台北市&#39;</span><span class="p">,</span> <span class="nl">district:</span> <span class="s1">&#39;信義區&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">      <span class="n">when</span><span class="p">(</span><span class="n">mockInventoryService</span><span class="p">.</span><span class="n">checkStock</span><span class="p">(</span><span class="s1">&#39;prod-001&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">          <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">StockStatus</span><span class="p">.</span><span class="n">available</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">      <span class="n">when</span><span class="p">(</span><span class="n">mockRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">any</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">          <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">SaveResult</span><span class="p">.</span><span class="n">success</span><span class="p">(</span><span class="s1">&#39;order-123&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">      <span class="c1">// When: 使用者點擊「提交訂單」
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">      <span class="c1">// Then: 系統確認訂單已儲存並回傳訂單編號
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"></span>      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">orderId</span><span class="p">,</span> <span class="s1">&#39;order-123&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">      <span class="n">verify</span><span class="p">(</span><span class="n">mockEventPublisher</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="n">any</span><span class="p">.</span><span class="n">having</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">e</span><span class="p">.</span><span class="n">type</span><span class="p">,</span> <span class="s1">&#39;event type&#39;</span><span class="p">,</span> <span class="n">EventType</span><span class="p">.</span><span class="n">orderCreated</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">      <span class="p">))).</span><span class="n">called</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;異常流程&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單失敗 - 商品庫存不足&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">      <span class="c1">// Given: 選擇的商品庫存為 0
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">100</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="nl">items:</span> <span class="p">[</span><span class="n">OrderItem</span><span class="p">(</span><span class="nl">productId:</span> <span class="s1">&#39;prod-001&#39;</span><span class="p">,</span> <span class="nl">quantity:</span> <span class="m">2</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">      <span class="n">when</span><span class="p">(</span><span class="n">mockInventoryService</span><span class="p">.</span><span class="n">checkStock</span><span class="p">(</span><span class="s1">&#39;prod-001&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">          <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">StockStatus</span><span class="p">.</span><span class="n">outOfStock</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">      <span class="c1">// When: 使用者點擊「提交訂單」
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">      <span class="c1">// Then: 系統回應庫存不足錯誤，不儲存訂單
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="c1"></span>      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">,</span> <span class="n">ErrorType</span><span class="p">.</span><span class="n">outOfStock</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">      <span class="n">verifyNever</span><span class="p">(</span><span class="n">mockRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">any</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單失敗 - Repository 儲存失敗&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">      <span class="c1">// Given: Repository 無法儲存（網路錯誤）
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">100</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="nl">items:</span> <span class="p">[</span><span class="n">OrderItem</span><span class="p">(</span><span class="nl">productId:</span> <span class="s1">&#39;prod-001&#39;</span><span class="p">,</span> <span class="nl">quantity:</span> <span class="m">1</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">      <span class="n">when</span><span class="p">(</span><span class="n">mockInventoryService</span><span class="p">.</span><span class="n">checkStock</span><span class="p">(</span><span class="n">any</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">          <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">StockStatus</span><span class="p">.</span><span class="n">available</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">      <span class="n">when</span><span class="p">(</span><span class="n">mockRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">any</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">          <span class="p">.</span><span class="n">thenAnswer</span><span class="p">((</span><span class="n">_</span><span class="p">)</span> <span class="kd">async</span> <span class="o">=&gt;</span> <span class="n">SaveResult</span><span class="p">.</span><span class="n">failure</span><span class="p">(</span><span class="s1">&#39;網路連線失敗&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">      <span class="c1">// When: 使用者點擊「提交訂單」
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">      <span class="c1">// Then: 系統回應訂單提交失敗
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="c1"></span>      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">,</span> <span class="n">ErrorType</span><span class="p">.</span><span class="n">saveFailed</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;邊界條件&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單失敗 - 訂單金額為 0&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">      <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">0</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="nl">items:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">      <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">,</span> <span class="n">ErrorType</span><span class="p">.</span><span class="n">invalidAmount</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;建立負數金額訂單拋出例外&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">      <span class="n">expect</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="p">()</span> <span class="o">=&gt;</span> <span class="n">Order</span><span class="p">(</span><span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="o">-</span><span class="m">100</span><span class="p">),</span> <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="n">throwsA</span><span class="p">(</span><span class="n">isA</span><span class="o">&lt;</span><span class="n">InvalidAmountException</span><span class="o">&gt;</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;使用者提交訂單失敗 - 訂單金額超過上限&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">      <span class="kd">final</span> <span class="n">order</span> <span class="o">=</span> <span class="n">Order</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="nl">amount:</span> <span class="n">OrderAmount</span><span class="p">(</span><span class="m">1000001</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="nl">userId:</span> <span class="n">UserId</span><span class="p">(</span><span class="s1">&#39;user-001&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="nl">items:</span> <span class="p">[</span><span class="n">OrderItem</span><span class="p">(</span><span class="nl">productId:</span> <span class="s1">&#39;prod-001&#39;</span><span class="p">,</span> <span class="nl">quantity:</span> <span class="m">10000</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">      <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">useCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isSuccess</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">,</span> <span class="n">ErrorType</span><span class="p">.</span><span class="n">amountExceedsLimit</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="結論">結論</h2>
<p>回頭看最初那個重構週期，二十幾個因為替換 Repository 實作而失敗的測試，問題很清楚：測試在監視實作細節，而不是守護業務行為。</p>
<p>切換到 BDD 之後，同樣的重構只需確認業務行為沒有改變，測試套件就能保持穩定。</p>
<p>但 BDD 不是萬靈丹。它需要思維轉換，需要建立明確規範，需要持續 Code Review 維持品質。混合策略（UseCase 層 BDD、Domain 層單元測試、UI 層整合測試）才能真正發揮效果。</p>]]></content:encoded></item><item><title>Clean Architecture 實作指引</title><link>https://tarrragon.github.io/blog/record/clean-architecture-%E5%AF%A6%E4%BD%9C%E6%8C%87%E5%BC%95/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/clean-architecture-%E5%AF%A6%E4%BD%9C%E6%8C%87%E5%BC%95/</guid><description>&lt;p>定義敏捷開發方法論時，我們需要一個明確的判斷基準：哪些任務屬於同一層？哪些必須依序完成？哪些可以並行？&lt;/p>
&lt;p>我們選擇了 Clean Architecture。它不只是架構模式，更是一套「責任分層」語言，讓我們和 AI 代理人都能用同一套詞彙討論誰該負責哪個部分、什麼時候可以動手。&lt;/p></description><content:encoded><![CDATA[<p>定義敏捷開發方法論時，我們需要一個明確的判斷基準：哪些任務屬於同一層？哪些必須依序完成？哪些可以並行？</p>
<p>我們選擇了 Clean Architecture。它不只是架構模式，更是一套「責任分層」語言，讓我們和 AI 代理人都能用同一套詞彙討論誰該負責哪個部分、什麼時候可以動手。</p>
<h2 id="核心原則依賴只能由外向內">核心原則：依賴只能由外向內</h2>
<p>Clean Architecture 最核心的一句話是「依賴只能由外向內」。內層不知道外層的存在，外層依賴內層定義的介面。業務邏輯因此得以獨立，換資料庫、換 UI 框架都不需要動到核心規則。</p>
<p>架構由內到外分為四層：</p>
<p><strong>Entities（核心業務規則）</strong> 封裝核心業務概念，例如書籍、訂單、使用者。定義實體屬性和業務不變量的驗證，不依賴任何框架或資料庫。</p>
<p><strong>Use Cases（應用業務邏輯）</strong> 協調 Entities 之間的互動，定義系統功能。同時定義 Input Port、Output Port、Repository Port，只依賴 Entities 和這些自己定義的介面。</p>
<p><strong>Interface Adapters（介面轉接層）</strong> 橋接業務邏輯和外部技術。Controller 接收外部請求並轉換為 Use Case 輸入，Presenter 格式化 Use Case 輸出，Repository 實作在這一層開始成形。</p>
<p><strong>Frameworks &amp; Drivers（框架與外部系統）</strong> 包含所有技術細節——資料庫、UI 框架、第三方服務。可以隨時替換，完全不影響內層。</p>
<h2 id="依賴反轉原則dip讓這一切成立">依賴反轉原則（DIP）讓這一切成立</h2>
<p>Use Case 需要存取資料，但不能直接依賴 SQLite。正確做法是：在 Use Cases 層定義 Repository Port（抽象介面），Use Case 只依賴這個介面。真正的資料庫實作在最外層，它去實作這個介面。</p>
<p>依賴方向因此被反轉：資料庫實作依賴 Use Case 定義的介面，而非 Use Case 依賴資料庫。最後在 Composition Root 把具體實作注入進去，這就是依賴注入的本質。</p>
<h2 id="設計從內到外實作從外到內">設計從內到外，實作從外到內</h2>
<p>這是我們在 AI 協作中最重要的一個認知。</p>
<h3 id="設計階段由內到外">設計階段由內到外</h3>
<ol>
<li>設計 Entities：識別業務實體、定義 Value Objects（ISBN、Title 這類有驗證規則的值物件），在建構子中驗證業務不變量。</li>
<li>設計 Use Cases：定義 Input Port、Output Port、Repository Port。確定業務邏輯需要什麼，但不決定如何實作。</li>
<li>設計 Interface Adapters：Controller 如何轉換外部請求、Presenter 如何格式化輸出。</li>
<li>設計 Frameworks：選擇資料庫方案，實作 Repository。到這步才碰具體技術。</li>
</ol>
<h4 id="實作階段由外到內">實作階段由外到內</h4>
<ol>
<li>先定義所有 Ports。在寫任何實作之前，確立 Use Case 介面和 Repository 介面，這是系統的骨架。</li>
<li>外層用 Mock 介面開發和測試。Controller 在 Use Case 還沒真正實作前就能測試，因為它依賴的是抽象介面。</li>
<li>補完內層實作：Interactor 實作業務邏輯，Repository 存取真正的資料庫。</li>
<li>Composition Root 組裝依賴注入，系統就能真正執行。</li>
</ol>
<p>設計階段確保業務邏輯不被技術細節污染，實作階段讓每一層都能獨立開發和測試。</p>
<h2 id="驗證架構是否正確">驗證架構是否正確</h2>
<p>每個 Phase 完成後，我們對照以下幾個方向確認。</p>
<h3 id="依賴方向">依賴方向</h3>
<ul>
<li>Entities 沒有任何外層的 import</li>
<li>Use Cases 只 import Entities 和自己定義的介面</li>
<li>Frameworks 層實作的是 Interface Adapters 定義的介面</li>
</ul>
<h4 id="介面契約">介面契約</h4>
<ul>
<li>Repository Port 定義在 Use Cases 層（不是 Frameworks 層）</li>
<li>Repository 回傳的是 Entity 而不是資料庫 DTO</li>
<li>Input/Output Port 沒有洩漏框架的型別（例如 HTTP Request、SQLite Row）</li>
</ul>
<h5 id="業務邏輯位置">業務邏輯位置</h5>
<ul>
<li>業務不變量的驗證在 Entity 建構子</li>
<li>應用層邏輯在 Use Case Interactor</li>
<li>Controller 只負責轉換和呼叫，不包含業務判斷</li>
</ul>
<h6 id="interface-driven-development">Interface-Driven Development</h6>
<ul>
<li>所有 Ports 在設計階段就定義完成</li>
<li>外層真的用 Mock 介面在開發測試</li>
<li>組裝注入在 Composition Root 統一處理</li>
</ul>
<h2 id="為什麼這對-ai-協作特別有價值">為什麼這對 AI 協作特別有價值</h2>
<p>傳統開發中，架構邊界的維護依賴工程師的經驗，容易在壓力下妥協。AI 代理人則非常適合執行有明確規則的框架——規則夠清楚，它就能持續一致地遵守。</p>
<p>分層邊界就是這樣的規則。告訴代理人「這個 class 屬於 Use Cases 層，所以不能 import 任何 Framework 層的東西」，代理人就能機械性地驗證和維護這個邊界。</p>
<p>這套語言也讓任務拆分有了清楚的依據。「這個功能需要修改 Entity 和 Use Case，但不涉及 Repository 實作」是一個清楚的任務描述，對應到具體的修改範圍，不會模糊。</p>
<p>從實際經驗來看，引入 Clean Architecture 之後，AI 的實作結果更容易預測，測試覆蓋率更容易維持，架構審查也更有效率。代價是設計階段需要更多前置思考，但這個投資通常值得。</p>]]></content:encoded></item><item><title>Code Smell 品質閘門檢測方法論</title><link>https://tarrragon.github.io/blog/record/code-smell-%E5%93%81%E8%B3%AA%E9%96%98%E9%96%80%E6%AA%A2%E6%B8%AC%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/code-smell-%E5%93%81%E8%B3%AA%E9%96%98%E9%96%80%E6%AA%A2%E6%B8%AC%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>Ticket 設計完成後才發現職責不清、範圍過大——這時程式碼都已經寫了，修正成本才是真正的問題所在。&lt;/p>
&lt;p>我們在專案中建立了一套設計階段品質閘門：&lt;strong>在 Ticket 進入 Phase 2 之前強制執行 C1/C2/C3 三項檢測。&lt;/strong> 問題在文字階段就抓出來，修正只需幾分鐘；等到程式碼都寫好了，才是幾個小時起跳的代價。&lt;/p></description><content:encoded><![CDATA[<p>Ticket 設計完成後才發現職責不清、範圍過大——這時程式碼都已經寫了，修正成本才是真正的問題所在。</p>
<p>我們在專案中建立了一套設計階段品質閘門：<strong>在 Ticket 進入 Phase 2 之前強制執行 C1/C2/C3 三項檢測。</strong> 問題在文字階段就抓出來，修正只需幾分鐘；等到程式碼都寫好了，才是幾個小時起跳的代價。</p>
<h2 id="三層檢測標準">三層檢測標準</h2>
<p><strong>C1 God Ticket</strong>：超過 10 個檔案、跨越超過 2 個架構層、或預估超過 16 小時，判定過大，必須拆分。</p>
<p><strong>C2 Incomplete Ticket</strong>：驗收條件少於 3 個可量化項目、缺少測試規劃、未定義工作日誌名稱、無參考文件連結——任一缺失，Ticket 不完整。</p>
<p><strong>C3 Ambiguous Responsibility</strong>：標題缺少層級標示（如 <code>[Layer 5]</code>）、目標含複合職責（出現「和」或「或」）、步驟用「相關檔案」而非具體路徑、驗收條件跨層——任一模糊，需重新定義。</p>
<h2 id="檢測順序的設計邏輯">檢測順序的設計邏輯</h2>
<p>固定執行順序：<strong>C1 → C3 → C2</strong>。</p>
<p>C1 最先，因為一旦判定 God Ticket，拆分後產生多個新 Ticket，C2/C3 的分析全部作廢要重來。C3 排第二，職責不清的 Ticket，補充驗收條件也會是模糊的。C2 最後，在範圍合理、職責明確的基礎上，確認必要元素齊全。</p>
<h2 id="執行流程">執行流程</h2>
<h3 id="step-1c1-god-ticket-檢測">Step 1：C1 God Ticket 檢測</h3>
<p>從 Ticket 的步驟章節提取所有檔案路徑，計算檔案數量。接著使用檔案路徑分析法判斷每個檔案所屬的架構層：</p>
<ul>
<li><code>lib/presentation/widgets/</code>、<code>lib/presentation/pages/</code> 屬於 Layer 1（UI 層）</li>
<li><code>lib/presentation/controllers/</code>、<code>lib/presentation/view_models/</code> 屬於 Layer 2（行為層）</li>
<li><code>lib/application/use_cases/</code>、<code>lib/application/services/</code> 屬於 Layer 3（UseCase 層）</li>
<li><code>lib/domain/repositories/</code>（介面）、<code>lib/domain/events/</code> 屬於 Layer 4（Domain 介面層）</li>
<li><code>lib/domain/entities/</code>、<code>lib/domain/value_objects/</code>、<code>lib/infrastructure/</code> 屬於 Layer 5（Domain 實作層）</li>
</ul>
<p>層級跨度 = 最高層 - 最低層。如果有檔案分佈在 Layer 1 到 Layer 5，層級跨度就是 4，遠超過 2 的上限。</p>
<p>預估工時根據步驟數量與複雜度係數評估：步驟數 × 平均每步時間 × 複雜度係數（1.0 到 2.0）。</p>
<p>檢測失敗時，依序嘗試三種拆分策略：</p>
<ol>
<li>按層級拆分（優先）：將跨層的 Ticket 按架構層拆成多個</li>
<li>按職責拆分（次要）：將工時過長的 Ticket 按職責邊界拆分</li>
<li>按功能模組拆分（最終）：將職責過多的 Ticket 按功能模組拆分</li>
</ol>
<p>拆分後的每個子 Ticket 都需要重新執行 C1 檢測。</p>
<h3 id="step-2c3-ambiguous-responsibility-檢測">Step 2：C3 Ambiguous Responsibility 檢測</h3>
<p>確認四個要素：</p>
<ul>
<li><strong>層級標示</strong>：標題包含 <code>[Layer X]</code> 標籤</li>
<li><strong>職責描述</strong>：目標章節無「和」或「或」的複合描述</li>
<li><strong>檔案範圍</strong>：步驟列出具體路徑，而非「相關檔案」</li>
<li><strong>驗收限定</strong>：驗收條件不出現跨層項目</li>
</ul>
<p>任一不符，依序修正：明確層級 → 重寫職責 → 列出具體檔案 → 限定驗收範圍。</p>
<h3 id="step-3c2-incomplete-ticket-檢測">Step 3：C2 Incomplete Ticket 檢測</h3>
<p>確認四個必要元素都存在：</p>
<ul>
<li><strong>驗收條件</strong>：3 個以上可量化、可驗證的項目（「程式碼品質提升」這種描述不算）</li>
<li><strong>測試規劃</strong>：明確的測試檔案路徑和對應的測試項目</li>
<li><strong>工作日誌</strong>：定義好工作日誌的檔案名稱（而非「待填寫」）</li>
<li><strong>參考文件</strong>：至少 1 個有效的文件連結</li>
</ul>
<p>缺少哪個就補哪個，沒有例外。</p>
<h3 id="step-4提交審查">Step 4：提交審查</h3>
<p>所有 Ticket 通過 C1/C2/C3 之後，生成品質閘門檢測報告，提交 PM 審查。PM 批准後才能進入 Phase 2。</p>
<h2 id="阻斷機制與執行時機">阻斷機制與執行時機</h2>
<p>任何 Ticket 未通過 C1/C2/C3，<strong>禁止進入 Phase 2</strong>。不是建議，是硬性規定。問題立即修正，所有過程寫入工作日誌。</p>
<p>執行時機有三個：設計完成後立即檢測、PM 分派前確認報告、PM 審查時以報告作依據。三個時機缺一不可——缺了任何一個，閘門就會有漏網之魚。</p>
<h2 id="實際效益">實際效益</h2>
<p>最直接的改變是：開發者接到 Ticket 時就已經有明確範圍、清楚職責、具體驗收標準，不需要反覆確認。</p>
<p>更重要的是，因為修正成本真的很低，團隊才會認真對待每一次品質閘門，而不是當作走形式。一旦修正成本高，就會開始討價還價、局部修正、累積技術債——品質閘門也就形同虛設。</p>]]></content:encoded></item><item><title>Ticket 生命週期管理方法論</title><link>https://tarrragon.github.io/blog/record/ticket-%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F%E7%AE%A1%E7%90%86%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/ticket-%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F%E7%AE%A1%E7%90%86%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>某個深夜追查 Bug，打開 Ticket 系統看到的是：「修復書籍搜尋問題」、「要加那個功能」、「上次說的那個」。沒有驗收條件，沒有步驟，只有模糊標題。Reviewer 不知道該查什麼，開發者不確定自己算不算完成，PM 無法判斷能不能關掉。&lt;/p>
&lt;p>這讓我們認真思考：Ticket 不只是一張便利貼，它是任務的完整生命週期。&lt;/p></description><content:encoded><![CDATA[<p>某個深夜追查 Bug，打開 Ticket 系統看到的是：「修復書籍搜尋問題」、「要加那個功能」、「上次說的那個」。沒有驗收條件，沒有步驟，只有模糊標題。Reviewer 不知道該查什麼，開發者不確定自己算不算完成，PM 無法判斷能不能關掉。</p>
<p>這讓我們認真思考：Ticket 不只是一張便利貼，它是任務的完整生命週期。</p>
<h2 id="四個狀態一條主線">四個狀態，一條主線</h2>
<p><strong>待執行（Pending）</strong>：Ticket 準備好但還沒人開始。進入條件是標題格式正確、五個核心欄位填寫完整、前置依賴都已完成。</p>
<p><strong>進行中（In Progress）</strong>：核心紀律是即時記錄，遇到問題就寫進日誌，不要等到最後才回憶。我們設的建議上限是 8 小時——超過通常代表 Ticket 太大，需要拆分。</p>
<p><strong>Review 中（In Review）</strong>：第三方驗證。重點聚焦在驗收條件：逐項打勾，要麼通過要麼不通過。建議 1-2 小時內完成，避免卡住流程。</p>
<p><strong>已完成（Completed）</strong>：不可逆的終態。進入條件：所有驗收條件打勾、Review 通過、測試 100% 通過、靜態分析 0 錯誤、工作日誌更新。缺一不可。</p>
<h2 id="ticket-標題動詞加目標">Ticket 標題：動詞加目標</h2>
<p>標準格式是「動詞 + 目標」。動詞選擇反映任務類型：「定義」用於介面或規範，「撰寫」用於測試文件，「實作」用於具體功能，「修復」用於 Bug，「重構」用於改善結構。</p>
<p>對比：「做 Repository」和「實作 SQLiteBookRepository」——後者讓人在領取前就知道工作規模。</p>
<h2 id="五個核心欄位">五個核心欄位</h2>
<p>每張 Ticket 必須有這五個欄位：</p>
<p><strong>背景</strong>說明為什麼需要這個任務，讓三個月後的自己也能理解來龍去脈，不用去問當初的人。</p>
<p><strong>目標</strong>一句話說清楚要達成什麼，建議不超過 30 字。可驗證的目標：「實作 SQLiteBookRepository，符合 IBookRepository 介面，通過所有單元測試」。不可驗證的：「優化資料儲存」。</p>
<p><strong>步驟</strong>列 3-5 個具體動作，每個指明操作對象和預期結果。目的是讓人一領取就知道從哪裡開始。</p>
<p><strong>驗收條件</strong>是任務的契約，必須客觀可勾選。不是「程式碼品質良好」，而是「dart analyze 0 錯誤」；不是「測試通過」，而是「覆蓋率 100%，通過率 100%」。</p>
<p><strong>參考文件</strong>連結設計文件、需求規格、相關 Ticket，讓執行者有完整上下文。</p>
<h2 id="執行的七個步驟">執行的七個步驟</h2>
<p><strong>領取</strong>前先確認依賴都完成，工作量合理再標記進行中。</p>
<p><strong>閱讀</strong>背景和目標，把參考文件看完再動手。</p>
<p><strong>執行</strong>時遇到問題即時記進日誌。</p>
<p><strong>自檢</strong>時逐項勾選驗收條件，全過才進下一步。</p>
<p><strong>提交</strong>時標記為 Review，附上自我檢查結果。</p>
<p><strong>處理 Review 結果</strong>，通過則關閉，未通過根據反饋修正再提交。</p>
<p><strong>記錄經驗</strong>——這一步最容易省略，也最有長期價值。Ticket 完成時回答：學到了什麼？有什麼可改進的地方？是否需要建立 error-pattern 防止重複？知識沉澱進系統，才不會只留在某個人腦子裡。</p>
<h2 id="阻塞狀態的積極處理">阻塞狀態的積極處理</h2>
<p>Blocked 最容易被誤用：標記了就等著。我們的原則是阻塞不超過 24 小時，必須採取行動。</p>
<p>行動方式取決於原因：缺前置條件就派發前置任務；技術問題不清楚就建調查任務；需要決策就建評估任務。把「被動等待」轉成「積極解除」，每個阻塞都變成一個有人負責的子任務。</p>
<h2 id="結語">結語</h2>
<p>從「修復那個問題」到「修復書籍搜尋在 ISBN 格式不一致時回傳空結果的問題」，這個距離比看起來近。只需要一點紀律，和對「完成」這件事更認真的態度。</p>]]></content:encoded></item><item><title>Ticket 設計派工方法論</title><link>https://tarrragon.github.io/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>開發 book_overview_app 某個版本時，工作日誌膨脹到 6000 行，查一個設計決策要上下滾半天，不同人同時修改同一份文件也頻繁衝突。這不是個別案例，大型功能開發幾乎必然會遇到。&lt;/p>
&lt;p>我們的解法是建立一套 Ticket 設計派工方法論。這篇文章記錄實踐方式和核心概念，以及後來加入的積極派發原則。&lt;/p></description><content:encoded><![CDATA[<p>開發 book_overview_app 某個版本時，工作日誌膨脹到 6000 行，查一個設計決策要上下滾半天，不同人同時修改同一份文件也頻繁衝突。這不是個別案例，大型功能開發幾乎必然會遇到。</p>
<p>我們的解法是建立一套 Ticket 設計派工方法論。這篇文章記錄實踐方式和核心概念，以及後來加入的積極派發原則。</p>
<h2 id="為什麼需要-ticket">為什麼需要 Ticket？</h2>
<p>工作日誌已經記錄了一切，為什麼還需要 Ticket？</p>
<p>兩者定位不同。工作日誌是「記錄層」，給未來的開發者看，記錄決策脈絡。Ticket 是「執行層」，給正在做事的人看，代表一個可以獨立完成、驗收、追蹤的最小任務單位。</p>
<p>一個 Ticket 有幾個核心特徵：獨立性（可以獨立執行和驗收）、原子性（不可再分割）、可驗證性（有明確的完成標準），以及單一職責（一個動詞加上一個目標）。</p>
<h2 id="單一職責是拆分-ticket-的唯一標準">單一職責是拆分 Ticket 的唯一標準</h2>
<p>方法論早期我們用量化指標判斷是否需要拆分：職責數量、檔案數量、測試案例數、行數。直觀，但實際用起來有問題：一個 100 行的 Ticket 可能職責非常清楚，完全不需要拆；一個 30 行的如果混了兩個不相關的目標，就應該拆。數字不能反映語義。</p>
<p>後來改為四個語義檢查：</p>
<p><strong>語義檢查</strong>：能用「動詞加單一目標」描述嗎？描述時出現「並且」或「以及」，通常需要拆。</p>
<p><strong>修改原因檢查</strong>：只有一個原因會觸發修改嗎？兩個不同的業務規則各自可能觸發修改，就是兩個 Ticket。</p>
<p><strong>驗收一致性</strong>：所有驗收條件都指向同一個目標嗎？</p>
<p><strong>依賴獨立性</strong>：拆分後不會產生循環依賴嗎？</p>
<p>這四個問題比任何量化指標都更接近本質。</p>
<p>驗收條件的標準也要說清楚：「功能正常」不夠，「所有單元測試通過且 dart analyze 輸出 0 個問題」才算。</p>
<h2 id="ticket-與-clean-architecture-的對應">Ticket 與 Clean Architecture 的對應</h2>
<p>我們的專案用 Clean Architecture，Ticket 設計也遵循同樣的分層邏輯。</p>
<p>Interface 定義優先於具體實作。每個功能模組先建 Interface 定義的 Ticket，再建具體實作的 Ticket。這樣外層可以依賴 Interface 先行開發，不被內層實作進度卡住。</p>
<p>分層拆分讓每個 Ticket 待在自己的架構層，Domain、Application、Infrastructure、Presentation 層的 Ticket 不互相混合，也更容易並行執行。</p>
<h2 id="即時-review在問題發生當下就解決">即時 Review：在問題發生當下就解決</h2>
<p>傳統 code review 在整個版本完成後進行，此時修正成本很高，問題可能已經影響了許多後續實作。</p>
<p>我們改為每個 Ticket 完成時立即觸發 Review。因為範圍只有單一 Ticket 的相關程式碼，Review 者不需要理解整個版本的脈絡，反饋在數小時內完成，修正成本極低。</p>
<p>Review 涵蓋四個面向：功能正確性（功能實現、驗收條件、邊界情況、錯誤處理）、架構合規性（Clean Architecture、依賴方向、Interface-Driven、架構債務）、測試通過率（單元測試、整合測試、覆蓋率、跳過測試）、文件同步性（Ticket 日誌、設計決策、API 文件、README），共 16 項。</p>
<h2 id="三層文件結構讓-6000-行問題消失">三層文件結構：讓 6000 行問題消失</h2>
<p>解決臃腫工作日誌的方式是拆層：</p>
<ul>
<li><strong>主版本日誌</strong>：500–1000 行，說明版本目標，提供所有 Ticket 的索引</li>
<li><strong>Ticket 工作日誌</strong>：每個 Ticket 一份，100–200 行，記錄執行過程、Review 結果和完成標記</li>
<li><strong>設計決策日誌</strong>：300–500 行，記錄設計決策演進，包含廢棄決策的原因</li>
</ul>
<p>原本 6000 行的文件拆分後變成一份 800 行主日誌加上 12 個 Ticket 日誌加上一份 400 行設計決策日誌，總量降到約 2000 行，資訊查找時間減少大約 80%。</p>
<h2 id="積極派發發現問題就建-ticket">積極派發：發現問題就建 Ticket</h2>
<p>這是後來加入的原則，也是這套方法論最核心的轉變。</p>
<p>執行一個 Ticket 時，經常會發現超出原本範圍的情況。以前的做法是「繼續在這個 Ticket 裡處理」或「先記著等有空再說」。前者讓 Ticket 失去單一職責，後者讓問題消失在記憶裡。</p>
<p>積極派發的做法：只要發現超出範圍的新情況，立刻評估性質，建立對應的新 Ticket。阻塞了當前 Ticket 就建子 Ticket 標記依賴；獨立於當前 Ticket 就建新 Ticket 讓兩者並行；是未來的改進就建技術債務 Ticket 記錄起來。</p>
<p>好處是：每個決策都有獨立記錄，保持可追溯性；任務粒度更細，更容易估算；學習點不會淹沒在大 Ticket 裡；一次只專注一件事。</p>
<p>要注意的反模式是「為派發而派發」。積極派發不是讓每件事都變成獨立 Ticket，判斷標準始終是單一職責：情況是否真的超出原 Ticket 範圍？</p>
<hr>
<p>這套方法論從單一大文件演進到精簡主文加引用網絡，量化指標被單一職責取代，是因為語義比數字更能反映 Ticket 是否設計得好。積極派發的加入，是因為 Ticket 可以成為持續改善的工具，不只是任務追蹤。</p>
<p>如果你的工作日誌越來越長、協作衝突越來越多，從三個問題開始：Ticket 能用「動詞加單一目標」描述嗎？驗收條件是客觀可檢查的嗎？跟其他 Ticket 的依賴關係清楚嗎？</p>]]></content:encoded></item><item><title>方法論文件撰寫方法論</title><link>https://tarrragon.github.io/blog/record/%E6%96%B9%E6%B3%95%E8%AB%96%E6%96%87%E4%BB%B6%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E6%96%B9%E6%B3%95%E8%AB%96%E6%96%87%E4%BB%B6%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="一個根本性的誤解">一個根本性的誤解&lt;/h2>
&lt;p>我們花了很長時間，把方法論文件寫得越來越完整——完整的定義、詳細的範例、細緻的流程說明，直到有一天發現，這樣的文件根本沒有人在用。&lt;/p>
&lt;p>問題出在誤解了方法論的讀者是誰。&lt;/p></description><content:encoded><![CDATA[<h2 id="一個根本性的誤解">一個根本性的誤解</h2>
<p>我們花了很長時間，把方法論文件寫得越來越完整——完整的定義、詳細的範例、細緻的流程說明，直到有一天發現，這樣的文件根本沒有人在用。</p>
<p>問題出在誤解了方法論的讀者是誰。</p>
<p>方法論的讀者是專家。他們已經知道這些內容，只是需要時忘記了細節。他們不需要被教導，只需要一個快速的提醒，幫他們找回記憶。</p>
<h2 id="30-秒電梯理論">30 秒電梯理論</h2>
<p>我們現在用一個簡單標準評估方法論：能在 30 秒內讀完嗎？如果不能，就太長了。</p>
<p>讀者打開方法論通常是在任務中途，需要確認某個原則或步驟。超出 30 秒的內容搬移到獨立的 SKILL 文件，由方法論引用。</p>
<h2 id="方法論與-skill-的分工">方法論與 SKILL 的分工</h2>
<p>方法論是複習清單：告訴你「有哪些原則」「判斷方向是什麼」，讓你快速確認沒有遺漏重要考量。</p>
<p>SKILL 是實作指南：包含範例、錯誤處理、邊界情況，回答「具體怎麼做」。</p>
<p>過去把兩者混在一起，結果兩頭不討好——方法論太長難以快速查閱，又因試圖簡潔而省略了真正需要的操作細節。</p>
<h2 id="撰寫方法論的核查">撰寫方法論的核查</h2>
<p>每次撰寫或改寫方法論，問三個問題：</p>
<p>有完整的操作流程嗎？建立對應 SKILL，方法論只保留原則和引用。</p>
<p>有程式碼範例或錯誤處理細節嗎？屬於 SKILL，不屬於方法論。</p>
<p>精簡後會流失關鍵資訊嗎？如果是，那些資訊本來就不屬於方法論。</p>
<h2 id="消除歧義仍然是核心要求">消除歧義仍然是核心要求</h2>
<p>精簡不代表模糊。「應該」改為「必須」，「建議」改為「要求」，「適當時機」改為具體時間點。每個判斷只有兩種結果：接受或拒絕、必須或禁止。</p>
<p>簡短但模糊的方法論比詳細但模糊的更糟糕——它讓讀者覺得已經複習完了，卻什麼都沒想清楚。</p>]]></content:encoded></item><item><title>即時 Review 機制：Ticket 完成就 Review，不累積</title><link>https://tarrragon.github.io/blog/record/%E5%8D%B3%E6%99%82-review-%E6%A9%9F%E5%88%B6ticket-%E5%AE%8C%E6%88%90%E5%B0%B1-review%E4%B8%8D%E7%B4%AF%E7%A9%8D/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E5%8D%B3%E6%99%82-review-%E6%A9%9F%E5%88%B6ticket-%E5%AE%8C%E6%88%90%E5%B0%B1-review%E4%B8%8D%E7%B4%AF%E7%A9%8D/</guid><description>&lt;p>以前我們也是等一批 Ticket 做完，才坐下來一起 review。&lt;/p>
&lt;p>結果就是：Review 的時候發現第一個 Ticket 的架構方向有問題，接下來五個都是建在那個錯誤上面。這時候修正成本已經是即時發現的六到十倍，開發者剛花了三天卻要整個打掉，士氣很難不崩。&lt;/p>
&lt;p>後來我們設計了「即時 Review 機制」，核心就一句話：&lt;strong>Ticket 完成就 Review，不累積。&lt;/strong>&lt;/p></description><content:encoded><![CDATA[<p>以前我們也是等一批 Ticket 做完，才坐下來一起 review。</p>
<p>結果就是：Review 的時候發現第一個 Ticket 的架構方向有問題，接下來五個都是建在那個錯誤上面。這時候修正成本已經是即時發現的六到十倍，開發者剛花了三天卻要整個打掉，士氣很難不崩。</p>
<p>後來我們設計了「即時 Review 機制」，核心就一句話：<strong>Ticket 完成就 Review，不累積。</strong></p>
<h2 id="三個原則">三個原則</h2>
<p><strong>即時觸發</strong>。Ticket 完成的當下就觸發，不是等 Wave 結束，不是等今天收工。錯誤的架構決策會在下一個 Ticket 開始前就污染後續設計，等不起。</p>
<p><strong>30 分鐘完成</strong>。這個時間限制是設計訊號：如果一個 Ticket 的 Review 超過一小時，代表那個 Ticket 太大，應該在規劃時就拆。</p>
<p><strong>標準清單</strong>。16 項固定檢查項，分功能正確性、架構合規性、測試通過率、文件同步性四類。不管誰 Review、什麼時間點，標準都一樣。</p>
<h2 id="怎麼跑">怎麼跑</h2>
<p><strong>觸發</strong>：完成實作、驗收條件全滿足、測試 100% 通過、靜態分析零錯誤，狀態標為「Review 中」。</p>
<p><strong>執行</strong>：30 分鐘內跑完 16 項。功能正確性（10 分鐘）、架構合規性（8 分鐘）、測試通過率（5 分鐘）、文件同步性（2 分鐘），最後 5 分鐘記錄結果。</p>
<p><strong>偏差糾正</strong>：發現問題就照固定流程走——暫停、記錄根因、建修正 Ticket、執行、再 Review。流程結構化是關鍵，「發現問題不知怎麼辦」是問題被忽略的主因。</p>
<h2 id="問題分三級">問題分三級</h2>
<p><strong>P0（阻塞）</strong>：功能錯誤、架構偏差、測試失敗。必須在 Review 通過前修正。</p>
<p><strong>P1（重要）</strong>：邊界處理缺失、文件不完整。建 Ticket 追蹤，當前 Ticket 可先通過。</p>
<p><strong>P2（建議）</strong>：命名改善、程式風格。記錄即可。</p>
<p>分級的目的：Review 保持快速，重要問題不被跳過。</p>
<h2 id="超時是訊號">超時是訊號</h2>
<p>Ticket 完成後 2 小時內開始 Review，目標 30 分鐘，最長 1 小時。超時代表 Ticket 範圍太大，不是 Reviewer 的問題。這個標準幫助我們持續校正 Ticket 的粒度。</p>
<h2 id="最明顯的變化">最明顯的變化</h2>
<p>導入前，「Review」這件事是模糊的——知道應該做，但時機不固定、標準不一、出了問題也不清楚怎麼處理。</p>
<p>導入後，偏差被控制在單一 Ticket 的範圍內，沒機會擴散。另一個意外收穫是，16 項清單讓我們對「品質」有了共同語言，Review 結果可以比較、可以追蹤，不再靠個人感覺。</p>
<hr>
<p>即時 Review 的目的是把修正成本壓到最低。問題在最小範圍被抓到，代價才會真的小。</p>]]></content:encoded></item><item><title>程式碼自然語言化撰寫方法論</title><link>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%87%AA%E7%84%B6%E8%AA%9E%E8%A8%80%E5%8C%96%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%87%AA%E7%84%B6%E8%AA%9E%E8%A8%80%E5%8C%96%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>程式碼不是寫給電腦看的，是寫給人類讀的。電腦只管執行，人類才要維護。&lt;/p></description><content:encoded><![CDATA[<p>程式碼不是寫給電腦看的，是寫給人類讀的。電腦只管執行，人類才要維護。</p>
<h2 id="認知負擔一切的出發點">認知負擔：一切的出發點</h2>
<p>人類工作記憶有限，大約一次只能處理七個項目（Miller&rsquo;s Law）。看到縮寫要在腦中展開、看到模糊詞要猜測含義、看到長函式要分段記憶——這些都是認知負擔。</p>
<p>自然語言化的目標很簡單：讓程式碼像讀文章一樣自然，把讀者的認知資源留給理解業務邏輯，而不是解碼程式碼本身。</p>
<hr>
<h2 id="第一原則命名要能直接讀懂">第一原則：命名要能直接讀懂</h2>
<h3 id="函式命名">函式命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：不知道在做什麼
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kt">void</span> <span class="n">handle</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 正確：一眼看懂
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">calculateBookReadingProgress</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="kt">void</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">User</span> <span class="n">user</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kt">void</span> <span class="n">enrichBookMetadataFromExternalSource</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{}</span></span></span></code></pre></div><h3 id="變數命名">變數命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：縮寫和多用途
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="n">usr</span> <span class="o">=</span> <span class="n">getCurrentUser</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">var</span> <span class="n">data</span> <span class="o">=</span> <span class="n">loadUserData</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">processBookData</span><span class="p">();</span> <span class="c1">// 100行後同一變數換了意思
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// 正確：明確且專用
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">authenticatedUser</span> <span class="o">=</span> <span class="n">getCurrentUser</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kd">final</span> <span class="n">userProfileData</span> <span class="o">=</span> <span class="n">loadUserData</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="kd">final</span> <span class="n">enrichedBookMetadata</span> <span class="o">=</span> <span class="n">processBookData</span><span class="p">();</span></span></span></code></pre></div><h3 id="類別命名">類別命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：說不清在做什麼
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Manager</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">class</span> <span class="nc">Handler</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDAO</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// 正確：業務職責一目了然
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookMetadataEnrichmentService</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kd">class</span> <span class="nc">UserRegistrationValidator</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="kd">class</span> <span class="nc">LibraryBookSearchEngine</span> <span class="p">{}</span></span></span></code></pre></div><h3 id="布林命名">布林命名</h3>
<p>布林變數應該能讀成問句，在 if 裡就能自然被理解：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">bool</span> <span class="n">isValid</span><span class="p">;</span>      <span class="c1">// &#34;Is valid?&#34;
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">hasPermission</span><span class="p">;</span> <span class="c1">// &#34;Has permission?&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">canEdit</span><span class="p">;</span>       <span class="c1">// &#34;Can edit?&#34;
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 不好：語意不清
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">permission</span><span class="p">;</span></span></span></code></pre></div><h3 id="常見命名反模式">常見命名反模式</h3>
<ul>
<li><strong>匈牙利命名法</strong>：<code>strName</code>, <code>intCount</code> — 型別系統自己會提供型別資訊，名稱不用重複</li>
<li><strong>無意義前綴</strong>：<code>theUser</code>, <code>aBook</code> — 沒帶來任何資訊，直接刪掉</li>
<li><strong>過度縮寫</strong>：<code>usrMgr</code> — 迫使讀者展開，<code>userManager</code> 更自然</li>
<li><strong>數字後綴</strong>：<code>user1</code>, <code>user2</code> — 改成 <code>primaryUser</code>, <code>secondaryUser</code> 才說明關係</li>
</ul>
<hr>
<h2 id="第二原則函式控制在五到十行">第二原則：函式控制在五到十行</h2>
<p>超過十行通常表示函式承擔了多重職責。判斷是否需要拆分的最快方法：函式名稱裡有「和」或「或」的話，一定要拆。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：15行，三種職責混在一起
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">Book</span> <span class="n">processBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">isbn</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;Invalid ISBN&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidISBNChecksum</span><span class="p">(</span><span class="n">isbn</span><span class="p">))</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN checksum failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="s1">&#39;/books/</span><span class="si">$</span><span class="n">isbn</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">statusCode</span> <span class="o">!=</span> <span class="m">200</span><span class="p">)</span> <span class="k">throw</span> <span class="n">Exception</span><span class="p">(</span><span class="s1">&#39;API failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">final</span> <span class="n">bookData</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">body</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nl">id:</span> <span class="n">BookId</span><span class="p">(</span><span class="n">generateUniqueId</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nl">title:</span> <span class="n">BookTitle</span><span class="p">(</span><span class="n">bookData</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">source</span><span class="o">:</span> <span class="n">BookSource</span><span class="p">.</span><span class="n">external</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// 正確：拆成三個單一職責函式
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="n">Book</span> <span class="n">createBookFromISBN</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">validateISBNFormat</span><span class="p">(</span><span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="kd">final</span> <span class="n">bookData</span> <span class="o">=</span> <span class="n">fetchBookDataFromExternalAPI</span><span class="p">(</span><span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">return</span> <span class="n">buildBookFromExternalData</span><span class="p">(</span><span class="n">bookData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kt">void</span> <span class="n">validateISBNFormat</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">isbn</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN must be 13 digits&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidISBNChecksum</span><span class="p">(</span><span class="n">isbn</span><span class="p">))</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN checksum validation failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">dynamic</span><span class="o">&gt;</span> <span class="n">fetchBookDataFromExternalAPI</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="s1">&#39;/books/</span><span class="si">$</span><span class="n">isbn</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">statusCode</span> <span class="o">!=</span> <span class="m">200</span><span class="p">)</span> <span class="k">throw</span> <span class="n">Exception</span><span class="p">(</span><span class="s1">&#39;Failed to fetch book data&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">body</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="n">Book</span> <span class="n">buildBookFromExternalData</span><span class="p">(</span><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">dynamic</span><span class="o">&gt;</span> <span class="n">bookData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nl">id:</span> <span class="n">BookId</span><span class="p">(</span><span class="n">generateUniqueId</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nl">title:</span> <span class="n">BookTitle</span><span class="p">(</span><span class="n">bookData</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">source</span><span class="o">:</span> <span class="n">BookSource</span><span class="p">.</span><span class="n">external</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第三原則一個變數只做一件事">第三原則：一個變數只做一件事</h2>
<p>同一個變數在不同地方承載不同意義，是我見過最難追蹤的 bug 來源之一。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：同一變數三種身分
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="n">result</span> <span class="o">=</span> <span class="n">validateUser</span><span class="p">(</span><span class="n">userData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">result</span> <span class="o">=</span> <span class="n">processPayment</span><span class="p">(</span><span class="n">paymentData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">updateDatabase</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 正確：每個變數都有自己的名字
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">userValidationResult</span> <span class="o">=</span> <span class="n">validateUser</span><span class="p">(</span><span class="n">userData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">userValidationResult</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="n">paymentProcessingResult</span> <span class="o">=</span> <span class="n">processPayment</span><span class="p">(</span><span class="n">paymentData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">paymentProcessingResult</span><span class="p">.</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="kd">final</span> <span class="n">databaseUpdateResult</span> <span class="o">=</span> <span class="n">updateDatabase</span><span class="p">(</span><span class="n">paymentProcessingResult</span><span class="p">.</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>變數生命週期也要管：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：books 在100行間一直換狀態
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">processLibraryBooks</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">var</span> <span class="n">books</span> <span class="o">=</span> <span class="n">getAllBooks</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">books</span> <span class="o">=</span> <span class="n">filterAvailableBooks</span><span class="p">(</span><span class="n">books</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">books</span> <span class="o">=</span> <span class="n">sortBooksByTitle</span><span class="p">(</span><span class="n">books</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 正確：每個階段的狀態都有名字
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">processLibraryBooks</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kd">final</span> <span class="n">allLibraryBooks</span> <span class="o">=</span> <span class="n">getAllBooks</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="kd">final</span> <span class="n">availableBooks</span> <span class="o">=</span> <span class="n">filterAvailableBooks</span><span class="p">(</span><span class="n">allLibraryBooks</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kd">final</span> <span class="n">sortedAvailableBooks</span> <span class="o">=</span> <span class="n">sortBooksByTitle</span><span class="p">(</span><span class="n">availableBooks</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第四原則用事件驅動表達業務流程">第四原則：用事件驅動表達業務流程</h2>
<p>複雜的業務流程往往會寫成一個大函式，裡面塞滿 if/else。問題不在於 if/else 本身，而是把不同職責的邏輯混在一起。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：驗證、API呼叫、結果處理全混在一起
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">submitForm</span><span class="p">(</span><span class="n">FormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;姓名不能為空&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;Email不能為空&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidEmail</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">))</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;Email格式不正確&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResult</span> <span class="o">=</span> <span class="n">submitToAPI</span><span class="p">(</span><span class="n">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">apiResult</span><span class="p">.</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">showSuccessMessage</span><span class="p">(</span><span class="s1">&#39;提交成功&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">navigateToSuccessPage</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">clearForm</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;提交失敗：&#39;</span> <span class="o">+</span> <span class="n">apiResult</span><span class="p">.</span><span class="n">error</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">highlightErrorFields</span><span class="p">(</span><span class="n">apiResult</span><span class="p">.</span><span class="n">errorFields</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// 正確：每個事件有自己的函式
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">submitUserRegistrationForm</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kd">final</span> <span class="n">validationResult</span> <span class="o">=</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">validationResult</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">handleSuccessfulValidation</span><span class="p">(</span><span class="n">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">handleValidationFailure</span><span class="p">(</span><span class="n">validationResult</span><span class="p">.</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">ValidationResult</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kd">final</span> <span class="n">errors</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">ValidationError</span><span class="o">&gt;</span><span class="p">[];</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidUserName</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">name</span><span class="p">))</span> <span class="n">errors</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">ValidationError</span><span class="p">.</span><span class="n">invalidUserName</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidUserEmail</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">))</span> <span class="n">errors</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">ValidationError</span><span class="p">.</span><span class="n">invalidEmail</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">.</span><span class="n">fromErrors</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="kt">void</span> <span class="n">handleSuccessfulValidation</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="n">submitUserRegistrationToAPI</span><span class="p">(</span><span class="n">formData</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">.</span><span class="n">then</span><span class="p">(</span><span class="n">handleSuccessfulAPIResponse</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">.</span><span class="n">catchError</span><span class="p">(</span><span class="n">handleAPIFailure</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="kt">void</span> <span class="n">handleValidationFailure</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">ValidationError</span><span class="o">&gt;</span> <span class="n">errors</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="n">displayValidationErrors</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">  <span class="n">highlightInvalidFormFields</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>狀態機也是同樣的道理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：一個函式根據狀態做完全不同的事
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">updateBookStatus</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">,</span> <span class="kt">String</span> <span class="n">newStatus</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;available&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">available</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">borrower</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">updateSearchIndex</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">notifyWaitingUsers</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;borrowed&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">borrowed</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">borrowDate</span> <span class="o">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">now</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">sendBorrowConfirmation</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;maintenance&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">maintenance</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">removeFromSearchIndex</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">notifyMaintenanceTeam</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">// 正確：每個事件獨立
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">handleBookReturnEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="n">executeBookReturn</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="n">notifyBookBecameAvailable</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="kt">void</span> <span class="n">handleBookBorrowEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">,</span> <span class="n">User</span> <span class="n">borrower</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="n">executeBookBorrow</span><span class="p">(</span><span class="n">book</span><span class="p">,</span> <span class="n">borrower</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="n">confirmBorrowingToUser</span><span class="p">(</span><span class="n">book</span><span class="p">,</span> <span class="n">borrower</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="kt">void</span> <span class="n">handleBookMaintenanceEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="n">markBookForMaintenance</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="n">notifyMaintenanceRequired</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第五原則可讀性優於簡潔性">第五原則：可讀性優於簡潔性</h2>
<p>程式碼的價值排序：正確性 &gt; 可讀性 &gt; 可維護性 &gt; 簡潔性。</p>
<p>行數從來不是指標，清晰才是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：為了少寫幾行犧牲可讀性
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">books</span><span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">b</span><span class="p">.</span><span class="n">s</span> <span class="o">==</span> <span class="s1">&#39;a&#39;</span> <span class="o">&amp;&amp;</span> <span class="n">b</span><span class="p">.</span><span class="n">p</span> <span class="o">&gt;</span> <span class="m">100</span><span class="p">).</span><span class="n">map</span><span class="p">((</span><span class="n">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">b</span><span class="p">.</span><span class="n">t</span><span class="p">).</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 正確：每一步都說清楚在做什麼
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">availableBooksWithMoreThan100Pages</span> <span class="o">=</span> <span class="n">allBooks</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">available</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">pageCount</span> <span class="o">&gt;</span> <span class="m">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">.</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kd">final</span> <span class="n">bookTitlesForDisplay</span> <span class="o">=</span> <span class="n">availableBooksWithMoreThan100Pages</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">.</span><span class="n">map</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">.</span><span class="n">toList</span><span class="p">();</span></span></span></code></pre></div><hr>
<h2 id="如何驗證程式碼品質">如何驗證程式碼品質</h2>
<p><strong>陌生人測試</strong>：讓不熟悉這段程式碼的工程師讀。5分鐘內能理解主要邏輯算合格，需要解釋才能理解就要重寫。</p>
<p><strong>自然語言測試</strong>：把程式碼翻譯成中文說出來。翻譯流暢自然算合格，說不清楚就改命名。</p>
<p><strong>六個月後測試</strong>：假設半年後的自己要修改這段程式碼，能快速找到位置算合格，不敢動怕壞掉就要重新設計。</p>
<hr>
<p>每一行程式碼都是一句話，每個函式都是一個段落。好的程式碼是對未來維護者的體貼——不只是風格偏好，而是降低維護成本的工程決策。</p>]]></content:encoded></item><item><title>程式碼註解撰寫方法論</title><link>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%A8%BB%E8%A7%A3%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%A8%BB%E8%A7%A3%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>接手一段六個月前的程式碼，看到 &lt;code>processBook()&lt;/code>，旁邊的註解寫著「處理書籍相關邏輯」。完全沒有幫助——只是重述函式名稱，沒說背後有什麼業務限制、改了會影響哪裡、當初為什麼這樣設計。&lt;/p>
&lt;p>這讓我們重新思考：註解到底是為了誰而寫的？&lt;/p></description><content:encoded><![CDATA[<p>接手一段六個月前的程式碼，看到 <code>processBook()</code>，旁邊的註解寫著「處理書籍相關邏輯」。完全沒有幫助——只是重述函式名稱，沒說背後有什麼業務限制、改了會影響哪裡、當初為什麼這樣設計。</p>
<p>這讓我們重新思考：註解到底是為了誰而寫的？</p>
<h2 id="註解的本質">註解的本質</h2>
<p>程式碼註解不是程式的解釋員。它的存在是為了保護原始設計意圖，提供無法從程式碼本身推斷出來的訊息。</p>
<p><strong>不應該做的</strong>：解釋程式在做什麼（好的程式碼應該自己說話）、描述函式使用方法（那是文件的工作）、充當 TODO 清單。</p>
<p><strong>應該做的</strong>：作為需求保護器，防止維護時破壞原始需求；記錄設計意圖，保存業務邏輯的考量；提供維護指引，明確標示約束條件；建立程式碼與需求規格的連結。</p>
<h2 id="第一原則程式碼本身必須自說明">第一原則：程式碼本身必須自說明</h2>
<p>如果程式碼需要靠註解才能被理解，首先應該改善的是程式碼，不是補更多解釋。</p>
<p><code>process(Book book)</code> 需要一行「檢查書籍狀態並更新進度」的註解，但如果直接命名為 <code>updateReadingProgressWhenStatusChanges(Book book)</code>，解釋性的註解就不需要了。此時真正值得寫下來的，是這個函式背後的業務需求和約束條件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">void</span> <span class="n">updateReadingProgressWhenStatusChanges</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// 需求：UC-005 閱讀進度管理
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="c1">// 當使用者標記書籍為「閱讀中」時，自動設定進度為 0%
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="c1">// 當使用者標記為「已完成」時，自動設定進度為 100%
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="c1">// 約束：不可覆蓋使用者手動設定的進度值
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>變數也一樣：<code>final data = book.getInfo()</code> 毫無意義，<code>final enrichedBookMetadata = book.getMetadataWithEnrichment()</code> 就完全自說明了。</p>
<p>驗證標準：移除所有註解後，如果仍能理解程式邏輯，程式碼達標。需要猜測變數含義就重新命名，無法確定函式目的就拆分函式。</p>
<h2 id="第二原則註解記錄的是需求脈絡">第二原則：註解記錄的是需求脈絡</h2>
<p>程式碼自說明，不代表不需要註解——需要的是不同種類的註解。</p>
<p>每個業務邏輯函式都應該追溯到明確的需求來源：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：UC-003.2 書籍分類管理
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 使用者可以為書籍設定多個標籤進行分類
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 約束：標籤名稱不可重複，最多 10 個標籤
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">addTagToBook</span><span class="p">(</span><span class="n">BookId</span> <span class="n">bookId</span><span class="p">,</span> <span class="n">Tag</span> <span class="n">tag</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>這條註解告訴維護者：這個函式的存在依據是 UC-003.2，不可以被打破的規則是標籤不可重複且最多十個。</p>
<p>書籍狀態的轉換順序是業務規則，不是技術細節，也應該記錄：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：BR-001 書籍狀態轉換規則
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 書籍狀態變更順序：初始 → 資訊補充中 → 資訊補充完成 → 可用
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 約束：不可跳過中間狀態，不可逆向轉換
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 例外：管理員可以直接設定為任何狀態
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="n">BookStatus</span> <span class="n">transitionBookStatus</span><span class="p">(</span><span class="n">BookStatus</span> <span class="n">current</span><span class="p">,</span> <span class="n">BookStatus</span> <span class="n">target</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>設計決策也要記錄。為什麼選懶載入加分頁？因為有效能需求：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：NFR-002 效能需求
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 書庫載入時間不可超過 2 秒
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 設計決策：採用懶載入 + 分頁載入策略
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 影響：首次載入只載入 20 本書，滾動時動態載入
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">loadLibraryWithPagination</span><span class="p">(</span><span class="kt">int</span> <span class="n">page</span><span class="p">,</span> <span class="kt">int</span> <span class="n">pageSize</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>一個完整的業務邏輯註解必須包含：需求來源（UC 或 BR 編號）、業務描述、約束條件、以及修改此邏輯會影響哪些功能。</p>
<h2 id="第三原則維護指引必須明確">第三原則：維護指引必須明確</h2>
<p>好的維護指引讓維護者修改程式碼之前就知道會影響哪裡。不應該隨意修改的邏輯，要主動發出警告：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：UC-001.3 書籍唯一性檢查
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 同一書庫內不可有相同 ISBN 的書籍
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 警告：此邏輯關聯到資料一致性，修改前必須檢查：
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// - 書籍匯入流程 (ImportBookService)
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">/// - 書籍合併功能 (BookMergeService)
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">/// - 資料庫索引設計 (book_isbn_unique_index)
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">isDuplicateBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">,</span> <span class="n">LibraryId</span> <span class="n">libraryId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>預期會被擴展的邏輯，要提供擴展指引：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：UC-004 書籍搜尋功能
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// 支援書名、作者、標籤的模糊搜尋
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 擴展指引：新增搜尋條件時必須：
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// 1. 更新 SearchCriteria 值物件
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">/// 2. 修改索引策略以維持效能
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">/// 3. 更新搜尋測試案例涵蓋新條件
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">searchBooks</span><span class="p">(</span><span class="n">SearchCriteria</span> <span class="n">criteria</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>模組間的耦合關係也需要標示：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 需求：UC-006 借閱管理
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// 計算書籍歸還到期日
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 相依性警告：此邏輯與以下模組緊密耦合
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// - LoanReminderService（提醒計算）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - OverdueBookDetector（逾期偵測）
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - LibraryStatistics（統計計算）
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// 修改歸還期限計算會影響上述所有模組
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="n">DateTime</span> <span class="n">calculateDueDate</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">loanDate</span><span class="p">,</span> <span class="kt">int</span> <span class="n">loanPeriodDays</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h2 id="第四原則結構一致的標準格式">第四原則：結構一致的標準格式</h2>
<p>把上面的原則整合成一個標準格式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 需求：[需求編號] [簡短描述]
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">/// [詳細業務描述，說明使用者需求]
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">/// 約束：[限制條件和邊界規則]
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// [維護指引：修改須知、相依性警告、擴展要求]
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">[</span><span class="err">函式簽名</span><span class="p">]</span></span></span></code></pre></div><p>複雜業務邏輯的範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 需求：UC-007.1 閱讀統計分析
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// 計算使用者的閱讀速度和預估剩餘時間
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 約束：只統計狀態為「閱讀中」的書籍，頁數必須大於 0
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// 計算邏輯：(已讀頁數 / 實際閱讀時間) = 閱讀速度（頁/小時）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// 維護指引：修改計算公式會影響：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - 閱讀目標設定功能
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// - 個人化推薦系統
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">/// - 學習分析報表
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">/// 相依模組：ReadingProgressTracker, BookMetadata, UserPreferences
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="n">ReadingSpeed</span> <span class="n">calculateReadingSpeed</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="n">ReadingProgress</span> <span class="n">progress</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">BookMetadata</span> <span class="n">metadata</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="n">Duration</span> <span class="n">actualReadingTime</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h2 id="禁止的註解模式">禁止的註解模式</h2>
<p>最常見的錯誤是重述程式碼行為。「設定書籍標題」對應的程式碼是 <code>book.setTitle(newTitle)</code>，完全多餘。「使用 Map 快速查找避免 O(n) 複雜度」也一樣，有經驗的開發者看程式碼就知道。</p>
<p>UI 層特別容易出現這類問題。以 Widget 選取回饋設計為例，錯誤做法是列出技術細節：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 反例：重複描述程式碼內容
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// BookListItem - 書庫列表項目 Widget
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// 視覺設計：
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - 陰影刻痕變化（凸起→凹陷）
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - AnimatedContainer 200ms 過渡動畫
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">/// 觸覺回饋：
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">/// - 選擇時：HapticFeedback.selectionClick()
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">/// - 取消選擇：HapticFeedback.lightImpact()
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListItem</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>這些看程式碼就能看到。真正有價值的是背後的決策依據：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 【需求來源】UC-05: 雙模式書庫展示切換 - 書籍選擇互動
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// 【規格文件】docs/ui_design_specification.md#book-selection-feedback
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 【設計決策】採用方案C-1基礎版 - 極簡視覺回饋設計
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// 【為什麼選擇陰影刻痕變化】
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - 不影響文字可讀性：避免背景色干擾閱讀體驗
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - 符合無障礙設計：不依賴顏色作為唯一視覺提示
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// 【為什麼選擇差異化觸覺回饋】
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">/// - 選中 vs 取消必須有不同的觸覺回饋類型
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">/// - selectionClick 提供明確的「確認」感受
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">/// - lightImpact 提供輕微的「狀態變更」提示
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">/// 【修改約束】
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">/// - 觸覺回饋時機不可調換（與使用者預期一致）
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">/// - 陰影變化動畫時長需保持 &lt; 250ms（符合 Material Design 規範）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">/// 【維護警告】
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">/// - 此 Widget 被 3 個書庫頁面使用
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">/// - 修改視覺回饋會影響整體使用者體驗一致性
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListItem</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>模糊描述也不可取——「處理書籍相關邏輯」等於沒有描述。過時的 TODO 必須清除，否則會讓人以為某個功能還沒實作。</p>
<h2 id="第五原則事件驅動架構的特殊需求">第五原則：事件驅動架構的特殊需求</h2>
<p>在 UseCase 或 Domain 層，函式名稱包含 <code>handle*</code>、<code>on*</code>、<code>process*</code>、<code>emit*</code>、<code>dispatch*</code>，或回傳類型為 <code>Future&lt;&gt;</code>、<code>Stream&lt;&gt;</code> 的函式，都需要標示其在事件流中的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 【需求來源】UC-01: Chrome Extension 匯入書籍資料
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// 【規格文件】docs/app-requirements-spec.md#chrome-extension-import
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 【設計方案】方案C-1基礎版 (v0.12.7 Phase 1)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// 【工作日誌】docs/work-logs/v0.12.7.md - 方案研究和設計決策
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// 【事件類型】BookAdded 事件處理
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// 【修改約束】修改時需確保事件流完整性，避免影響上游訂閱者
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// 【維護警告】此函式被 3 個 UseCase 依賴，修改前需檢查影響範圍
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">handleBookAdded</span><span class="p">(</span><span class="n">BookAddedEvent</span> <span class="n">event</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>以 <code>_</code> 開頭的私有輔助函式（<code>_isValid*</code>、<code>_format*</code>、<code>_convert*</code>、<code>_validate*</code> 等）不包含業務邏輯，豁免詳細業務註解。</p>
<h2 id="第六原則widget-獨立性的明確標示">第六原則：Widget 獨立性的明確標示</h2>
<p>非私有命名（不以 <code>_</code> 開頭）、繼承自 <code>StatefulWidget</code>、<code>ConsumerWidget</code>、<code>StreamBuilder</code> 或 <code>FutureBuilder</code> 的 Widget，具備獨立狀態，需要明確記錄需求來源和修改約束：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 【需求來源】UC-05: 雙模式書庫展示切換
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">/// 【規格文件】docs/ui_design_specification.md#book-list-item
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 【設計方案】方案C-1基礎版 - 陰影刻痕變化 + 觸覺回饋
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// 【工作日誌】docs/work-logs/v0.12.7.md - UI 互動設計
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// 【Widget 類型】獨立狀態管理 Widget
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// 【修改約束】此 Widget 具備獨立狀態，下層刷新不觸發上層重建
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// 【維護警告】修改前需確認子 Widget 依賴關係
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListItem</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 實作...
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>私有的 <code>StatelessWidget</code>（如 <code>_BookTitleText</code>、<code>_ProgressBar</code>）和純展示型組件，只展示上層傳遞的資料，豁免詳細業務邏輯註解。</p>
<h2 id="第七原則工作日誌與規格文件的追溯鏈">第七原則：工作日誌與規格文件的追溯鏈</h2>
<p>設計決策涉及複雜研究或多方案比較時，幾行註解無法承載所有背景，需要建立追溯鏈：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="o">///</span> <span class="err">【工作日誌】</span><span class="n">docs</span><span class="o">/</span><span class="n">work</span><span class="o">-</span><span class="n">logs</span><span class="o">/</span><span class="n">v0</span><span class="p">.</span><span class="m">12.7</span><span class="p">.</span><span class="n">md</span> <span class="o">-</span> <span class="err">方案</span><span class="n">C</span><span class="o">-</span><span class="m">1</span><span class="err">基礎版設計</span></span></span></code></pre></div><p>維護者能循著這條鏈找到完整的決策記錄。業務邏輯也可以指向規格文件章節：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// 【規格文件】docs/app-requirements-spec.md#section-name
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="o">///</span> <span class="err">【規格文件】</span><span class="n">docs</span><span class="o">/</span><span class="n">event</span><span class="o">-</span><span class="n">driven</span><span class="o">-</span><span class="n">architecture</span><span class="o">-</span><span class="n">design</span><span class="p">.</span><span class="n">md</span><span class="err">#</span><span class="n">event</span><span class="o">-</span><span class="n">flow</span></span></span></code></pre></div><p>這讓程式碼成為整個需求、設計、實作文件體系的一部分，而不是孤立的存在。</p>
<h2 id="品質驗證兩個測試">品質驗證：兩個測試</h2>
<p><strong>可執行性測試</strong>：維護者看到這條註解後，能理解業務需求嗎？修改約束明確嗎？需求來源可以追溯嗎？</p>
<p><strong>必要性測試</strong>：移除這條註解後，是否會遺失業務脈絡？如果移除後仍能理解程式邏輯，就要檢查它是否只是在重述程式碼。如果內容過時，直接刪除，不要保留會誤導維護者的假資訊。</p>
<h2 id="結論">結論</h2>
<p>好的程式碼是自說明的，但好的業務系統還需要一個跨越時間的溝通機制，讓六個月後接手的人能理解每個設計決策背後的原因，不會在不了解背景的情況下破壞原始設計。</p>
<p>這就是我們對程式碼註解的重新定位——需求的守護者。</p>
<p>這是需求保護機制，不是文書工作。</p>]]></content:encoded></item><item><title>層級隔離：讓每張 Ticket 只做一件層級的事</title><link>https://tarrragon.github.io/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/</guid><description>&lt;p>架構圖貼出來，層級畫得漂漂亮亮，但 PR 送進來還是一次動了 UI、Controller、UseCase 和 Entity 四層。&lt;/p></description><content:encoded><![CDATA[<p>架構圖貼出來，層級畫得漂漂亮亮，但 PR 送進來還是一次動了 UI、Controller、UseCase 和 Entity 四層。</p>
<h2 id="問題不在架構在派工">問題不在架構，在派工</h2>
<p>Clean Architecture 告訴你「怎麼組織程式碼」，但沒告訴你「怎麼拆 Ticket」。每次 Code Review 都像翻地層，從 Widget 翻到 Entity，不知道從哪開始看；Domain 沒穩定，UI 那層就沒辦法測，整個流程互相等待。</p>
<p>這個銜接點，需要一套專門處理 Ticket 拆法的方法論。</p>
<h2 id="核心原則一張-ticket一個層級">核心原則：一張 Ticket，一個層級</h2>
<blockquote>
<p>一個 Ticket 只應該修改單一架構層級的程式碼，變更的原因單一且明確。</p></blockquote>
<p>SRP 說一個類別只有一個改變的原因，我們把它升一層：一張 Ticket 也只有一個改變的原因。</p>
<p>聽起來嚴苛，但實際跑起來好處很直接：Code Review 只需要理解一層的邏輯、測試不需要拉起整個系統、PR 影響範圍可預測，壞掉的時候更容易定位。</p>
<h2 id="我們怎麼定義五層">我們怎麼定義「五層」</h2>
<p>傳統 Clean Architecture 四層中，Interface Adapters 同時處理「事件邏輯」和「資料轉換」，職責太雜，我們把它細分成五層：</p>
<p><strong>Layer 1 — UI/Presentation</strong>：純視覺呈現，Widget 長什麼樣。變更原因只有一個：設計稿改了。</p>
<p><strong>Layer 2 — Application/Behavior</strong>：事件處理和 UI 邏輯。按鈕點擊怎麼處理、Loading 狀態怎麼切換、Domain Entity 怎麼轉成 ViewModel。Flutter 對應 Controller 和 ViewModel。</p>
<p><strong>Layer 3 — UseCase</strong>：業務流程編排。協調多個 Repository 和 Domain Service，把業務步驟串起來。不管 UI 怎麼顯示，也不管資料庫怎麼存。</p>
<p><strong>Layer 4 — Domain Events/Interfaces</strong>：定義契約。Repository 抽象介面、Domain Event 結構、跨層 DTO。只定義，不實作。</p>
<p><strong>Layer 5 — Domain Implementation</strong>：核心業務邏輯。Entity、Value Object、Domain Service、業務規則驗證。整個系統最穩定的部分。</p>
<p>Infrastructure 層（資料庫、外部 API、EventBus）不納入層級隔離，它的變更驅動是技術決策，不是業務需求，Ticket 設計上本來就獨立對待。</p>
<h2 id="從外而內而不是從內而外">從外而內，而不是從內而外</h2>
<p>許多教材說「先設計 Domain 再往外做」，但實際開發時，我們發現從外而內更能控制風險。</p>
<p>原因很簡單：Layer 1 UI 壞掉只影響視覺，Layer 5 Domain 邏輯壞掉影響整個系統的業務規則。從影響最小的地方開始，需求偏差時調整成本低；一開始就動 Domain，到了 UI 才發現需求理解有誤，代價就大得多。</p>
<p>實作順序是 Layer 1 → Layer 2 → Layer 3 → Layer 4 → Layer 5，每層完成後立即驗證。</p>
<p>有幾個例外：架構遷移要先定義 Layer 4 介面契約（Interface-First），讓外層修改有穩定依據；安全性修復從 Layer 5 往外；Bug Fix 從問題根源那層開始。</p>
<h2 id="ticket-拆分的量化標準">Ticket 拆分的量化標準</h2>
<p>幾個判斷指標：修改檔案數 1 到 3 個（最多 5 個）、預估開發時間 2 到 8 小時（超過一天就拆）、修改層級嚴格限制 1 層、新增程式碼測試覆蓋率 100%。</p>
<p>數字可以商議，但有標準就不用靠直覺判斷「感覺差不多」。</p>
<p>反面教材：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Ticket：實作書籍收藏功能
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">變更範圍：
</span></span><span class="line"><span class="ln">4</span><span class="cl">- lib/ui/pages/book_detail_page.dart       (Layer 1)
</span></span><span class="line"><span class="ln">5</span><span class="cl">- lib/application/controllers/book_detail_controller.dart  (Layer 2)
</span></span><span class="line"><span class="ln">6</span><span class="cl">- lib/usecases/add_book_to_favorite_usecase.dart  (Layer 3)
</span></span><span class="line"><span class="ln">7</span><span class="cl">- lib/domain/entities/favorite.dart        (Layer 5)</span></span></code></pre></div><p>這張 Ticket 跨了四層，PR 送出來沒人知道從哪開始審，測試也很難設計。正確做法是拆成四張各自獨立的 Ticket，按依賴順序執行。</p>
<h2 id="如何判斷一段程式碼屬於哪一層">如何判斷一段程式碼屬於哪一層</h2>
<p>最常模糊的是 Layer 2 和 Layer 3 之間的邊界。判斷流程：</p>
<ol>
<li>在渲染 UI 元素？→ Layer 1</li>
<li>在處理 UI 事件、控制 UI 狀態、或把 Domain 資料轉成 UI 格式？→ Layer 2（把 Domain Exception 轉成 ErrorViewModel 也是這層的事）</li>
<li>在協調多個 Domain Service 或 Repository、編排業務步驟？→ Layer 3</li>
<li>在定義介面契約或事件結構？→ Layer 4</li>
<li>在實作業務規則或定義 Entity？→ Layer 5</li>
<li>以上都不是 → Infrastructure 層</li>
</ol>
<h2 id="這套方法論的定位">這套方法論的定位</h2>
<p>這是 Clean Architecture 的「派工指南」。Clean Architecture 告訴你程式碼怎麼組織，層級隔離告訴你 Ticket 怎麼拆、按什麼順序做。</p>
<p>它和 Atomic Ticket 方法論也不衝突：Atomic Ticket 強調職責維度（一個 Action 加一個 Target），層級隔離強調層級維度（一個 Ticket 只動一層），兩個維度同時符合才是最完整的 Ticket 設計。</p>
<p>緊急 Hotfix、原型開發、一次性腳本不需要強行套用。但在正常功能開發和重構中，跑起來之後的感覺是：每次把一個大需求拆成按層排好的 Ticket 序列，就等於把架構邊界重新確認了一遍。</p>]]></content:encoded></item><item><title>錯誤修復和重構方法論</title><link>https://tarrragon.github.io/blog/record/%E9%8C%AF%E8%AA%A4%E4%BF%AE%E5%BE%A9%E5%92%8C%E9%87%8D%E6%A7%8B%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E9%8C%AF%E8%AA%A4%E4%BF%AE%E5%BE%A9%E5%92%8C%E9%87%8D%E6%A7%8B%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>測試失敗了，應該修改程式，還是修改測試？&lt;/p>
&lt;p>這個判斷決定了整個修復方向。判斷錯，我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。&lt;/p></description><content:encoded><![CDATA[<p>測試失敗了，應該修改程式，還是修改測試？</p>
<p>這個判斷決定了整個修復方向。判斷錯，我們會花時間做出一個「讓測試通過」但實際上破壞需求的修改。</p>
<h2 id="核心原則程式服務測試測試服務需求">核心原則：程式服務測試，測試服務需求</h2>
<p>測試代表需求的具體描述。為了讓測試通過而修改測試本身，等於在需求上妥協。正確的做法是保持測試不變，調整程式實作直到符合期望。</p>
<p>唯一的例外：需求本身發生了變化——架構調整、業務流程重設計。這兩種情況的判斷，就是整個方法論的核心。</p>
<h2 id="第一步分類">第一步：分類</h2>
<p>面對測試失敗，先問「為什麼失敗」，不是「怎麼修」。</p>
<p><strong>程式實作錯誤</strong>：需求沒變，但程式行為不符預期——錯誤輸出、邏輯判斷有誤、型別處理不當。處理方式直接：保持測試不變，修正程式。</p>
<p>最容易犯的錯誤，是在這種情況下改了測試的期望值，讓測試配合錯誤的程式。這等於讓錯誤行為成為「正確需求」。</p>
<p><strong>架構變更需求</strong>：需求文件已更新，業務流程本質性改變，影響多個模組。這類情況確實需要調整測試，但前提是需求文件已反映這個變更。步驟：先確認文件、評估變更範圍、列出需修改的測試，最後才執行。跳過前置確認直接改測試，和無章法地亂改沒有區別。</p>
<h2 id="觀測公開行為不觀測內部實作">觀測公開行為，不觀測內部實作</h2>
<p>我們只驗證透過公開介面的輸入輸出、公開屬性的狀態變化。不該碰私有方法的調用順序、私有屬性的中間值。</p>
<p>一旦測試觀測內部狀態，它就和具體實作綁定了，任何重構都會讓測試失敗，即使行為沒變。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：觀測內部私有屬性
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍驗證應該檢查所有欄位&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">validator</span> <span class="o">=</span> <span class="n">BookValidator</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">validator</span><span class="p">.</span><span class="n">validate</span><span class="p">(</span><span class="n">invalidBook</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">validator</span><span class="p">.</span><span class="n">_titleValidated</span><span class="p">,</span> <span class="n">isTrue</span><span class="p">);</span>   <span class="c1">// 內部狀態
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="n">expect</span><span class="p">(</span><span class="n">validator</span><span class="p">.</span><span class="n">_isbnValidated</span><span class="p">,</span> <span class="n">isTrue</span><span class="p">);</span>    <span class="c1">// 私有屬性
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 正確：觀測公開行為結果
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍驗證應該檢查所有欄位&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kd">final</span> <span class="n">validator</span> <span class="o">=</span> <span class="n">BookValidator</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="n">validator</span><span class="p">.</span><span class="n">validate</span><span class="p">(</span><span class="n">invalidBook</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isValid</span><span class="p">,</span> <span class="n">isFalse</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">contains</span><span class="p">(</span><span class="s1">&#39;標題不可為空&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">contains</span><span class="p">(</span><span class="s1">&#39;ISBN 格式錯誤&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="三個常見的反模式">三個常見的反模式</h2>
<p><strong>測試遷就程式</strong>：程式返回錯誤值，開發者把測試期望值改成那個錯誤值。測試通過了，bug 也永遠存在了。正確做法：保持期望值不動，修正程式邏輯。</p>
<p><strong>內部狀態依賴</strong>：測試直接存取 <code>book._internalState</code> 來驗證操作。一旦重構就失敗。改用公開方法 <code>book.isAvailable()</code> 驗證行為結果。</p>
<p><strong>跳過文件檢查</strong>：覺得需求「應該」要改，就直接改測試，沒確認需求文件是否更新。正確做法：先查規格書，確認文件已反映變更，再評估影響範圍，最後才動手。</p>
<h2 id="驗收標準">驗收標準</h2>
<p>修復前：錯誤類型已判斷、需求文件狀態已確認、受影響測試範圍已識別。</p>
<p>修復後：所有測試 100% 通過、無內部狀態曝露、無行為旁路、每個修改都有測試保護。</p>
<p>100% 通過率是底線。某個測試通過不了，不是跳過它或修改它，是找出真正的問題。</p>]]></content:encoded></item><item><title>MVVM ViewModel 開發方法論</title><link>https://tarrragon.github.io/blog/record/mvvm-viewmodel-%E9%96%8B%E7%99%BC%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/mvvm-viewmodel-%E9%96%8B%E7%99%BC%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>為了提升AI開發前端的穩定性，我想依賴MVVM確實定義前端的狀態跟模型，以及責任分層，這樣可以降低除錯的複雜度&lt;/p>
&lt;h2 id="核心概念">核心概念&lt;/h2>
&lt;h3 id="viewmodel-定位">ViewModel 定位&lt;/h3>
&lt;p>&lt;strong>ViewModel 是 MVVM 架構的核心層&lt;/strong>，負責：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Domain → UI 轉換&lt;/strong>：將 Domain 模型轉為 UI 需要的格式&lt;/li>
&lt;li>&lt;strong>UI 狀態管理&lt;/strong>：管理 Widget 狀態和互動邏輯&lt;/li>
&lt;li>&lt;strong>Provider 定義&lt;/strong>：定義 Riverpod Provider 供 Widget 使用&lt;/li>
&lt;li>&lt;strong>UI 專用計算邏輯&lt;/strong>：提供顏色、圖標、格式化文字等 UI 屬性&lt;/li>
&lt;/ol>
&lt;h3 id="mvvm-分層原則">MVVM 分層原則&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">┌─────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">│ Presentation Layer (UI 層) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├─────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ Widget (Page/Extensions) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ - 純 UI 組裝，無業務邏輯 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ - 使用 ViewModel Provider 取得資料 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ - 顯示 ViewModel 提供的 UI 屬性 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├─────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ ViewModel │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ - Domain → UI 轉換 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ - UI 狀態管理 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ - UI 專用計算邏輯 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ - Provider 定義 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">├─────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">│ Mapper │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">│ - Domain 模型 → ViewModel 轉換邏輯 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">├─────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">│ Domain Layer (領域層) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">│ - 業務邏輯 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">│ - Domain 模型 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">│ - Domain 服務 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">└─────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="viewmodel-命名規範">ViewModel 命名規範&lt;/h2>
&lt;h3 id="命名格式">命名格式&lt;/h3>
&lt;p>&lt;strong>格式&lt;/strong>：&lt;code>[Feature]ViewModel&lt;/code>&lt;/p>
&lt;p>&lt;strong>範例&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>EnrichmentProgressViewModel&lt;/code> - 補充進度顯示&lt;/li>
&lt;li>&lt;code>ChromeExtensionImportViewModel&lt;/code> - Chrome Extension 匯入&lt;/li>
&lt;li>&lt;code>LibraryDisplayViewModel&lt;/code> - 書庫展示&lt;/li>
&lt;li>&lt;code>AdvancedSearchViewModel&lt;/code> - 進階搜尋&lt;/li>
&lt;/ul>
&lt;h3 id="檔案位置">檔案位置&lt;/h3>
&lt;p>&lt;strong>標準路徑&lt;/strong>：&lt;code>lib/presentation/[feature]/[feature]_viewmodel.dart&lt;/code>&lt;/p>
&lt;p>&lt;strong>範例&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">lib/presentation/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── import/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ └── chrome_extension_import_viewmodel.dart
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── library/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">│ ├── library_viewmodel.dart
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">│ └── library_display_page.dart
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">└── search/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> └── advanced_search_viewmodel.dart&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="viewmodel-職責定義">ViewModel 職責定義&lt;/h2>
&lt;h3 id="包含的職責">包含的職責&lt;/h3>
&lt;h4 id="1-domain--ui-轉換">1. Domain → UI 轉換&lt;/h4>
&lt;p>將 Domain 模型轉換為 UI 需要的格式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">/// Domain 來源
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">final&lt;/span> &lt;span class="n">EnrichmentProgress&lt;/span> &lt;span class="n">domainProgress&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1">/// UI 專用欄位（計算屬性）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">String&lt;/span> &lt;span class="kd">get&lt;/span> &lt;span class="n">displayStatus&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">_mapStatus&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">IconData&lt;/span> &lt;span class="kd">get&lt;/span> &lt;span class="n">statusIcon&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">_mapIcon&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">Color&lt;/span> &lt;span class="kd">get&lt;/span> &lt;span class="n">progressColor&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">_mapColor&lt;/span>&lt;span class="p">();&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="2-ui-狀態管理">2. UI 狀態管理&lt;/h4>
&lt;p>管理 Widget 需要的狀態：&lt;/p></description><content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>為了提升AI開發前端的穩定性，我想依賴MVVM確實定義前端的狀態跟模型，以及責任分層，這樣可以降低除錯的複雜度</p>
<h2 id="核心概念">核心概念</h2>
<h3 id="viewmodel-定位">ViewModel 定位</h3>
<p><strong>ViewModel 是 MVVM 架構的核心層</strong>，負責：</p>
<ol>
<li><strong>Domain → UI 轉換</strong>：將 Domain 模型轉為 UI 需要的格式</li>
<li><strong>UI 狀態管理</strong>：管理 Widget 狀態和互動邏輯</li>
<li><strong>Provider 定義</strong>：定義 Riverpod Provider 供 Widget 使用</li>
<li><strong>UI 專用計算邏輯</strong>：提供顏色、圖標、格式化文字等 UI 屬性</li>
</ol>
<h3 id="mvvm-分層原則">MVVM 分層原則</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">┌─────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│ Presentation Layer (UI 層)              │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├─────────────────────────────────────────┤
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│ Widget (Page/Extensions)                │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│ - 純 UI 組裝，無業務邏輯                 │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│ - 使用 ViewModel Provider 取得資料       │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│ - 顯示 ViewModel 提供的 UI 屬性          │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├─────────────────────────────────────────┤
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│ ViewModel                               │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│ - Domain → UI 轉換                       │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│ - UI 狀態管理                            │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│ - UI 專用計算邏輯                        │
</span></span><span class="line"><span class="ln">13</span><span class="cl">│ - Provider 定義                          │
</span></span><span class="line"><span class="ln">14</span><span class="cl">├─────────────────────────────────────────┤
</span></span><span class="line"><span class="ln">15</span><span class="cl">│ Mapper                                  │
</span></span><span class="line"><span class="ln">16</span><span class="cl">│ - Domain 模型 → ViewModel 轉換邏輯       │
</span></span><span class="line"><span class="ln">17</span><span class="cl">├─────────────────────────────────────────┤
</span></span><span class="line"><span class="ln">18</span><span class="cl">│ Domain Layer (領域層)                    │
</span></span><span class="line"><span class="ln">19</span><span class="cl">│ - 業務邏輯                               │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│ - Domain 模型                            │
</span></span><span class="line"><span class="ln">21</span><span class="cl">│ - Domain 服務                            │
</span></span><span class="line"><span class="ln">22</span><span class="cl">└─────────────────────────────────────────┘</span></span></code></pre></div><hr>
<h2 id="viewmodel-命名規範">ViewModel 命名規範</h2>
<h3 id="命名格式">命名格式</h3>
<p><strong>格式</strong>：<code>[Feature]ViewModel</code></p>
<p><strong>範例</strong>：</p>
<ul>
<li><code>EnrichmentProgressViewModel</code> - 補充進度顯示</li>
<li><code>ChromeExtensionImportViewModel</code> - Chrome Extension 匯入</li>
<li><code>LibraryDisplayViewModel</code> - 書庫展示</li>
<li><code>AdvancedSearchViewModel</code> - 進階搜尋</li>
</ul>
<h3 id="檔案位置">檔案位置</h3>
<p><strong>標準路徑</strong>：<code>lib/presentation/[feature]/[feature]_viewmodel.dart</code></p>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">lib/presentation/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── import/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   └── chrome_extension_import_viewmodel.dart
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── library/
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   ├── library_viewmodel.dart
</span></span><span class="line"><span class="ln">6</span><span class="cl">│   └── library_display_page.dart
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── search/
</span></span><span class="line"><span class="ln">8</span><span class="cl">    └── advanced_search_viewmodel.dart</span></span></code></pre></div><hr>
<h2 id="viewmodel-職責定義">ViewModel 職責定義</h2>
<h3 id="包含的職責">包含的職責</h3>
<h4 id="1-domain--ui-轉換">1. Domain → UI 轉換</h4>
<p>將 Domain 模型轉換為 UI 需要的格式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">/// Domain 來源
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">EnrichmentProgress</span> <span class="n">domainProgress</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">/// UI 專用欄位（計算屬性）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="kt">String</span> <span class="kd">get</span> <span class="n">displayStatus</span> <span class="o">=&gt;</span> <span class="n">_mapStatus</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">IconData</span> <span class="kd">get</span> <span class="n">statusIcon</span> <span class="o">=&gt;</span> <span class="n">_mapIcon</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">Color</span> <span class="kd">get</span> <span class="n">progressColor</span> <span class="o">=&gt;</span> <span class="n">_mapColor</span><span class="p">();</span></span></span></code></pre></div><h4 id="2-ui-狀態管理">2. UI 狀態管理</h4>
<p>管理 Widget 需要的狀態：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">class</span> <span class="nc">EnrichmentProgressViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// 狀態欄位
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">EnrichmentProgress</span> <span class="n">domainProgress</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">failedBooks</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="c1">// UI 控制狀態
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span>  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">showFailedBooks</span> <span class="o">=&gt;</span> <span class="n">failedBooks</span><span class="p">.</span><span class="n">isNotEmpty</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">canRetry</span> <span class="o">=&gt;</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span> <span class="o">&amp;&amp;</span> <span class="n">failedBooks</span><span class="p">.</span><span class="n">isNotEmpty</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h4 id="3-provider-定義">3. Provider 定義</h4>
<p>定義 Riverpod Provider 供 Widget 使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">final</span> <span class="n">enrichmentProgressViewModelProvider</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="n">StreamProvider</span><span class="p">.</span><span class="n">family</span><span class="o">&lt;</span><span class="n">EnrichmentProgressViewModel</span><span class="p">,</span> <span class="kt">String</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="p">(</span><span class="n">ref</span><span class="p">,</span> <span class="n">operationId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">      <span class="c1">// Provider 邏輯
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>    <span class="p">},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">);</span></span></span></code></pre></div><h4 id="4-ui-專用計算邏輯">4. UI 專用計算邏輯</h4>
<p>提供 UI 需要的格式化資料：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// 格式化的摘要文字
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">String</span> <span class="kd">get</span> <span class="n">summaryText</span> <span class="o">=&gt;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="s1">&#39;已處理 </span><span class="si">${</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">processedBooks</span><span class="si">}</span><span class="s1">/</span><span class="si">${</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">totalBooks</span><span class="si">}</span><span class="s1"> 本&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// 進度顏色（根據狀態決定）
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span><span class="n">Color</span> <span class="kd">get</span> <span class="n">progressColor</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">failedEnrichments</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">orange</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span><span class="p">)</span> <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">green</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">blue</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="不包含的內容">不包含的內容</h3>
<h4 id="1-widget-程式碼">1. Widget 程式碼</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：ViewModel 中包含 Widget
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">EnrichmentProgressViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">Widget</span> <span class="n">buildProgressBar</span><span class="p">()</span> <span class="p">{</span>  <span class="c1">// 違規
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">LinearProgressIndicator</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 正例：Widget 在 Extension 中
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="n">extension</span> <span class="n">EnrichmentProgressWidgets</span> <span class="n">on</span> <span class="n">Widget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">Widget</span> <span class="n">enrichmentProgressBar</span><span class="p">(</span><span class="n">EnrichmentProgressViewModel</span> <span class="n">vm</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">LinearProgressIndicator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="nl">value:</span> <span class="n">vm</span><span class="p">.</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">percentageComplete</span> <span class="o">/</span> <span class="m">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="nl">color:</span> <span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h4 id="2-直接依賴-flutter-框架除-changenotifier">2. 直接依賴 Flutter 框架（除 ChangeNotifier）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：依賴 Flutter Material
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:flutter/material.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">BuildContext</span><span class="o">?</span> <span class="n">context</span><span class="p">;</span>  <span class="c1">// 違規
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 正例：使用 Dart 原生類型
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">Color</span> <span class="n">progressColor</span><span class="p">;</span>  <span class="c1">// 可以使用 Color（來自 dart:ui）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>  <span class="n">IconData</span> <span class="n">statusIcon</span><span class="p">;</span>  <span class="c1">// 可以使用 IconData
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h4 id="3-業務邏輯">3. 業務邏輯</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：在 ViewModel 中執行業務邏輯
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">EnrichmentProgressViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">enrichBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1">// 呼叫 API、驗證資料、儲存到資料庫
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="c1">// 這些是 Domain 層的職責
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// 正例：業務邏輯在 Domain Service
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">IBookInfoEnrichmentService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">EnrichedBookInfo</span><span class="o">&gt;</span> <span class="n">enrichBookInfo</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// ViewModel 只負責狀態管理
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">EnrichmentProgressViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="kd">final</span> <span class="n">EnrichmentProgress</span> <span class="n">domainProgress</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="c1">// 不包含業務邏輯
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="viewmodel-結構範本">ViewModel 結構範本</h2>
<h3 id="基本結構">基本結構</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// UI 層專用的 [Feature] 顯示模型
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 職責：
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// - 將 Domain 模型轉換為 UI 需要的格式
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - 提供 UI 專用的計算屬性
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - 管理 UI 狀態
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">/// 需求：[需求編號]
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="err">[</span><span class="nc">Feature</span><span class="p">]</span><span class="n">ViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>  <span class="c1">// Domain 來源（不可變）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="c1">/// Domain 模型來源
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="p">[</span><span class="n">DomainModel</span><span class="p">]</span> <span class="n">domainModel</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="c1">/// 額外的 Domain 資料（如失敗清單）
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="p">[</span><span class="n">Entity</span><span class="p">]</span><span class="o">&gt;</span> <span class="n">additionalData</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>  <span class="c1">// UI 專用欄位（計算屬性）
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="c1">/// 狀態顯示文字
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="kd">get</span> <span class="n">displayStatus</span> <span class="o">=&gt;</span> <span class="n">_mapStatus</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="c1">/// 狀態圖標
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span>  <span class="n">IconData</span> <span class="kd">get</span> <span class="n">statusIcon</span> <span class="o">=&gt;</span> <span class="n">_mapIcon</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="c1">/// 進度顏色
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"></span>  <span class="n">Color</span> <span class="kd">get</span> <span class="n">progressColor</span> <span class="o">=&gt;</span> <span class="n">_mapColor</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">  <span class="c1">/// 摘要文字
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="kd">get</span> <span class="n">summaryText</span> <span class="o">=&gt;</span> <span class="n">_formatSummary</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"></span>  <span class="c1">// 建構子
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">  <span class="kd">const</span> <span class="p">[</span><span class="n">Feature</span><span class="p">]</span><span class="n">ViewModel</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">domainModel</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">additionalData</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"></span>  <span class="c1">// Domain → UI 轉換方法（私有）
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">  <span class="c1">/// 對應狀態到顯示文字
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="n">_mapStatus</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="c1">// 轉換邏輯
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">  <span class="c1">/// 對應狀態到圖標
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="c1"></span>  <span class="n">IconData</span> <span class="n">_mapIcon</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="c1">// 轉換邏輯
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">  <span class="c1">/// 對應狀態到顏色
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="c1"></span>  <span class="n">Color</span> <span class="n">_mapColor</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="c1">// 轉換邏輯
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">  <span class="c1">/// 格式化摘要文字
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="n">_formatSummary</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="c1">// 格式化邏輯
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="完整範例enrichmentprogressviewmodel">完整範例：EnrichmentProgressViewModel</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:flutter/material.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/import/value_objects/enrichment_progress.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="c1">/// UI 層專用的補充進度顯示模型
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="c1">/// 職責：
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="c1">/// - 將 EnrichmentProgress Domain 模型轉為 UI 格式
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="c1">/// - 提供進度顏色、圖標、文字等 UI 屬性
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="c1">/// - 管理失敗書籍清單的顯示
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1">/// 需求：UC-01.Enrichment.Progress
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">EnrichmentProgressViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"></span>  <span class="c1">// Domain 來源
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">  <span class="c1">/// Domain 進度模型
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">EnrichmentProgress</span> <span class="n">domainProgress</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">  <span class="c1">/// 失敗補充的書籍清單
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">failedBooks</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="c1"></span>  <span class="c1">// UI 專用欄位（計算屬性）
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">  <span class="c1">/// 狀態顯示文字
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 對應規則：
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - processedBooks == 0 → &#34;準備中&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - processedBooks &gt; 0 &amp;&amp; !isComplete → &#34;補充中&#34;
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - isComplete → &#34;已完成&#34;
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="kd">get</span> <span class="n">displayStatus</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span><span class="p">)</span> <span class="k">return</span> <span class="s1">&#39;已完成&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">processedBooks</span> <span class="o">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="s1">&#39;準備中&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">return</span> <span class="s1">&#39;補充中&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">  <span class="c1">/// 狀態圖標
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 對應規則：
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 準備中 → Icons.pending
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 補充中 → Icons.sync
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 已完成 → Icons.check_circle
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="c1"></span>  <span class="n">IconData</span> <span class="kd">get</span> <span class="n">statusIcon</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span><span class="p">)</span> <span class="k">return</span> <span class="n">Icons</span><span class="p">.</span><span class="n">check_circle</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">processedBooks</span> <span class="o">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Icons</span><span class="p">.</span><span class="n">pending</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="k">return</span> <span class="n">Icons</span><span class="p">.</span><span class="kd">sync</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">  <span class="c1">/// 進度顏色
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 對應規則：
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 有失敗 → 橘色警告
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 已完成 → 綠色成功
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - 進行中 → 藍色
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="c1"></span>  <span class="n">Color</span> <span class="kd">get</span> <span class="n">progressColor</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">failedEnrichments</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">orange</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span><span class="p">)</span> <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">green</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="k">return</span> <span class="n">Colors</span><span class="p">.</span><span class="n">blue</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">  <span class="c1">/// 摘要文字
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 格式：「已處理 X/Y 本（成功 A，失敗 B）」
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span> <span class="kd">get</span> <span class="n">summaryText</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="kd">final</span> <span class="n">processed</span> <span class="o">=</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">processedBooks</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="kd">final</span> <span class="n">total</span> <span class="o">=</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">totalBooks</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="kd">final</span> <span class="n">success</span> <span class="o">=</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">successfulEnrichments</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="kd">final</span> <span class="n">failed</span> <span class="o">=</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">failedEnrichments</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">failed</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">      <span class="k">return</span> <span class="s1">&#39;已處理 </span><span class="si">$</span><span class="n">processed</span><span class="s1">/</span><span class="si">$</span><span class="n">total</span><span class="s1"> 本（成功 </span><span class="si">$</span><span class="n">success</span><span class="s1">，失敗 </span><span class="si">$</span><span class="n">failed</span><span class="s1">）&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">return</span> <span class="s1">&#39;已處理 </span><span class="si">$</span><span class="n">processed</span><span class="s1">/</span><span class="si">$</span><span class="n">total</span><span class="s1"> 本&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">  <span class="c1">/// 失敗書籍摘要清單
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 提供簡化的書籍資訊供 UI 顯示
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="c1"></span>  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookSummary</span><span class="o">&gt;</span> <span class="kd">get</span> <span class="n">failedBooksSummary</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">return</span> <span class="n">failedBooks</span><span class="p">.</span><span class="n">map</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">BookSummary</span><span class="p">.</span><span class="n">fromBook</span><span class="p">(</span><span class="n">book</span><span class="p">)).</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">  <span class="c1">/// 進度百分比（直接使用 Domain 計算）
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="c1"></span>  <span class="kt">double</span> <span class="kd">get</span> <span class="n">progressPercentage</span> <span class="o">=&gt;</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">percentageComplete</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">  <span class="c1">/// 當前處理書名（如果有）
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="c1"></span>  <span class="kt">String</span><span class="o">?</span> <span class="kd">get</span> <span class="n">currentBookTitle</span> <span class="o">=&gt;</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">currentBook</span><span class="o">?</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">  <span class="c1">/// 是否顯示失敗清單
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="c1"></span>  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">showFailedBooks</span> <span class="o">=&gt;</span> <span class="n">failedBooks</span><span class="p">.</span><span class="n">isNotEmpty</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">  <span class="c1">/// 是否可以重試
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="c1"></span>  <span class="kt">bool</span> <span class="kd">get</span> <span class="n">canRetry</span> <span class="o">=&gt;</span> <span class="n">domainProgress</span><span class="p">.</span><span class="n">isComplete</span> <span class="o">&amp;&amp;</span> <span class="n">failedBooks</span><span class="p">.</span><span class="n">isNotEmpty</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="c1"></span>  <span class="c1">// 建構子
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="c1"></span>  <span class="c1">// =============================================================================
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">  <span class="kd">const</span> <span class="n">EnrichmentProgressViewModel</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">domainProgress</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">failedBooks</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="c1">/// 書籍摘要（UI 專用簡化資料）
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookSummary</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">author</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">  <span class="kd">const</span> <span class="n">BookSummary</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">title</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">author</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl">  <span class="kd">factory</span> <span class="n">BookSummary</span><span class="p">.</span><span class="n">fromBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="k">return</span> <span class="n">BookSummary</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">      <span class="nl">id:</span> <span class="n">book</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">      <span class="nl">title:</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">      <span class="nl">author:</span> <span class="n">book</span><span class="p">.</span><span class="n">author</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="mapper-模式">Mapper 模式</h2>
<h3 id="mapper-職責">Mapper 職責</h3>
<p><strong>Mapper 負責 Domain 模型 → ViewModel 的轉換邏輯</strong>。</p>
<h3 id="mapper-結構">Mapper 結構</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// Domain [DomainModel] → UI ViewModel 轉換器
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 職責：
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// - 將 Domain 模型轉換為 ViewModel
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - 整合多個 Domain 資料來源
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - 處理轉換過程中的資料格式化
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="err">[</span><span class="nc">Feature</span><span class="p">]</span><span class="n">Mapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="c1">/// 轉換 Domain 模型為 ViewModel
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>  <span class="kd">static</span> <span class="p">[</span><span class="n">Feature</span><span class="p">]</span><span class="n">ViewModel</span> <span class="n">toViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">[</span><span class="n">DomainModel</span><span class="p">]</span> <span class="n">domainModel</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1">// 額外的 Domain 資料來源
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">Feature</span><span class="p">]</span><span class="n">ViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">      <span class="nl">domainModel:</span> <span class="n">domainModel</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">      <span class="c1">// 額外欄位轉換
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>    <span class="p">);</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="c1">/// 批量轉換
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="kd">static</span> <span class="n">List</span><span class="o">&lt;</span><span class="p">[</span><span class="n">Feature</span><span class="p">]</span><span class="n">ViewModel</span><span class="o">&gt;</span> <span class="n">toViewModelList</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">List</span><span class="o">&lt;</span><span class="p">[</span><span class="n">DomainModel</span><span class="p">]</span><span class="o">&gt;</span> <span class="n">domainModels</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">domainModels</span><span class="p">.</span><span class="n">map</span><span class="p">((</span><span class="n">model</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">model</span><span class="p">)).</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="完整範例enrichmentprogressmapper">完整範例：EnrichmentProgressMapper</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/import/value_objects/enrichment_progress.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/presentation/import/enrichment_progress_viewmodel.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// Domain EnrichmentProgress → UI ViewModel 轉換器
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">/// 職責：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">/// - 整合 EnrichmentProgress 和失敗書籍清單
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">/// - 轉換為 UI 層需要的 ViewModel 格式
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">EnrichmentProgressMapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="c1">/// 轉換 Domain 進度模型為 ViewModel
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 參數：
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - [progress]: Domain 進度模型
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>  <span class="c1">/// - [failedBooks]: 失敗補充的書籍清單（從 Service 取得）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>  <span class="c1">///
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>  <span class="c1">/// 回傳：UI 層專用的 ViewModel
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>  <span class="kd">static</span> <span class="n">EnrichmentProgressViewModel</span> <span class="n">toViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">EnrichmentProgress</span> <span class="n">progress</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">failedBooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="n">EnrichmentProgressViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">      <span class="nl">domainProgress:</span> <span class="n">progress</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">      <span class="nl">failedBooks:</span> <span class="n">failedBooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="c1">/// 批量轉換（如果需要顯示多個進度）
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"></span>  <span class="kd">static</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">EnrichmentProgressViewModel</span><span class="o">&gt;</span> <span class="n">toViewModelList</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">List</span><span class="o">&lt;</span><span class="n">EnrichmentProgress</span><span class="o">&gt;</span> <span class="n">progressList</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">failedBooksMap</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="k">return</span> <span class="n">progressList</span><span class="p">.</span><span class="n">map</span><span class="p">((</span><span class="n">progress</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">      <span class="c1">// 假設每個 progress 有唯一 ID
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">failedBooks</span> <span class="o">=</span> <span class="n">failedBooksMap</span><span class="p">[</span><span class="n">progress</span><span class="p">.</span><span class="n">hashCode</span><span class="p">.</span><span class="n">toString</span><span class="p">()]</span> <span class="o">??</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">      <span class="k">return</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="n">failedBooks</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">}).</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="provider-整合模式">Provider 整合模式</h2>
<h3 id="streamprovider-整合">StreamProvider 整合</h3>
<p><strong>當 Domain 資料是 Stream 時使用 StreamProvider</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// ViewModel StreamProvider 定義
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">///
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">/// 職責：
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">/// - 整合多個 Domain Provider
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">/// - 使用 Mapper 轉換為 ViewModel
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">/// - 提供給 Widget 使用
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">enrichmentProgressViewModelProvider</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="n">StreamProvider</span><span class="p">.</span><span class="n">family</span><span class="o">&lt;</span><span class="n">EnrichmentProgressViewModel</span><span class="p">,</span> <span class="kt">String</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">(</span><span class="n">ref</span><span class="p">,</span> <span class="n">operationId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="c1">// 1. 監聽 Domain Progress Stream
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">domainProgressStream</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">enrichmentProgressProvider</span><span class="p">(</span><span class="n">operationId</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">      <span class="c1">// 2. 監聽失敗書籍 Stream
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>      <span class="kd">final</span> <span class="n">failedBooksStream</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">failedBooksProvider</span><span class="p">(</span><span class="n">operationId</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">      <span class="c1">// 3. 合併 Stream 並轉換為 ViewModel
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>      <span class="k">return</span> <span class="n">Rx</span><span class="p">.</span><span class="n">combineLatest2</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">domainProgressStream</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">failedBooksStream</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="p">(</span><span class="n">EnrichmentProgress</span> <span class="n">progress</span><span class="p">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">failedBooks</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">          <span class="k">return</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">progress</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">failedBooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">          <span class="p">);</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="p">);</span></span></span></code></pre></div><h3 id="stateprovider-整合">StateProvider 整合</h3>
<p><strong>當 ViewModel 需要狀態管理時使用 Notifier</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">/// ViewModel State 定義
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">LibraryDisplayState</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">DisplayMode</span> <span class="n">displayMode</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">LibraryBookModel</span><span class="o">&gt;</span> <span class="n">books</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="n">Set</span><span class="o">&lt;</span><span class="kt">String</span><span class="o">&gt;</span> <span class="n">selectedBookIds</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">const</span> <span class="n">LibraryDisplayState</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">displayMode</span> <span class="o">=</span> <span class="n">DisplayMode</span><span class="p">.</span><span class="n">simple</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">books</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">selectedBookIds</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="n">LibraryDisplayState</span> <span class="n">copyWith</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">DisplayMode</span><span class="o">?</span> <span class="n">displayMode</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">List</span><span class="o">&lt;</span><span class="n">LibraryBookModel</span><span class="o">&gt;?</span> <span class="n">books</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">Set</span><span class="o">&lt;</span><span class="kt">String</span><span class="o">&gt;?</span> <span class="n">selectedBookIds</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">LibraryDisplayState</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">      <span class="nl">displayMode:</span> <span class="n">displayMode</span> <span class="o">??</span> <span class="k">this</span><span class="p">.</span><span class="n">displayMode</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">      <span class="nl">books:</span> <span class="n">books</span> <span class="o">??</span> <span class="k">this</span><span class="p">.</span><span class="n">books</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">      <span class="nl">selectedBookIds:</span> <span class="n">selectedBookIds</span> <span class="o">??</span> <span class="k">this</span><span class="p">.</span><span class="n">selectedBookIds</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1">/// ViewModel Notifier
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">LibraryDisplayViewModel</span> <span class="kd">extends</span> <span class="n">Notifier</span><span class="o">&lt;</span><span class="n">LibraryDisplayState</span><span class="o">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="n">LibraryDisplayState</span> <span class="n">build</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="kd">const</span> <span class="n">LibraryDisplayState</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">  <span class="c1">/// 切換顯示模式
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">toggleDisplayMode</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="kd">final</span> <span class="n">newMode</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">displayMode</span> <span class="o">==</span> <span class="n">DisplayMode</span><span class="p">.</span><span class="n">simple</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="o">?</span> <span class="n">DisplayMode</span><span class="p">.</span><span class="n">management</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="o">:</span> <span class="n">DisplayMode</span><span class="p">.</span><span class="n">simple</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">state</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">copyWith</span><span class="p">(</span><span class="nl">displayMode:</span> <span class="n">newMode</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="c1">/// 選擇書籍
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">toggleBookSelection</span><span class="p">(</span><span class="kt">String</span> <span class="n">bookId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="kd">final</span> <span class="n">selectedIds</span> <span class="o">=</span> <span class="n">Set</span><span class="o">&lt;</span><span class="kt">String</span><span class="o">&gt;</span><span class="p">.</span><span class="n">from</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">selectedBookIds</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">selectedIds</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">bookId</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">      <span class="n">selectedIds</span><span class="p">.</span><span class="n">remove</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">      <span class="n">selectedIds</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">state</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">copyWith</span><span class="p">(</span><span class="nl">selectedBookIds:</span> <span class="n">selectedIds</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1">/// Provider 定義
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">libraryDisplayViewModelProvider</span> <span class="o">=</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="n">NotifierProvider</span><span class="o">&lt;</span><span class="n">LibraryDisplayViewModel</span><span class="p">,</span> <span class="n">LibraryDisplayState</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="n">LibraryDisplayViewModel</span><span class="p">.</span><span class="k">new</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">  <span class="p">);</span></span></span></code></pre></div><hr>
<h2 id="widget-使用方式">Widget 使用方式</h2>
<h3 id="streamprovider-使用">StreamProvider 使用</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">class</span> <span class="nc">EnrichmentProgressWidget</span> <span class="kd">extends</span> <span class="n">ConsumerWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">operationId</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="kd">const</span> <span class="n">EnrichmentProgressWidget</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">operationId</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">super</span><span class="p">.</span><span class="n">key</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">WidgetRef</span> <span class="n">ref</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="kd">final</span> <span class="n">viewModelAsync</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="n">enrichmentProgressViewModelProvider</span><span class="p">(</span><span class="n">operationId</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="n">viewModelAsync</span><span class="p">.</span><span class="n">when</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">      <span class="nl">data:</span> <span class="p">(</span><span class="n">viewModel</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">_buildProgressContent</span><span class="p">(</span><span class="n">viewModel</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">      <span class="nl">loading:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="kd">const</span> <span class="n">CircularProgressIndicator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">      <span class="nl">error:</span> <span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">stack</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">ErrorWidget</span><span class="p">(</span><span class="n">error</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="n">Widget</span> <span class="n">_buildProgressContent</span><span class="p">(</span><span class="n">EnrichmentProgressViewModel</span> <span class="n">vm</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1">// 使用 ViewModel 的 UI 屬性
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span>        <span class="n">Icon</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">statusIcon</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">displayStatus</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">LinearProgressIndicator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">          <span class="nl">value:</span> <span class="n">vm</span><span class="p">.</span><span class="n">progressPercentage</span> <span class="o">/</span> <span class="m">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">          <span class="nl">color:</span> <span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">summaryText</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="c1">// 失敗清單
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">showFailedBooks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">          <span class="n">_buildFailedBooksList</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">failedBooksSummary</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="stateprovider-使用">StateProvider 使用</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">class</span> <span class="nc">LibraryDisplayPage</span> <span class="kd">extends</span> <span class="n">ConsumerWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">WidgetRef</span> <span class="n">ref</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="kd">final</span> <span class="n">state</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">watch</span><span class="p">(</span><span class="n">libraryDisplayViewModelProvider</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="kd">final</span> <span class="n">viewModel</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="n">libraryDisplayViewModelProvider</span><span class="p">.</span><span class="n">notifier</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s1">&#39;書庫&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nl">actions:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">          <span class="n">IconButton</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="p">.</span><span class="n">view_list</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="nl">onPressed:</span> <span class="n">viewModel</span><span class="p">.</span><span class="n">toggleDisplayMode</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">      <span class="nl">body:</span> <span class="n">ListView</span><span class="p">.</span><span class="n">builder</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="nl">itemCount:</span> <span class="n">state</span><span class="p">.</span><span class="n">books</span><span class="p">.</span><span class="n">length</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">          <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">books</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">          <span class="kd">final</span> <span class="n">isSelected</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">selectedBookIds</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">          <span class="k">return</span> <span class="n">ListTile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="nl">selected:</span> <span class="n">isSelected</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="nl">onTap:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="n">viewModel</span><span class="p">.</span><span class="n">toggleBookSelection</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">id</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">          <span class="p">);</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="測試要求">測試要求</h2>
<h3 id="單元測試覆蓋率">單元測試覆蓋率</h3>
<p><strong>每個 ViewModel 必須有單元測試，覆蓋率 ≥ 90%</strong>。</p>
<h3 id="測試項目">測試項目</h3>
<ol>
<li><strong>Domain → UI 轉換邏輯</strong></li>
<li><strong>UI 專用計算邏輯</strong></li>
<li><strong>狀態管理邏輯</strong>（如果是 Notifier）</li>
<li><strong>邊界條件和錯誤處理</strong></li>
</ol>
<h3 id="測試範例">測試範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:flutter_test/flutter_test.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/import/value_objects/enrichment_progress.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/presentation/import/enrichment_progress_viewmodel.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/presentation/import/enrichment_progress_mapper.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;EnrichmentProgressViewModel&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">group</span><span class="p">(</span><span class="s1">&#39;displayStatus&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;準備中 - processedBooks == 0&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">        <span class="c1">// Arrange
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1"></span>        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">.</span><span class="n">initial</span><span class="p">(</span><span class="m">10</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="c1">// Act &amp; Assert
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"></span>        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">displayStatus</span><span class="p">,</span> <span class="s1">&#39;準備中&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;補充中 - processedBooks &gt; 0 且未完成&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="c1">// Arrange
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c1"></span>        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="c1">// Act &amp; Assert
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="c1"></span>        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">displayStatus</span><span class="p">,</span> <span class="s1">&#39;補充中&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;已完成 - processedBooks == totalBooks&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="c1">// Arrange
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="c1"></span>        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="c1">// Act &amp; Assert
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="c1"></span>        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">displayStatus</span><span class="p">,</span> <span class="s1">&#39;已完成&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">group</span><span class="p">(</span><span class="s1">&#39;statusIcon&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;準備中 - Icons.pending&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">.</span><span class="n">initial</span><span class="p">(</span><span class="m">10</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">statusIcon</span><span class="p">,</span> <span class="n">Icons</span><span class="p">.</span><span class="n">pending</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;補充中 - Icons.sync&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">statusIcon</span><span class="p">,</span> <span class="n">Icons</span><span class="p">.</span><span class="kd">sync</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;已完成 - Icons.check_circle&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">statusIcon</span><span class="p">,</span> <span class="n">Icons</span><span class="p">.</span><span class="n">check_circle</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="n">group</span><span class="p">(</span><span class="s1">&#39;progressColor&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;有失敗 - Colors.orange&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">,</span> <span class="n">Colors</span><span class="p">.</span><span class="n">orange</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;已完成無失敗 - Colors.green&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">,</span> <span class="n">Colors</span><span class="p">.</span><span class="n">green</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;進行中 - Colors.blue&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">progressColor</span><span class="p">,</span> <span class="n">Colors</span><span class="p">.</span><span class="n">blue</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="n">group</span><span class="p">(</span><span class="s1">&#39;summaryText&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;無失敗 - 顯示處理進度&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">summaryText</span><span class="p">,</span> <span class="s1">&#39;已處理 5/10 本&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">      <span class="n">test</span><span class="p">(</span><span class="s1">&#39;有失敗 - 顯示成功和失敗數&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="kd">final</span> <span class="n">progress</span> <span class="o">=</span> <span class="n">EnrichmentProgress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">          <span class="nl">totalBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">          <span class="nl">processedBooks:</span> <span class="m">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">          <span class="nl">successfulEnrichments:</span> <span class="m">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">          <span class="nl">failedEnrichments:</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="kd">final</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">EnrichmentProgressMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">progress</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="n">expect</span><span class="p">(</span><span class="n">vm</span><span class="p">.</span><span class="n">summaryText</span><span class="p">,</span> <span class="s1">&#39;已處理 10/10 本（成功 8，失敗 2）&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="viewmodel-開發檢查清單">ViewModel 開發檢查清單</h2>
<h3 id="phase-1-設計階段">Phase 1: 設計階段</h3>
<ul>
<li><input disabled="" type="checkbox"> 確認 Domain 模型已完成</li>
<li><input disabled="" type="checkbox"> 定義 ViewModel 需要的 UI 屬性</li>
<li><input disabled="" type="checkbox"> 設計 Domain → UI 轉換邏輯</li>
<li><input disabled="" type="checkbox"> 規劃 Mapper 結構</li>
<li><input disabled="" type="checkbox"> 定義 Provider 整合方式</li>
</ul>
<h3 id="phase-2-實作階段">Phase 2: 實作階段</h3>
<ul>
<li><input disabled="" type="checkbox"> 建立 ViewModel 類別和欄位</li>
<li><input disabled="" type="checkbox"> 實作 UI 專用計算屬性</li>
<li><input disabled="" type="checkbox"> 實作 Mapper 轉換方法</li>
<li><input disabled="" type="checkbox"> 定義 Provider</li>
<li><input disabled="" type="checkbox"> 撰寫完整註解（包含需求編號）</li>
</ul>
<h3 id="phase-3-測試階段">Phase 3: 測試階段</h3>
<ul>
<li><input disabled="" type="checkbox"> 撰寫 ViewModel 單元測試</li>
<li><input disabled="" type="checkbox"> 測試所有計算屬性</li>
<li><input disabled="" type="checkbox"> 測試 Mapper 轉換邏輯</li>
<li><input disabled="" type="checkbox"> 測試邊界條件</li>
<li><input disabled="" type="checkbox"> 達成 90% 以上覆蓋率</li>
</ul>
<h3 id="phase-4-整合階段">Phase 4: 整合階段</h3>
<ul>
<li><input disabled="" type="checkbox"> Widget 整合 ViewModel Provider</li>
<li><input disabled="" type="checkbox"> 驗證 UI 正確顯示</li>
<li><input disabled="" type="checkbox"> 執行 Widget 測試</li>
<li><input disabled="" type="checkbox"> Code Review 確認符合規範</li>
</ul>
<hr>
<h2 id="常見問題和最佳實踐">常見問題和最佳實踐</h2>
<h3 id="q1-viewmodel-可以包含-statefulwidget-的狀態嗎">Q1: ViewModel 可以包含 StatefulWidget 的狀態嗎？</h3>
<p><strong>A</strong>: 不可以。ViewModel 應該是純資料模型，不包含 Widget 生命週期邏輯。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 反例
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 正例
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kd">final</span> <span class="n">MyDomainModel</span> <span class="n">domainModel</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="c1">// 純資料模型
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h3 id="q2-如何處理複雜的-ui-狀態">Q2: 如何處理複雜的 UI 狀態？</h3>
<p><strong>A</strong>: 使用 Notifier 管理狀態，定義 State 類別。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 正例
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">MyState</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">DisplayMode</span> <span class="n">mode</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Item</span><span class="o">&gt;</span> <span class="n">items</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="n">Set</span><span class="o">&lt;</span><span class="kt">String</span><span class="o">&gt;</span> <span class="n">selectedIds</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="n">MyState</span> <span class="n">copyWith</span><span class="p">({...})</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="kd">extends</span> <span class="n">Notifier</span><span class="o">&lt;</span><span class="n">MyState</span><span class="o">&gt;</span> <span class="p">{</span> <span class="p">}</span></span></span></code></pre></div><h3 id="q3-viewmodel-可以呼叫-domain-service-嗎">Q3: ViewModel 可以呼叫 Domain Service 嗎？</h3>
<p><strong>A</strong>: 可以，但建議透過 Provider 整合而非直接呼叫。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 推薦：透過 Provider 整合
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">myViewModelProvider</span> <span class="o">=</span> <span class="n">Provider</span><span class="p">((</span><span class="n">ref</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">final</span> <span class="n">domainData</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="n">watch</span><span class="p">(</span><span class="n">domainServiceProvider</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">return</span> <span class="n">MyMapper</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">domainData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// 可接受但不推薦：直接呼叫
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">MyViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="kd">final</span> <span class="n">MyDomainService</span> <span class="n">service</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">fetchData</span><span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="kd">final</span> <span class="n">data</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">service</span><span class="p">.</span><span class="n">getData</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="q4-多個-domain-模型如何整合到一個-viewmodel">Q4: 多個 Domain 模型如何整合到一個 ViewModel？</h3>
<p><strong>A</strong>: 在 Mapper 中整合多個來源。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">class</span> <span class="nc">MyMapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="kd">static</span> <span class="n">MyViewModel</span> <span class="n">toViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">DomainModel1</span> <span class="n">model1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">DomainModel2</span> <span class="n">model2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">List</span><span class="o">&lt;</span><span class="n">Entity</span><span class="o">&gt;</span> <span class="n">entities</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">MyViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="nl">field1:</span> <span class="n">model1</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">      <span class="nl">field2:</span> <span class="n">model2</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="nl">items:</span> <span class="n">entities</span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">_mapEntity</span><span class="p">).</span><span class="n">toList</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div>]]></content:encoded></item><item><title>Ticket 拆分標準方法論</title><link>https://tarrragon.github.io/blog/record/ticket-%E6%8B%86%E5%88%86%E6%A8%99%E6%BA%96%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/ticket-%E6%8B%86%E5%88%86%E6%A8%99%E6%BA%96%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="方法論概述">方法論概述&lt;/h2>
&lt;h3 id="為什麼需要-ticket-拆分標準">為什麼需要 Ticket 拆分標準&lt;/h3>
&lt;p>&lt;strong>問題背景&lt;/strong>:&lt;/p>
&lt;p>在大型軟體開發專案中，我們經常面臨以下挑戰：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>God Ticket 問題&lt;/strong>：單一任務過於龐大，包含數十個檔案修改，跨越多個架構層級&lt;/li>
&lt;li>&lt;strong>主觀判斷困境&lt;/strong>：不同開發者對「任務大小」的理解不同，導致協作效率低&lt;/li>
&lt;li>&lt;strong>進度追蹤困難&lt;/strong>：任務粒度不一致，難以準確評估完成度&lt;/li>
&lt;li>&lt;strong>風險控制失衡&lt;/strong>：大任務失敗影響大，小任務過碎增加管理成本&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>傳統拆分方法的問題&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>經驗導向&lt;/strong>：依賴個人經驗判斷，缺乏客觀標準&lt;/li>
&lt;li>&lt;strong>時間估算&lt;/strong>：受個人能力和環境影響，難以標準化&lt;/li>
&lt;li>&lt;strong>模糊描述&lt;/strong>：「不要太大」「保持適中」等描述無法執行&lt;/li>
&lt;li>&lt;strong>後驗調整&lt;/strong>：任務執行後才發現過大，增加返工成本&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h3 id="核心目標">核心目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>標準化任務拆分&lt;/strong>：提供統一的拆分標準，消除主觀判斷&lt;/li>
&lt;li>&lt;strong>控制任務複雜度&lt;/strong>：確保每個 Ticket 在可控範圍內&lt;/li>
&lt;li>&lt;strong>提升協作效率&lt;/strong>：明確的任務邊界，減少溝通成本&lt;/li>
&lt;li>&lt;strong>及早風險管控&lt;/strong>：設計階段就識別過大任務，降低執行風險&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="與其他方法論的關係">與其他方法論的關係&lt;/h3>
&lt;p>&lt;strong>本方法論的定位&lt;/strong>:&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Ticket 設計派工方法論 (主方法論)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── Ticket 拆分標準方法論 ← 你正在閱讀
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ └── 提供量化拆分標準和決策樹
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── Code Smell 品質閘門檢測方法論
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">│ └── 提供設計階段的品質檢測
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">├── Ticket 生命週期管理方法論
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">│ └── 提供 Ticket 執行流程管理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">└── 即時 Review 機制方法論
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> └── 提供執行過程中的 Review 機制&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="第一章量化指標體系">第一章：量化指標體系&lt;/h2>
&lt;h3 id="11-指標-1職責數量responsibilities-最優先">1.1 指標 1：職責數量（Responsibilities） 最優先&lt;/h3>
&lt;p>&lt;strong>定義&lt;/strong>：Ticket 需要完成的獨立職責數量。&lt;/p>
&lt;p>&lt;strong>為什麼職責是第一指標&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>最客觀&lt;/strong>：不受個人能力影響&lt;/li>
&lt;li>&lt;strong>最穩定&lt;/strong>：不受環境和參考資料影響&lt;/li>
&lt;li>&lt;strong>最易溝通&lt;/strong>：PM 和工程師都能理解&lt;/li>
&lt;li>&lt;strong>最精確&lt;/strong>：直接對應業務需求&lt;/li>
&lt;/ul>
&lt;h4 id="職責的精確定義">職責的精確定義&lt;/h4>
&lt;p>&lt;strong>什麼算一個職責&lt;/strong>：&lt;/p>
&lt;p>一個職責 = 一個明確可驗證的功能點或邊界條件&lt;/p>
&lt;p>&lt;strong>識別方式&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>每個「需要實作的功能點」算一個職責&lt;/li>
&lt;li>每個「需要驗證的邊界條件」算一個職責&lt;/li>
&lt;li>每個「需要處理的錯誤情境」算一個職責&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>範例說明&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">範例任務：實作書籍評分功能
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">職責識別：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">1.&lt;/span> 定義 Rating Value Object（數值範圍驗證） ← 職責 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">2.&lt;/span> 定義 Rating Entity（包含評分和評論） ← 職責 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">3.&lt;/span> 實作 IRatingRepository 介面 ← 職責 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">4.&lt;/span> 實作評分儲存邏輯 ← 職責 4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">5.&lt;/span> 處理無效評分錯誤 ← 職責 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">6.&lt;/span> 處理資料庫錯誤 ← 職責 6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">總計：6 個職責 → 超過 5 個，必須拆分&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="職責數量標準">職責數量標準&lt;/h4>
&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;strong>簡單 Ticket&lt;/strong>&lt;/td>
 &lt;td>1 個&lt;/td>
 &lt;td>理想&lt;/td>
 &lt;td>單一職責，最易管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>中等 Ticket&lt;/strong>&lt;/td>
 &lt;td>2-3 個&lt;/td>
 &lt;td>可接受&lt;/td>
 &lt;td>少數相關職責，可控範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>複雜 Ticket&lt;/strong>&lt;/td>
 &lt;td>3-5 個&lt;/td>
 &lt;td>需檢查&lt;/td>
 &lt;td>多職責，建議拆分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>必須拆分&lt;/strong>&lt;/td>
 &lt;td>&amp;gt; 5 個&lt;/td>
 &lt;td>禁止&lt;/td>
 &lt;td>範圍失控，必須拆分&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>強制規則&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>超過 5 個職責 = 必須拆分&lt;/strong>，無例外&lt;/li>
&lt;li>&lt;strong>3-5 個職責 = 評估是否可拆分&lt;/strong>，優先拆分&lt;/li>
&lt;li>&lt;strong>1-2 個職責 = 理想狀態&lt;/strong>，鼓勵維持&lt;/li>
&lt;/ul>
&lt;h4 id="職責識別實例">職責識別實例&lt;/h4>
&lt;h5 id="實例-1簡單-ticket1-職責">實例 1：簡單 Ticket（1 職責）&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">任務：定義 SelectionManager 介面方法簽名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">職責分析：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">職責 1：定義 toggleSelection、clearSelection、getSelectedIds 三個方法簽名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">總計：1 個職責
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">判定：簡單 Ticket&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h6 id="實例-2中等-ticket2-3-職責">實例 2：中等 Ticket（2-3 職責）&lt;/h6>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">任務：實作 SelectionManager 基礎功能
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">職責分析：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">職責 1：實作 toggleSelection 方法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">職責 2：實作 clearSelection 方法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">職責 3：實作 ChangeNotifier 通知機制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">總計：3 個職責
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">判定：中等 Ticket（可接受）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h6 id="實例-3複雜-ticket3-5-職責--建議拆分">實例 3：複雜 Ticket（3-5 職責）- 建議拆分&lt;/h6>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">任務：實作完整的 BookRepository
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">職責分析：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">職責 1：實作 getBookByIsbn CRUD 方法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">職責 2：實作 saveBook CRUD 方法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">職責 3：實作 Data Mapper 轉換
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">職責 4：實作錯誤處理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">職責 5：實作 Cache 管理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">總計：5 個職責
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">判定：複雜 Ticket（建議拆分為 2-3 個 Ticket）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h6 id="實例-4必須拆分-5-職責">實例 4：必須拆分（&amp;gt; 5 職責）&lt;/h6>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">任務：實作書籍評分完整功能（包含 UI、UseCase、Repository）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">職責分析：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">職責 1：定義 Rating Value Object
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">職責 2：定義 Rating Entity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">職責 3：建立 RatingWidget UI
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">職責 4：實作 RateBookUseCase
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">職責 5：定義 IRatingRepository
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">職責 6：實作 SQLiteRatingRepository
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">職責 7：處理各種異常情境
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">職責 8：撰寫完整測試
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">總計：8 個職責
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">判定：God Ticket 必須拆分&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>拆分建議&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<h2 id="方法論概述">方法論概述</h2>
<h3 id="為什麼需要-ticket-拆分標準">為什麼需要 Ticket 拆分標準</h3>
<p><strong>問題背景</strong>:</p>
<p>在大型軟體開發專案中，我們經常面臨以下挑戰：</p>
<ol>
<li><strong>God Ticket 問題</strong>：單一任務過於龐大，包含數十個檔案修改，跨越多個架構層級</li>
<li><strong>主觀判斷困境</strong>：不同開發者對「任務大小」的理解不同，導致協作效率低</li>
<li><strong>進度追蹤困難</strong>：任務粒度不一致，難以準確評估完成度</li>
<li><strong>風險控制失衡</strong>：大任務失敗影響大，小任務過碎增加管理成本</li>
</ol>
<p><strong>傳統拆分方法的問題</strong>:</p>
<ul>
<li><strong>經驗導向</strong>：依賴個人經驗判斷，缺乏客觀標準</li>
<li><strong>時間估算</strong>：受個人能力和環境影響，難以標準化</li>
<li><strong>模糊描述</strong>：「不要太大」「保持適中」等描述無法執行</li>
<li><strong>後驗調整</strong>：任務執行後才發現過大，增加返工成本</li>
</ul>
<hr>
<h3 id="核心目標">核心目標</h3>
<ol>
<li><strong>標準化任務拆分</strong>：提供統一的拆分標準，消除主觀判斷</li>
<li><strong>控制任務複雜度</strong>：確保每個 Ticket 在可控範圍內</li>
<li><strong>提升協作效率</strong>：明確的任務邊界，減少溝通成本</li>
<li><strong>及早風險管控</strong>：設計階段就識別過大任務，降低執行風險</li>
</ol>
<hr>
<h3 id="與其他方法論的關係">與其他方法論的關係</h3>
<p><strong>本方法論的定位</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Ticket 設計派工方法論 (主方法論)
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── Ticket 拆分標準方法論 ← 你正在閱讀
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   └── 提供量化拆分標準和決策樹
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── Code Smell 品質閘門檢測方法論
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   └── 提供設計階段的品質檢測
</span></span><span class="line"><span class="ln">6</span><span class="cl">├── Ticket 生命週期管理方法論
</span></span><span class="line"><span class="ln">7</span><span class="cl">│   └── 提供 Ticket 執行流程管理
</span></span><span class="line"><span class="ln">8</span><span class="cl">└── 即時 Review 機制方法論
</span></span><span class="line"><span class="ln">9</span><span class="cl">    └── 提供執行過程中的 Review 機制</span></span></code></pre></div><hr>
<h2 id="第一章量化指標體系">第一章：量化指標體系</h2>
<h3 id="11-指標-1職責數量responsibilities-最優先">1.1 指標 1：職責數量（Responsibilities） 最優先</h3>
<p><strong>定義</strong>：Ticket 需要完成的獨立職責數量。</p>
<p><strong>為什麼職責是第一指標</strong>：</p>
<ul>
<li><strong>最客觀</strong>：不受個人能力影響</li>
<li><strong>最穩定</strong>：不受環境和參考資料影響</li>
<li><strong>最易溝通</strong>：PM 和工程師都能理解</li>
<li><strong>最精確</strong>：直接對應業務需求</li>
</ul>
<h4 id="職責的精確定義">職責的精確定義</h4>
<p><strong>什麼算一個職責</strong>：</p>
<p>一個職責 = 一個明確可驗證的功能點或邊界條件</p>
<p><strong>識別方式</strong>：</p>
<ul>
<li>每個「需要實作的功能點」算一個職責</li>
<li>每個「需要驗證的邊界條件」算一個職責</li>
<li>每個「需要處理的錯誤情境」算一個職責</li>
</ul>
<p><strong>範例說明</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">範例任務：實作書籍評分功能
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">職責識別：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">1.</span> 定義 Rating Value Object（數值範圍驗證）      ← 職責 1
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">2.</span> 定義 Rating Entity（包含評分和評論）         ← 職責 2
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">3.</span> 實作 IRatingRepository 介面                  ← 職責 3
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">4.</span> 實作評分儲存邏輯                              ← 職責 4
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">5.</span> 處理無效評分錯誤                              ← 職責 5
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">6.</span> 處理資料庫錯誤                                ← 職責 6
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">總計：6 個職責 → 超過 5 個，必須拆分</span></span></code></pre></div><h4 id="職責數量標準">職責數量標準</h4>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>職責數量</th>
          <th>判定</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>簡單 Ticket</strong></td>
          <td>1 個</td>
          <td>理想</td>
          <td>單一職責，最易管理</td>
      </tr>
      <tr>
          <td><strong>中等 Ticket</strong></td>
          <td>2-3 個</td>
          <td>可接受</td>
          <td>少數相關職責，可控範圍</td>
      </tr>
      <tr>
          <td><strong>複雜 Ticket</strong></td>
          <td>3-5 個</td>
          <td>需檢查</td>
          <td>多職責，建議拆分</td>
      </tr>
      <tr>
          <td><strong>必須拆分</strong></td>
          <td>&gt; 5 個</td>
          <td>禁止</td>
          <td>範圍失控，必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>強制規則</strong>：</p>
<ul>
<li><strong>超過 5 個職責 = 必須拆分</strong>，無例外</li>
<li><strong>3-5 個職責 = 評估是否可拆分</strong>，優先拆分</li>
<li><strong>1-2 個職責 = 理想狀態</strong>，鼓勵維持</li>
</ul>
<h4 id="職責識別實例">職責識別實例</h4>
<h5 id="實例-1簡單-ticket1-職責">實例 1：簡單 Ticket（1 職責）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">任務：定義 SelectionManager 介面方法簽名
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">職責分析：
</span></span><span class="line"><span class="ln">4</span><span class="cl">職責 1：定義 toggleSelection、clearSelection、getSelectedIds 三個方法簽名
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">總計：1 個職責
</span></span><span class="line"><span class="ln">7</span><span class="cl">判定：簡單 Ticket</span></span></code></pre></div><h6 id="實例-2中等-ticket2-3-職責">實例 2：中等 Ticket（2-3 職責）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">任務：實作 SelectionManager 基礎功能
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">職責分析：
</span></span><span class="line"><span class="ln">4</span><span class="cl">職責 1：實作 toggleSelection 方法
</span></span><span class="line"><span class="ln">5</span><span class="cl">職責 2：實作 clearSelection 方法
</span></span><span class="line"><span class="ln">6</span><span class="cl">職責 3：實作 ChangeNotifier 通知機制
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">總計：3 個職責
</span></span><span class="line"><span class="ln">9</span><span class="cl">判定：中等 Ticket（可接受）</span></span></code></pre></div><h6 id="實例-3複雜-ticket3-5-職責--建議拆分">實例 3：複雜 Ticket（3-5 職責）- 建議拆分</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作完整的 BookRepository
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">職責分析：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">職責 1：實作 getBookByIsbn CRUD 方法
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">職責 2：實作 saveBook CRUD 方法
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">職責 3：實作 Data Mapper 轉換
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">職責 4：實作錯誤處理
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">職責 5：實作 Cache 管理
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">總計：5 個職責
</span></span><span class="line"><span class="ln">11</span><span class="cl">判定：複雜 Ticket（建議拆分為 2-3 個 Ticket）</span></span></code></pre></div><h6 id="實例-4必須拆分-5-職責">實例 4：必須拆分（&gt; 5 職責）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作書籍評分完整功能（包含 UI、UseCase、Repository）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">職責分析：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">職責 1：定義 Rating Value Object
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">職責 2：定義 Rating Entity
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">職責 3：建立 RatingWidget UI
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">職責 4：實作 RateBookUseCase
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">職責 5：定義 IRatingRepository
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">職責 6：實作 SQLiteRatingRepository
</span></span><span class="line"><span class="ln">10</span><span class="cl">職責 7：處理各種異常情境
</span></span><span class="line"><span class="ln">11</span><span class="cl">職責 8：撰寫完整測試
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">總計：8 個職責
</span></span><span class="line"><span class="ln">14</span><span class="cl">判定：God Ticket 必須拆分</span></span></code></pre></div><p><strong>拆分建議</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">拆分為 4 個 Ticket：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket 1: 定義 Rating Domain 模型（職責 1, 2）
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket 2: 實作 RatingRepository（職責 5, 6）
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Ticket 3: 實作 RateBookUseCase（職責 4, 7）
</span></span><span class="line"><span class="ln">6</span><span class="cl">- Ticket 4: 實作 RatingWidget UI（職責 3, 8）</span></span></code></pre></div><hr>
<h3 id="12-指標-2程式碼行數lines-of-code">1.2 指標 2：程式碼行數（Lines of Code）</h3>
<p><strong>定義</strong>：Ticket 涉及的程式碼修改行數（新增 + 修改 + 刪除）。</p>
<p><strong>為什麼需要行數指標</strong>：</p>
<ul>
<li><strong>可量化</strong>：使用 <code>git diff --stat</code> 精確統計</li>
<li><strong>可驗證</strong>：執行後可驗證預估準確性</li>
<li><strong>風險指標</strong>：行數越多，出錯風險越高</li>
</ul>
<h4 id="行數統計標準">行數統計標準</h4>
<p><strong>測量方式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 統計修改行數</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">git diff --stat
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 範例輸出</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">lib/domain/entities/book.dart        <span class="p">|</span> <span class="m">25</span> +++++++++++++
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">lib/domain/repositories/i_repo.dart  <span class="p">|</span> <span class="m">15</span> ++++++--
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">test/unit/domain/book_test.dart      <span class="p">|</span> <span class="m">40</span> ++++++++++++++++++++
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="m">3</span> files changed, <span class="m">78</span> insertions<span class="o">(</span>+<span class="o">)</span>, <span class="m">2</span> deletions<span class="o">(</span>-<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 計算方式</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nv">總行數</span> <span class="o">=</span> 新增行數 + 修改行數 + <span class="nv">刪除行數</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="o">=</span> <span class="m">25</span> + <span class="m">15</span> + <span class="nv">40</span> <span class="o">=</span> <span class="m">80</span> 行</span></span></code></pre></div><p><strong>計算規則</strong>：</p>
<ul>
<li>包含新增行數</li>
<li>包含修改行數</li>
<li>包含刪除行數</li>
<li>不包含空行（pure whitespace changes）</li>
<li>不包含純註解行（comment-only changes）</li>
</ul>
<h4 id="行數標準">行數標準</h4>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>行數範圍</th>
          <th>判定</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>簡單 Ticket</strong></td>
          <td>&lt; 30 行</td>
          <td>理想</td>
          <td>Interface 定義、簡單 Value Object</td>
      </tr>
      <tr>
          <td><strong>中等 Ticket</strong></td>
          <td>30-70 行</td>
          <td>可接受</td>
          <td>中等實作、含測試</td>
      </tr>
      <tr>
          <td><strong>複雜 Ticket</strong></td>
          <td>70-100 行</td>
          <td>需檢查</td>
          <td>複雜實作、多測試案例</td>
      </tr>
      <tr>
          <td><strong>必須拆分</strong></td>
          <td>&gt; 100 行</td>
          <td>禁止</td>
          <td>範圍過大，必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>強制規則</strong>：</p>
<ul>
<li><strong>超過 100 行 = 必須拆分</strong></li>
<li><strong>70-100 行 = 評估是否可拆分</strong></li>
<li><strong>&lt; 70 行 = 可接受範圍</strong></li>
</ul>
<h4 id="行數估算實例">行數估算實例</h4>
<h5 id="實例-1簡單-ticket-30-行">實例 1：簡單 Ticket（&lt; 30 行）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// lib/domain/repositories/i_book_repository.dart
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="c1">/// 根據 ISBN 取得書籍
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">?&gt;</span> <span class="n">getBookByIsbn</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="c1">/// 儲存書籍
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">saveBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="c1">/// 刪除書籍
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">deleteBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// 預估行數：~20 行（含註解）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：簡單</span> <span class="n">Ticket</span></span></span></code></pre></div><h6 id="實例-2中等-ticket30-70-行">實例 2：中等 Ticket（30-70 行）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：實作 Rating Value Object
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// lib/domain/value_objects/rating.dart + test
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// rating.dart (~40 行)
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Rating</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="kt">int</span> <span class="n">value</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="n">Rating</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">&lt;</span> <span class="m">1</span> <span class="o">||</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="m">5</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">.</span><span class="n">invalidRating</span><span class="p">(</span><span class="n">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="c1">// ... 其他方法（equals, hashCode, toString）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// rating_test.dart (~25 行)
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="n">test</span><span class="p">(</span><span class="s1">&#39;建立有效評分&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過低拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過高拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1">// 總計：~65 行
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：中等</span> <span class="n">Ticket</span></span></span></code></pre></div><h6 id="實例-3複雜-ticket70-100-行--建議拆分">實例 3：複雜 Ticket（70-100 行）- 建議拆分</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：實作完整的 BookRepository
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// lib/infrastructure/repositories/sqlite_book_repository.dart + test
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// repository.dart (~80 行)
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">SQLiteBookRepository</span> <span class="kd">implements</span> <span class="n">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="c1">// ... 建構子和欄位定義
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">?&gt;</span> <span class="n">getBookByIsbn</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1">// ... SQL 查詢邏輯（~15 行）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>    <span class="c1">// ... Data Mapper 轉換（~10 行）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>    <span class="c1">// ... 錯誤處理（~5 行）
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">saveBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1">// ... SQL 插入/更新邏輯（~20 行）
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>    <span class="c1">// ... Data Mapper 轉換（~10 行）
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span>    <span class="c1">// ... 錯誤處理（~5 行）
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">deleteBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="c1">// ... SQL 刪除邏輯（~10 行）
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span>    <span class="c1">// ... 錯誤處理（~5 行）
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1">// repository_test.dart (~80 行)
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1">// ... 8 個測試案例
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1">// 總計：~160 行
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：超過</span> <span class="m">100</span> <span class="err">行，必須拆分</span></span></span></code></pre></div><p><strong>拆分建議</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">拆分為 3 個 Ticket：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket 1: 定義 IBookRepository 介面（~20 行）
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket 2: 實作 getBookByIsbn + 測試（~60 行）
</span></span><span class="line"><span class="ln">5</span><span class="cl">- Ticket 3: 實作 saveBook + deleteBook + 測試（~80 行）</span></span></code></pre></div><hr>
<h3 id="13-指標-3涉及檔案數files">1.3 指標 3：涉及檔案數（Files）</h3>
<p><strong>定義</strong>：Ticket 需要修改的檔案數量（不含測試檔案）。</p>
<p><strong>為什麼需要檔案數指標</strong>：</p>
<ul>
<li><strong>架構層級指標</strong>：檔案數反映任務的架構範圍</li>
<li><strong>風險管控</strong>：修改越多檔案，影響範圍越大</li>
<li><strong>測試複雜度</strong>：檔案數越多，測試整合越複雜</li>
</ul>
<h4 id="檔案統計標準">檔案統計標準</h4>
<p><strong>測量方式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 統計修改檔案數</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">git diff --name-only <span class="p">|</span> grep -v <span class="s1">&#39;_test\.dart$&#39;</span> <span class="p">|</span> wc -l
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 範例輸出</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">lib/domain/entities/book.dart
</span></span><span class="line"><span class="ln">6</span><span class="cl">lib/domain/repositories/i_book_repository.dart
</span></span><span class="line"><span class="ln">7</span><span class="cl">lib/infrastructure/repositories/sqlite_book_repository.dart
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"># 結果：3 個檔案（不含測試）</span></span></span></code></pre></div><p><strong>計算規則</strong>：</p>
<ul>
<li>包含新建檔案</li>
<li>包含修改檔案</li>
<li>包含刪除檔案</li>
<li>不包含測試檔案（測試檔案另計為「測試數量」指標）</li>
<li>不包含配置檔案（如 pubspec.yaml, analysis_options.yaml）</li>
</ul>
<h4 id="檔案數標準">檔案數標準</h4>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>檔案數量</th>
          <th>判定</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>簡單 Ticket</strong></td>
          <td>1 個</td>
          <td>理想</td>
          <td>單一檔案修改</td>
      </tr>
      <tr>
          <td><strong>中等 Ticket</strong></td>
          <td>2-3 個</td>
          <td>可接受</td>
          <td>相關檔案修改</td>
      </tr>
      <tr>
          <td><strong>複雜 Ticket</strong></td>
          <td>3-5 個</td>
          <td>需檢查</td>
          <td>多檔案修改，建議拆分</td>
      </tr>
      <tr>
          <td><strong>必須拆分</strong></td>
          <td>&gt; 5 個</td>
          <td>禁止</td>
          <td>範圍過大，必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>強制規則</strong>：</p>
<ul>
<li><strong>超過 5 個檔案 = 必須拆分</strong></li>
<li><strong>3-5 個檔案 = 評估是否可拆分</strong></li>
<li><strong>1-2 個檔案 = 理想狀態</strong></li>
</ul>
<h4 id="檔案數實例">檔案數實例</h4>
<h5 id="實例-1簡單-ticket1-個檔案">實例 1：簡單 Ticket（1 個檔案）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">任務：建立 Rating Value Object
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">涉及檔案：
</span></span><span class="line"><span class="ln">4</span><span class="cl">lib/domain/value_objects/rating.dart
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">總計：1 個檔案
</span></span><span class="line"><span class="ln">7</span><span class="cl">判定：簡單 Ticket</span></span></code></pre></div><h6 id="實例-2中等-ticket2-3-個檔案">實例 2：中等 Ticket（2-3 個檔案）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">任務：更新 Book Entity 增加評分欄位
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">涉及檔案：
</span></span><span class="line"><span class="ln">4</span><span class="cl">lib/domain/entities/book.dart         （修改）
</span></span><span class="line"><span class="ln">5</span><span class="cl">lib/domain/value_objects/rating.dart  （新增）
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">總計：2 個檔案
</span></span><span class="line"><span class="ln">8</span><span class="cl">判定：中等 Ticket</span></span></code></pre></div><h6 id="實例-3複雜-ticket3-5-個檔案--建議拆分">實例 3：複雜 Ticket（3-5 個檔案）- 建議拆分</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作完整的書籍評分功能
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">涉及檔案：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">lib/domain/entities/rating.dart
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">lib/domain/entities/book.dart
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">lib/domain/repositories/i_rating_repository.dart
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">lib/infrastructure/repositories/sqlite_rating_repository.dart
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">lib/infrastructure/mappers/rating_mapper.dart
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">總計：5 個檔案
</span></span><span class="line"><span class="ln">11</span><span class="cl">判定：複雜 Ticket（建議拆分）</span></span></code></pre></div><h6 id="實例-4必須拆分-5-個檔案">實例 4：必須拆分（&gt; 5 個檔案）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作評分功能（含 UI、UseCase、Repository）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">涉及檔案：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">lib/presentation/widgets/rating_widget.dart
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">lib/presentation/controllers/rating_controller.dart
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">lib/application/use_cases/rate_book_use_case.dart
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">lib/domain/entities/rating.dart
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">lib/domain/entities/book.dart
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">lib/domain/repositories/i_rating_repository.dart
</span></span><span class="line"><span class="ln">10</span><span class="cl">lib/infrastructure/repositories/sqlite_rating_repository.dart
</span></span><span class="line"><span class="ln">11</span><span class="cl">lib/infrastructure/mappers/rating_mapper.dart
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">總計：8 個檔案
</span></span><span class="line"><span class="ln">14</span><span class="cl">判定：God Ticket，必須拆分</span></span></code></pre></div><p><strong>拆分建議</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">按 Clean Architecture 分層拆分為 4 個 Ticket：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket 1: Domain 層（檔案 4, 5, 6）             - 3 個檔案
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket 2: Infrastructure 層（檔案 7, 8）        - 2 個檔案
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Ticket 3: Application 層（檔案 3）              - 1 個檔案
</span></span><span class="line"><span class="ln">6</span><span class="cl">- Ticket 4: Presentation 層（檔案 1, 2）          - 2 個檔案</span></span></code></pre></div><hr>
<h3 id="14-指標-4測試用例數tests">1.4 指標 4：測試用例數（Tests）</h3>
<p><strong>定義</strong>：Ticket 對應的測試用例數量。</p>
<p><strong>為什麼需要測試數量指標</strong>：</p>
<ul>
<li><strong>品質保證指標</strong>：測試數量反映功能複雜度</li>
<li><strong>執行時間預估</strong>：測試數量影響 TDD 循環時間</li>
<li><strong>維護成本</strong>：過多測試增加維護負擔</li>
</ul>
<h4 id="測試統計標準">測試統計標準</h4>
<p><strong>測量方式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 計算測試方法數
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;Rating Value Object&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;建立有效評分&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>           <span class="c1">// 測試 1
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過低拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>        <span class="c1">// 測試 2
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過高拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>        <span class="c1">// 測試 3
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;相同評分視為相等&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>        <span class="c1">// 測試 4
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="o">//</span> <span class="err">總計：</span><span class="m">4</span> <span class="err">個測試</span></span></span></code></pre></div><p><strong>計算規則</strong>：</p>
<ul>
<li>每個 <code>test('...', () {...})</code> 算一個測試</li>
<li>每個 <code>testWidgets('...', () {...})</code> 算一個測試</li>
<li>包含單元測試和整合測試</li>
<li>不包含 <code>group()</code> 本身（只是測試組織）</li>
</ul>
<h4 id="測試數量標準">測試數量標準</h4>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>測試數量</th>
          <th>判定</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>簡單 Ticket</strong></td>
          <td>1-3 個</td>
          <td>理想</td>
          <td>基本功能測試</td>
      </tr>
      <tr>
          <td><strong>中等 Ticket</strong></td>
          <td>3-6 個</td>
          <td>可接受</td>
          <td>含邊界和異常測試</td>
      </tr>
      <tr>
          <td><strong>複雜 Ticket</strong></td>
          <td>6-10 個</td>
          <td>需檢查</td>
          <td>複雜邏輯，多測試案例</td>
      </tr>
      <tr>
          <td><strong>必須拆分</strong></td>
          <td>&gt; 10 個</td>
          <td>禁止</td>
          <td>測試過多，必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>強制規則</strong>：</p>
<ul>
<li><strong>超過 10 個測試 = 必須拆分</strong></li>
<li><strong>6-10 個測試 = 評估是否可拆分</strong></li>
<li><strong>1-6 個測試 = 可接受範圍</strong></li>
</ul>
<h4 id="測試數量實例">測試數量實例</h4>
<h5 id="實例-1簡單-ticket1-3-個測試">實例 1：簡單 Ticket（1-3 個測試）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：定義 Rating Value Object
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;Rating Value Object&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;建立有效評分&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="kd">final</span> <span class="n">rating</span> <span class="o">=</span> <span class="n">Rating</span><span class="p">(</span><span class="m">4</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="n">expect</span><span class="p">(</span><span class="n">rating</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="m">4</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過低拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="n">expect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="n">Rating</span><span class="p">(</span><span class="m">0</span><span class="p">),</span> <span class="n">throwsA</span><span class="p">(</span><span class="n">isA</span><span class="o">&lt;</span><span class="n">ValidationException</span><span class="o">&gt;</span><span class="p">()));</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;評分過高拋出異常&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">      <span class="n">expect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="n">Rating</span><span class="p">(</span><span class="m">6</span><span class="p">),</span> <span class="n">throwsA</span><span class="p">(</span><span class="n">isA</span><span class="o">&lt;</span><span class="n">ValidationException</span><span class="o">&gt;</span><span class="p">()));</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1">// 總計：3 個測試
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：簡單</span> <span class="n">Ticket</span></span></span></code></pre></div><h6 id="實例-2中等-ticket3-6-個測試">實例 2：中等 Ticket（3-6 個測試）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：實作 BookRepository.getBookByIsbn
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;BookRepository.getBookByIsbn&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;成功取得存在的書籍&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>          <span class="c1">// 測試 1
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍不存在回傳 null&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>          <span class="c1">// 測試 2
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;無效 ISBN 拋出 ValidationException&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span> <span class="c1">// 測試 3
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;資料庫錯誤拋出 StorageException&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>    <span class="c1">// 測試 4
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;資料庫連線失敗拋出 NetworkException&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span> <span class="c1">// 測試 5
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// 總計：5 個測試
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：中等</span> <span class="n">Ticket</span></span></span></code></pre></div><h6 id="實例-3複雜-ticket6-10-個測試--建議拆分">實例 3：複雜 Ticket（6-10 個測試）- 建議拆分</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 任務：實作完整的 BookRepository CRUD
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kt">void</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">group</span><span class="p">(</span><span class="s1">&#39;BookRepository CRUD&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1">// getBookByIsbn 測試
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;取得存在的書籍&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>              <span class="c1">// 測試 1
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;書籍不存在回傳 null&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>         <span class="c1">// 測試 2
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">// saveBook 測試
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;新增書籍成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>                <span class="c1">// 測試 3
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;更新書籍成功&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>                <span class="c1">// 測試 4
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;儲存時資料庫錯誤&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>            <span class="c1">// 測試 5
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1">// deleteBook 測試
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;刪除存在的書籍&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>              <span class="c1">// 測試 6
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;刪除不存在的書籍&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>            <span class="c1">// 測試 7
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;刪除時資料庫錯誤&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>            <span class="c1">// 測試 8
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1">// Data Mapper 測試
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;Entity 轉 DTO 正確&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>          <span class="c1">// 測試 9
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>    <span class="n">test</span><span class="p">(</span><span class="s1">&#39;DTO 轉 Entity 正確&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="p">});</span>          <span class="c1">// 測試 10
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">// 總計：10 個測試
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="err">判定：複雜</span> <span class="n">Ticket</span><span class="err">（達上限，建議拆分）</span></span></span></code></pre></div><p><strong>拆分建議</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">拆分為 3 個 Ticket：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket 1: getBookByIsbn + 測試（2 個測試）    
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket 2: saveBook + 測試（3 個測試）         
</span></span><span class="line"><span class="ln">5</span><span class="cl">- Ticket 3: deleteBook + Mapper + 測試（5 個測試）</span></span></code></pre></div><hr>
<h3 id="15-指標整合評估方法">1.5 指標整合評估方法</h3>
<h4 id="整合評估原則">整合評估原則</h4>
<p><strong>最高等級原則</strong>：</p>
<ul>
<li>取 4 個指標中「最高的複雜度等級」作為最終評估結果</li>
<li>任一指標達到「必須拆分」，則整個 Ticket 必須拆分</li>
</ul>
<p><strong>評估公式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Ticket 複雜度 = max(職責複雜度, 行數複雜度, 檔案數複雜度, 測試數複雜度)
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">where 複雜度等級：
</span></span><span class="line"><span class="ln">4</span><span class="cl">  簡單 = 1
</span></span><span class="line"><span class="ln">5</span><span class="cl">  中等 = 2
</span></span><span class="line"><span class="ln">6</span><span class="cl">  複雜 = 3
</span></span><span class="line"><span class="ln">7</span><span class="cl">  必須拆分 = 4</span></span></code></pre></div><h4 id="整合評估實例">整合評估實例</h4>
<h5 id="實例-1所有指標都是簡單--簡單-ticket">實例 1：所有指標都是簡單 → 簡單 Ticket</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：定義 Rating Value Object
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">指標評估：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責數量：1 個職責 → 簡單
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 程式碼行數：25 行 → 簡單
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個檔案 → 簡單
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試用例：3 個測試 → 簡單
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">最終判定：簡單 Ticket（最理想狀態）</span></span></code></pre></div><h6 id="實例-2有一個指標是中等--中等-ticket">實例 2：有一個指標是中等 → 中等 Ticket</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作 Rating Value Object
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">指標評估：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責數量：2 個職責（建立、驗證）→ 中等
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 程式碼行數：45 行 → 中等
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個檔案 → 簡單
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試用例：5 個測試 → 中等
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">最終判定：中等 Ticket（可接受）</span></span></code></pre></div><h6 id="實例-3有一個指標是複雜--複雜-ticket建議拆分">實例 3：有一個指標是複雜 → 複雜 Ticket（建議拆分）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作 BookRepository.getBookByIsbn
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">指標評估：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責數量：3 個職責（查詢、轉換、異常處理）→ 複雜
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 程式碼行數：65 行 → 中等
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 涉及檔案：2 個檔案 → 中等
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試用例：6 個測試 → 複雜
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">最終判定：複雜 Ticket（建議拆分）</span></span></code></pre></div><h6 id="實例-4有任一指標超標--必須拆分">實例 4：有任一指標超標 → 必須拆分</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作完整的書籍評分功能
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">指標評估：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責數量：8 個職責 → 必須拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 程式碼行數：180 行 → 必須拆分
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 涉及檔案：7 個檔案 → 必須拆分
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試用例：15 個測試 → 必須拆分
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">最終判定：God Ticket 必須立即拆分</span></span></code></pre></div><h4 id="評估決策流程">評估決策流程</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">步驟 1：計算 4 個指標
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">步驟 2：取最高複雜度等級
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">    ├─ 簡單 → 可直接建立 Ticket
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ├─ 中等 → 可直接建立 Ticket（可選：評估是否拆分為更小 Ticket）
</span></span><span class="line"><span class="ln">7</span><span class="cl">    ├─ 複雜 → 強烈建議拆分（可選：無法拆分時勉強接受）
</span></span><span class="line"><span class="ln">8</span><span class="cl">    └─ 必須拆分 → 阻止建立，必須先拆分再重新評估</span></span></code></pre></div><p><strong>評估檢查清單</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已計算 4 個指標的值
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已確定每個指標的複雜度等級
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已取最高複雜度等級作為最終判定
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 如為「必須拆分」，已執行拆分並重新評估
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 如為「複雜」，已評估是否可拆分為更小 Ticket</span></span></code></pre></div><hr>
<h2 id="第二章複雜度評估方法">第二章：複雜度評估方法</h2>
<h3 id="21-複雜度等級定義">2.1 複雜度等級定義</h3>
<p><strong>4 級複雜度體系</strong>：</p>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>職責</th>
          <th>行數</th>
          <th>檔案</th>
          <th>測試</th>
          <th>描述</th>
          <th>處理方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Level 1: 簡單</strong></td>
          <td>1 個</td>
          <td>&lt; 30 行</td>
          <td>1 個</td>
          <td>1-3 個</td>
          <td>單一職責，單一檔案</td>
          <td>直接建立 Ticket</td>
      </tr>
      <tr>
          <td><strong>Level 2: 中等</strong></td>
          <td>2-3 個</td>
          <td>30-70 行</td>
          <td>2-3 個</td>
          <td>3-6 個</td>
          <td>少數相關職責，少數檔案</td>
          <td>直接建立 Ticket</td>
      </tr>
      <tr>
          <td><strong>Level 3: 複雜</strong></td>
          <td>3-5 個</td>
          <td>70-100 行</td>
          <td>3-5 個</td>
          <td>6-10 個</td>
          <td>多職責，多檔案</td>
          <td>建議拆分</td>
      </tr>
      <tr>
          <td><strong>Level 4: 超標</strong></td>
          <td>&gt; 5 個</td>
          <td>&gt; 100 行</td>
          <td>&gt; 5 個</td>
          <td>&gt; 10 個</td>
          <td>範圍失控</td>
          <td>必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>複雜度特徵</strong>：</p>
<h4 id="level-1-簡單">Level 1: 簡單</h4>
<ul>
<li><strong>特徵</strong>: 最小可交付單元，職責明確</li>
<li><strong>適用</strong>: Interface 定義、單一 Value Object、單一方法實作</li>
<li><strong>優點</strong>: 風險低、易測試、易 Review</li>
<li><strong>預估時間</strong>: 5-20 分鐘</li>
</ul>
<h5 id="level-2-中等">Level 2: 中等</h5>
<ul>
<li><strong>特徵</strong>: 少數相關職責，內聚性高</li>
<li><strong>適用</strong>: 含業務邏輯的 Entity、基礎 Repository 方法</li>
<li><strong>注意</strong>: 確保職責相關性，避免職責分散</li>
<li><strong>預估時間</strong>: 20-40 分鐘</li>
</ul>
<h6 id="level-3-複雜">Level 3: 複雜</h6>
<ul>
<li><strong>特徵</strong>: 多職責或跨檔案，整合性高</li>
<li><strong>適用</strong>: 完整 UseCase、Repository CRUD、複雜業務邏輯</li>
<li><strong>風險</strong>: 測試複雜、Review 困難、易出錯</li>
<li><strong>預估時間</strong>: 40-60 分鐘</li>
<li><strong>建議</strong>: 優先評估是否可拆分為 Level 1-2</li>
</ul>
<h6 id="level-4-超標">Level 4: 超標</h6>
<ul>
<li><strong>特徵</strong>: 任一指標超標，範圍失控</li>
<li><strong>問題</strong>: God Ticket、高風險、難以管理</li>
<li><strong>處理</strong>: 必須拆分，無例外</li>
<li><strong>禁止</strong>: 禁止建立此等級 Ticket</li>
</ul>
<hr>
<h3 id="22-評估流程">2.2 評估流程</h3>
<p><strong>3 步驟評估流程</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 初步評估（基於任務描述）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    計算 4 個指標的預估值
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 複雜度確認（取最高等級）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    取 4 個指標中最高的複雜度等級
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 3: 拆分決策（基於等級決定）
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ├─ Level 1-2 → 可直接建立 Ticket
</span></span><span class="line"><span class="ln">12</span><span class="cl">    ├─ Level 3 → 評估是否可拆分
</span></span><span class="line"><span class="ln">13</span><span class="cl">    └─ Level 4 → 必須拆分</span></span></code></pre></div><h4 id="步驟-1初步評估">步驟 1：初步評估</h4>
<p><strong>目標</strong>: 快速估算 4 個指標的值</p>
<p><strong>評估依據</strong>:</p>
<ol>
<li>任務描述（What to do）</li>
<li>驗收條件（Acceptance Criteria）</li>
<li>預期步驟（Steps）</li>
</ol>
<p><strong>評估方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">範例任務：實作 Book Entity
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1-1：估算職責數量
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 分析任務描述，列出所有需要完成的功能點
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 功能點 1：定義 Entity 欄位
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 功能點 2：實作 equals/hashCode
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 功能點 3：實作 toString
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 功能點 4：撰寫測試
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">→ 預估：4 個職責
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 1-2：估算程式碼行數
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 根據類似任務經驗估算
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> Entity 定義：~30 行
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> equals/hashCode：~15 行
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> toString：~5 行
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> 測試：~40 行
</span></span><span class="line"><span class="ln">17</span><span class="cl">→ 預估：~90 行
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">步驟 1-3：估算檔案數量
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 列出需要建立/修改的檔案
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> lib/domain/entities/book.dart（新增）
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">-</span> test/unit/domain/entities/book_test.dart（新增）
</span></span><span class="line"><span class="ln">23</span><span class="cl">→ 預估：2 個檔案（1 個生產 + 1 個測試）
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">步驟 1-4：估算測試數量
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> 根據驗收條件估算測試案例
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 測試 Entity 建立
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 測試 equals（相等/不等）
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 測試 hashCode
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> 測試 toString
</span></span><span class="line"><span class="ln">31</span><span class="cl">→ 預估：5 個測試</span></span></code></pre></div><h4 id="步驟-2複雜度確認">步驟 2：複雜度確認</h4>
<p><strong>目標</strong>: 確定最終複雜度等級</p>
<p><strong>確認方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">對應 4 個指標到複雜度等級：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">職責數量：4 個 → Level 3（複雜）
</span></span><span class="line"><span class="ln">4</span><span class="cl">程式碼行數：90 行 → Level 3（複雜）
</span></span><span class="line"><span class="ln">5</span><span class="cl">檔案數量：2 個（1 生產 + 1 測試）→ Level 2（中等）
</span></span><span class="line"><span class="ln">6</span><span class="cl">測試數量：5 個 → Level 2（中等）
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">取最高等級：Level 3（複雜）</span></span></code></pre></div><p><strong>確認檢查清單</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已計算所有 4 個指標
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已對應每個指標到複雜度等級
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已確定最高複雜度等級
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已記錄評估依據</span></span></code></pre></div><h4 id="步驟-3拆分決策">步驟 3：拆分決策</h4>
<p><strong>目標</strong>: 決定是否拆分以及如何拆分</p>
<p><strong>決策規則</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Level 1-2 → 可直接建立 Ticket
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ├─ 無需拆分
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    └─ 直接進入 Phase 2（測試設計）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Level 3 → 評估是否可拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ├─ 可拆分為 Level 1-2 → 執行拆分
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    └─ 無法拆分 → 勉強接受，加強 Review
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Level 4 → 必須拆分
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ├─ 阻止建立 Ticket
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ├─ 執行拆分（使用第三章拆分策略）
</span></span><span class="line"><span class="ln">12</span><span class="cl">    └─ 重新評估每個拆分後的子 Ticket</span></span></code></pre></div><p><strong>Level 3 拆分評估</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">範例：實作 Book Entity（Level 3）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">拆分評估：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">1.</span> 是否可拆分為更小 Ticket？
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   → 是，可拆分為兩個 Ticket
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">2.</span> 如何拆分？
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   Ticket A: 定義 Book Entity 欄位和基礎方法
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   <span class="k">-</span> 職責：定義欄位 + equals/hashCode
</span></span><span class="line"><span class="ln">10</span><span class="cl">   <span class="k">-</span> 行數：~45 行
</span></span><span class="line"><span class="ln">11</span><span class="cl">   <span class="k">-</span> 檔案：1 個
</span></span><span class="line"><span class="ln">12</span><span class="cl">   <span class="k">-</span> 測試：3 個
</span></span><span class="line"><span class="ln">13</span><span class="cl">   → Level 2（中等）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">   Ticket B: 補充 Book Entity 完整功能
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> 職責：toString + 完整測試
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> 行數：~45 行
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 檔案：1 個（修改）
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 測試：2 個
</span></span><span class="line"><span class="ln">20</span><span class="cl">   → Level 1（簡單）
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">3.</span> 拆分後依賴關係？
</span></span><span class="line"><span class="ln">23</span><span class="cl">   → Ticket B 依賴 Ticket A（B 在 A 完成後執行）
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">4.</span> 拆分價值評估？
</span></span><span class="line"><span class="ln">26</span><span class="cl">   → 降低風險：兩個 Level 1-2 比一個 Level 3 更易管理
</span></span><span class="line"><span class="ln">27</span><span class="cl">   → 易於 Review：分兩次 Review，每次範圍更小
</span></span><span class="line"><span class="ln">28</span><span class="cl">   → 成本：增加一個 Ticket，但風險降低值得
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">結論：建議拆分</span></span></code></pre></div><p><strong>Level 4 拆分處理</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">範例：實作完整書籍評分功能（Level 4）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">當前狀態：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責：8 個 → Level 4
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 行數：180 行 → Level 4
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 檔案：7 個 → Level 4
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試：15 個 → Level 4
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">拆分決策：必須拆分（無選項）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">拆分方式：使用「基於 Clean Architecture 分層拆分策略」（詳見第三章）</span></span></code></pre></div><hr>
<h3 id="23-評估決策樹">2.3 評估決策樹</h3>
<p><strong>完整決策流程圖</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[開始評估]
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[計算 4 個指標]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">[取最高複雜度等級]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    ├─ Level 1（簡單）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    │   [可直接建立 Ticket]
</span></span><span class="line"><span class="ln">10</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">    │   [進入 Phase 2：測試設計]
</span></span><span class="line"><span class="ln">12</span><span class="cl">    │
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ├─ Level 2（中等）
</span></span><span class="line"><span class="ln">14</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">    │   [可直接建立 Ticket]
</span></span><span class="line"><span class="ln">16</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">    │   [（可選）評估是否拆分為更小 Ticket]
</span></span><span class="line"><span class="ln">18</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">19</span><span class="cl">    │   [進入 Phase 2：測試設計]
</span></span><span class="line"><span class="ln">20</span><span class="cl">    │
</span></span><span class="line"><span class="ln">21</span><span class="cl">    ├─ Level 3（複雜）
</span></span><span class="line"><span class="ln">22</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">23</span><span class="cl">    │   [評估是否可拆分]
</span></span><span class="line"><span class="ln">24</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">25</span><span class="cl">    │   ├─ 可拆分？
</span></span><span class="line"><span class="ln">26</span><span class="cl">    │   │   ├─ Yes → [執行拆分]
</span></span><span class="line"><span class="ln">27</span><span class="cl">    │   │   │           ↓
</span></span><span class="line"><span class="ln">28</span><span class="cl">    │   │   │       [重新評估子 Ticket]
</span></span><span class="line"><span class="ln">29</span><span class="cl">    │   │   │           ↓
</span></span><span class="line"><span class="ln">30</span><span class="cl">    │   │   │       [確保所有子 Ticket 為 Level 1-2]
</span></span><span class="line"><span class="ln">31</span><span class="cl">    │   │   │
</span></span><span class="line"><span class="ln">32</span><span class="cl">    │   │   └─ No → [勉強接受]
</span></span><span class="line"><span class="ln">33</span><span class="cl">    │   │              ↓
</span></span><span class="line"><span class="ln">34</span><span class="cl">    │   │          [標記為高風險 Ticket]
</span></span><span class="line"><span class="ln">35</span><span class="cl">    │   │              ↓
</span></span><span class="line"><span class="ln">36</span><span class="cl">    │   │          [加強 Review 機制]
</span></span><span class="line"><span class="ln">37</span><span class="cl">    │   │              ↓
</span></span><span class="line"><span class="ln">38</span><span class="cl">    │   │          [進入 Phase 2]
</span></span><span class="line"><span class="ln">39</span><span class="cl">    │
</span></span><span class="line"><span class="ln">40</span><span class="cl">    └─ Level 4（超標）
</span></span><span class="line"><span class="ln">41</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">42</span><span class="cl">        [禁止建立 Ticket]
</span></span><span class="line"><span class="ln">43</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">44</span><span class="cl">        [阻止進入 Phase 2]
</span></span><span class="line"><span class="ln">45</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">46</span><span class="cl">        [必須拆分（使用第三章策略）]
</span></span><span class="line"><span class="ln">47</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">48</span><span class="cl">        [重新評估所有子 Ticket]
</span></span><span class="line"><span class="ln">49</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">50</span><span class="cl">        [確保所有子 Ticket ≤ Level 3]</span></span></code></pre></div><p><strong>決策節點詳細說明</strong>:</p>
<h4 id="節點-1level-3-拆分評估">節點 1：Level 3 拆分評估</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">問題：此 Ticket 是否可拆分為更小 Ticket？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">評估準則：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">1. 職責是否可分離？
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   - 是否包含多個獨立功能點？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   - 是否可按照 Clean Architecture 分層拆分？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">2. 拆分後是否降低複雜度？
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 拆分後每個子 Ticket 是否 ≤ Level 2？
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 是否減少單一 Ticket 的風險？
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">3. 拆分成本是否合理？
</span></span><span class="line"><span class="ln">13</span><span class="cl">   - 增加的管理成本 vs 降低的風險
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 是否需要額外的整合測試？
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">決策：
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 滿足 1 且 2 → 建議拆分
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 不滿足 1 或 2，但 3 成本高 → 勉強接受 Level 3</span></span></code></pre></div><h5 id="節點-2level-4-強制拆分">節點 2：Level 4 強制拆分</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Level 4 無需評估，必須拆分
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">拆分方法（按優先順序）：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">1. 優先：按 Clean Architecture 分層拆分（詳見第三章 3.1-3.4）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">2. 次之：按職責拆分（每個職責獨立 Ticket）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">3. 最後：按檔案拆分（每個檔案獨立 Ticket）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">拆分要求：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 所有子 Ticket 必須 ≤ Level 3
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 建議所有子 Ticket ≤ Level 2
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 理想所有子 Ticket = Level 1
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">拆分後驗證：
</span></span><span class="line"><span class="ln">15</span><span class="cl">□ 所有子 Ticket 都已重新評估
</span></span><span class="line"><span class="ln">16</span><span class="cl">□ 所有子 Ticket 都 ≤ Level 3
</span></span><span class="line"><span class="ln">17</span><span class="cl">□ 子 Ticket 依賴關係明確
</span></span><span class="line"><span class="ln">18</span><span class="cl">□ 子 Ticket 總和涵蓋原始 Ticket 所有功能</span></span></code></pre></div><hr>
<h3 id="24-複雜度評估實例">2.4 複雜度評估實例</h3>
<p><strong>完整評估案例</strong>：</p>
<h4 id="案例-1簡單-ticket-評估">案例 1：簡單 Ticket 評估</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：定義 IBookRepository 介面
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1：初步評估
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 職責數量：1 個（定義介面方法簽名）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 程式碼行數：~20 行
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個（i_book_repository.dart）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 測試數量：0 個（Interface 不需測試）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 2：複雜度確認
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 職責：1 個 → Level 1
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 行數：20 行 → Level 1
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 檔案：1 個 → Level 1
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 測試：0 個 → Level 1
</span></span><span class="line"><span class="ln">14</span><span class="cl">→ 最高等級：Level 1
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 3：拆分決策
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Level 1 → 可直接建立 Ticket
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 無需拆分
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 進入 Phase 2</span></span></code></pre></div><h4 id="案例-2中等-ticket-評估">案例 2：中等 Ticket 評估</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作 Rating Value Object
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1：初步評估
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 職責數量：2 個（建立 + 驗證）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 程式碼行數：~50 行（含測試）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個（rating.dart）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 測試數量：5 個
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 2：複雜度確認
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 職責：2 個 → Level 2
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 行數：50 行 → Level 2
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 檔案：1 個 → Level 1
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 測試：5 個 → Level 2
</span></span><span class="line"><span class="ln">14</span><span class="cl">→ 最高等級：Level 2
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 3：拆分決策
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Level 2 → 可直接建立 Ticket
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 評估：可選拆分，但不必要（職責內聚性高）
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 進入 Phase 2</span></span></code></pre></div><h4 id="案例-3複雜-ticket-評估與拆分">案例 3：複雜 Ticket 評估與拆分</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作 BookRepository CRUD
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1：初步評估
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 職責數量：5 個（get + save + delete + mapper + 錯誤處理）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 程式碼行數：~160 行（含測試）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 涉及檔案：2 個（repository.dart + mapper.dart）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 測試數量：10 個
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 2：複雜度確認
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 職責：5 個 → Level 3
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 行數：160 行 → Level 4（超過 100 行）
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 檔案：2 個 → Level 2
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 測試：10 個 → Level 3
</span></span><span class="line"><span class="ln">14</span><span class="cl">→ 最高等級：Level 4（行數超標）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 3：拆分決策
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Level 4 → 必須拆分
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 拆分方式：按 CRUD 方法拆分
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  Ticket A: 實作 getBookByIsbn
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="k">-</span> 職責：2 個（查詢 + 轉換）
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="k">-</span> 行數：~50 行
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">-</span> 檔案：2 個
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">-</span> 測試：3 個
</span></span><span class="line"><span class="ln">25</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">  Ticket B: 實作 saveBook
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="k">-</span> 職責：2 個（儲存 + 錯誤處理）
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">-</span> 行數：~60 行
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">-</span> 檔案：1 個（修改 repository.dart）
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">-</span> 測試：4 個
</span></span><span class="line"><span class="ln">32</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">  Ticket C: 實作 deleteBook
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="k">-</span> 職責：2 個（刪除 + 錯誤處理）
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="k">-</span> 行數：~50 行
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="k">-</span> 檔案：1 個（修改 repository.dart）
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="k">-</span> 測試：3 個
</span></span><span class="line"><span class="ln">39</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">結果：拆分為 3 個 Level 2 Ticket</span></span></code></pre></div><h4 id="案例-4god-ticket-評估與拆分">案例 4：God Ticket 評估與拆分</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">任務：實作完整書籍評分功能（UI + UseCase + Repository）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1：初步評估
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 職責數量：8 個（UI + Controller + UseCase + Entity + Repository + Mapper + 測試 + 整合）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 程式碼行數：~300 行
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 涉及檔案：8 個（跨 4 個架構層級）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 測試數量：20 個
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 2：複雜度確認
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 職責：8 個 → Level 4
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 行數：300 行 → Level 4
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 檔案：8 個 → Level 4
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 測試：20 個 → Level 4
</span></span><span class="line"><span class="ln">14</span><span class="cl">→ 最高等級：Level 4（所有指標都超標）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 3：拆分決策
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Level 4 → 必須拆分（God Ticket）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 拆分方式：使用 Clean Architecture 分層拆分策略
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  Ticket 1: Domain 層實作（Layer 5）
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="k">-</span> Rating Entity + Rating Value Object
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="k">-</span> 職責：2 個
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">-</span> 行數：~60 行
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">-</span> 檔案：2 個
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="k">-</span> 測試：6 個
</span></span><span class="line"><span class="ln">26</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">  Ticket 2: Repository 層實作（Layer 4-5 Interface + Infra）
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">-</span> IRatingRepository + SQLiteRatingRepository + Mapper
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">-</span> 職責：3 個
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">-</span> 行數：~90 行
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="k">-</span> 檔案：3 個
</span></span><span class="line"><span class="ln">33</span><span class="cl">  <span class="k">-</span> 測試：8 個
</span></span><span class="line"><span class="ln">34</span><span class="cl">  → Level 3（可接受）
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">  Ticket 3: UseCase 層實作（Layer 3）
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="k">-</span> RateBookUseCase
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="k">-</span> 職責：2 個
</span></span><span class="line"><span class="ln">39</span><span class="cl">  <span class="k">-</span> 行數：~60 行
</span></span><span class="line"><span class="ln">40</span><span class="cl">  <span class="k">-</span> 檔案：1 個
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="k">-</span> 測試：4 個
</span></span><span class="line"><span class="ln">42</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">  Ticket 4: Presentation 層實作（Layer 1-2）
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="k">-</span> RatingWidget + RatingController
</span></span><span class="line"><span class="ln">46</span><span class="cl">  <span class="k">-</span> 職責：2 個
</span></span><span class="line"><span class="ln">47</span><span class="cl">  <span class="k">-</span> 行數：~90 行
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="k">-</span> 檔案：2 個
</span></span><span class="line"><span class="ln">49</span><span class="cl">  <span class="k">-</span> 測試：4 個（Widget 測試）
</span></span><span class="line"><span class="ln">50</span><span class="cl">  → Level 2
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">結果：拆分為 4 個 Ticket（3 個 Level 2 + 1 個 Level 3）
</span></span><span class="line"><span class="ln">53</span><span class="cl">依賴順序：Ticket 1 → Ticket 2 → Ticket 3 → Ticket 4</span></span></code></pre></div><hr>
<h2 id="第三章clean-architecture-分層拆分策略">第三章：Clean Architecture 分層拆分策略</h2>
<h3 id="為什麼需要基於架構分層拆分">為什麼需要基於架構分層拆分</h3>
<p><strong>架構分層拆分的核心價值</strong>:</p>
<ol>
<li>
<p><strong>單層修改原則</strong>（Single Layer Modification Principle）</p>
<ul>
<li>每個 Ticket 專注於單一架構層級</li>
<li>降低跨層依賴帶來的複雜度</li>
<li>提升程式碼審查效率</li>
</ul>
</li>
<li>
<p>依賴方向一致性</p>
<ul>
<li>遵循 Clean Architecture 依賴規則（內層不依賴外層）</li>
<li>避免循環依賴</li>
<li>確保架構穩定性</li>
</ul>
</li>
<li>
<p>測試可獨立性</p>
<ul>
<li>每層有明確的測試策略</li>
<li>可獨立測試不依賴其他層</li>
<li>簡化 Mock 和 Stub</li>
</ul>
</li>
</ol>
<p><strong>本章內容結構</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">3.1 Clean Architecture 五層架構回顧
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    └── 快速回顧五層架構和依賴規則
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">3.2 四種標準拆分策略
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ├── 策略 1: Interface 定義 Ticket
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    ├── 策略 2: 具體實作 Ticket
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ├── 策略 3: 測試驗證 Ticket
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    └── 策略 4: 整合連接 Ticket
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">3.3 分層拆分決策指引
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    └── 如何選擇合適的拆分策略
</span></span><span class="line"><span class="ln">10</span><span class="cl">3.4 分層拆分實務案例
</span></span><span class="line"><span class="ln">11</span><span class="cl">    └── 完整的書籍評分功能拆分範例</span></span></code></pre></div><hr>
<h3 id="31-clean-architecture-五層架構回顧">3.1 Clean Architecture 五層架構回顧</h3>
<p><strong>五層架構定義</strong>（引用自 Clean Architecture 實作方法論）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Layer 1 (UI - 最外層)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── 職責: 使用者介面元件
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── 路徑: lib/presentation/widgets/, lib/presentation/pages/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 依賴: Layer 2 (Behavior)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">└── 不依賴: Layer 3-5
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Layer 2 (Behavior)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── 職責: UI 行為控制（State Management）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── 路徑: lib/presentation/controllers/, lib/presentation/providers/
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 依賴: Layer 3 (UseCase)
</span></span><span class="line"><span class="ln">11</span><span class="cl">└── 不依賴: Layer 1, 4-5
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Layer 3 (UseCase)
</span></span><span class="line"><span class="ln">14</span><span class="cl">├── 職責: 業務用例協調
</span></span><span class="line"><span class="ln">15</span><span class="cl">├── 路徑: lib/application/use_cases/, lib/application/services/
</span></span><span class="line"><span class="ln">16</span><span class="cl">├── 依賴: Layer 4-5 (Domain)
</span></span><span class="line"><span class="ln">17</span><span class="cl">└── 不依賴: Layer 1-2
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">Layer 4 (Domain Events/Interfaces)
</span></span><span class="line"><span class="ln">20</span><span class="cl">├── 職責: 領域事件和介面定義
</span></span><span class="line"><span class="ln">21</span><span class="cl">├── 路徑: lib/domain/events/, lib/domain/repositories/ (介面)
</span></span><span class="line"><span class="ln">22</span><span class="cl">├── 依賴: Layer 5 (Domain Implementation)
</span></span><span class="line"><span class="ln">23</span><span class="cl">└── 不依賴: Layer 1-3
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">Layer 5 (Domain Implementation - 最內層)
</span></span><span class="line"><span class="ln">26</span><span class="cl">├── 職責: 領域模型實作和基礎設施
</span></span><span class="line"><span class="ln">27</span><span class="cl">├── 路徑: lib/domain/entities/, lib/domain/value_objects/, lib/infrastructure/
</span></span><span class="line"><span class="ln">28</span><span class="cl">├── 依賴: 無（核心層）
</span></span><span class="line"><span class="ln">29</span><span class="cl">└── 不依賴: 任何層</span></span></code></pre></div><p><strong>依賴規則（Dependency Rule）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">外層 → 內層 允許
</span></span><span class="line"><span class="ln">2</span><span class="cl">內層 → 外層 禁止
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">- Layer 2 (Behavior) → Layer 3 (UseCase)
</span></span><span class="line"><span class="ln">7</span><span class="cl">- Layer 3 (UseCase) → Layer 2 (Behavior)</span></span></code></pre></div><p><strong>單層修改原則</strong>:</p>
<ul>
<li><strong>理想</strong>: 每個 Ticket 只修改單一層級</li>
<li><strong>可接受</strong>: Ticket 修改相鄰兩層（如 Interface + Implementation）</li>
<li><strong>禁止</strong>: Ticket 跨越超過 2 層（如 UI → Domain 直接跨越）</li>
</ul>
<hr>
<h3 id="32-四種標準拆分策略">3.2 四種標準拆分策略</h3>
<h4 id="策略-1interface-定義-ticket">策略 1：Interface 定義 Ticket</h4>
<p><strong>定義</strong>: 定義一個介面及其輸入輸出契約。</p>
<p><strong>適用層級</strong>: 主要用於 Layer 4 (Domain Interfaces)</p>
<p><strong>職責範圍</strong>:</p>
<ul>
<li>定義 Interface 簽名</li>
<li>定義輸入參數類型</li>
<li>定義回傳類型</li>
<li>撰寫文檔註解（含業務需求編號）</li>
</ul>
<p><strong>禁止包含</strong>:</p>
<ul>
<li>具體實作邏輯</li>
<li>資料庫操作</li>
<li>業務邏輯</li>
</ul>
<p><strong>Ticket 範本</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #NNN: 定義 {Interface 名稱} 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>[引用業務需求編號，如 REQ-001]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`{Interface 名稱}`</span> 介面，定義 {業務功能} 的契約
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domain/repositories/`</span> 建立 <span class="sb">`{interface_file}.dart`</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`{method1}`</span> 方法簽名
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`{method2}`</span> 方法簽名
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 撰寫文檔註解（含需求編號）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Interface 檔案建立在正確位置
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 所有方法簽名完整且明確
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 輸入輸出類型定義清楚
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> 包含完整的文檔註解（含需求編號）
</span></span><span class="line"><span class="ln">20</span><span class="cl">- [ ] dart analyze 0 錯誤</span></span></code></pre></div><p><strong>實務範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #101: 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料存取功能
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IBookRepository`</span> 介面，定義書籍資料存取的契約
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domain/repositories/`</span> 建立 <span class="sb">`i_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`getBookByIsbn`</span> 方法簽名
</span></span><span class="line"><span class="ln">12</span><span class="cl">   ```dart
</span></span><span class="line"><span class="ln">13</span><span class="cl">   /// [REQ-LIB-001.1] 根據 ISBN 查詢書籍
</span></span><span class="line"><span class="ln">14</span><span class="cl">   ///
</span></span><span class="line"><span class="ln">15</span><span class="cl">   /// 參數:
</span></span><span class="line"><span class="ln">16</span><span class="cl">   /// - isbn: 書籍 ISBN 編號
</span></span><span class="line"><span class="ln">17</span><span class="cl">   ///
</span></span><span class="line"><span class="ln">18</span><span class="cl">   /// 回傳:
</span></span><span class="line"><span class="ln">19</span><span class="cl">   /// - Book 物件（存在）或 null（不存在）
</span></span><span class="line"><span class="ln">20</span><span class="cl">   Future<span class="p">&lt;</span><span class="nt">Book</span><span class="err">?</span><span class="p">&gt;</span> getBookByIsbn(String isbn);</span></span></code></pre></div><ol start="3">
<li>定義 <code>saveBook</code> 方法簽名</li>
<li>定義 <code>deleteBook</code> 方法簽名</li>
<li>撰寫文檔註解</li>
</ol>
<h3 id="驗收條件">驗收條件</h3>
<ul>
<li><input disabled="" type="checkbox"> Interface 檔案建立在 <code>lib/domain/repositories/</code></li>
<li><input disabled="" type="checkbox"> 3 個方法簽名完整且明確</li>
<li><input disabled="" type="checkbox"> 輸入輸出類型定義清楚</li>
<li><input disabled="" type="checkbox"> 包含完整的文檔註解（含需求編號）</li>
<li><input disabled="" type="checkbox"> dart analyze 0 錯誤</li>
</ul>
<h3 id="指標評估">指標評估</h3>
<ul>
<li>職責: 1 個（定義介面）</li>
<li>行數: ~25 行</li>
<li>檔案: 1 個</li>
<li>測試: 0 個（Interface 不需單元測試）</li>
</ul>
<p>→ Level 1（簡單）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">#### 策略 2：具體實作 Ticket
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">**定義**: 實作一個類別的核心邏輯。
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">**適用層級**:
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- Layer 5 (Domain Implementation): Entity, Value Object
</span></span><span class="line"><span class="ln">10</span><span class="cl">- Layer 5 (Infrastructure): Repository 實作, Service 實作
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">**職責範圍**:
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 實作類別邏輯
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 實現介面方法
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 處理異常
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 撰寫單元測試
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">**禁止包含**:
</span></span><span class="line"><span class="ln">19</span><span class="cl">- UI 元件
</span></span><span class="line"><span class="ln">20</span><span class="cl">- 跨層整合（如直接呼叫 UseCase）
</span></span><span class="line"><span class="ln">21</span><span class="cl">- 測試以外的其他層修改
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">**Ticket 範本**:
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">```markdown
</span></span><span class="line"><span class="ln">26</span><span class="cl">## Ticket #NNN: 實作 {類別名稱}
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">### 業務需求
</span></span><span class="line"><span class="ln">29</span><span class="cl">[引用業務需求編號]
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">### 目標
</span></span><span class="line"><span class="ln">32</span><span class="cl">實作 `{類別名稱}`，提供 {業務功能} 的具體實現
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">### 依賴 Ticket
</span></span><span class="line"><span class="ln">35</span><span class="cl">- Ticket #XXX: 定義 {Interface 名稱} 介面（必須先完成）
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">### 步驟
</span></span><span class="line"><span class="ln">38</span><span class="cl">1. 建立 `{類別名稱}` 類別（實作 `{Interface}`）
</span></span><span class="line"><span class="ln">39</span><span class="cl">2. 實作 `{method1}` 方法
</span></span><span class="line"><span class="ln">40</span><span class="cl">3. 實作 `{method2}` 方法
</span></span><span class="line"><span class="ln">41</span><span class="cl">4. 處理異常情況
</span></span><span class="line"><span class="ln">42</span><span class="cl">5. 撰寫單元測試（正常流程 + 異常處理）
</span></span><span class="line"><span class="ln">43</span><span class="cl">6. 確保所有測試通過
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">### 驗收條件
</span></span><span class="line"><span class="ln">46</span><span class="cl">- [ ] 實作所有 Interface 方法
</span></span><span class="line"><span class="ln">47</span><span class="cl">- [ ] 異常處理完整
</span></span><span class="line"><span class="ln">48</span><span class="cl">- [ ] 單元測試 100% 通過
</span></span><span class="line"><span class="ln">49</span><span class="cl">- [ ] 測試覆蓋正常流程和異常處理
</span></span><span class="line"><span class="ln">50</span><span class="cl">- [ ] dart analyze 0 錯誤</span></span></code></pre></div><p><strong>實務範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #102: 實作 SQLiteBookRepository
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料存取功能
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SQLiteBookRepository`</span>，提供書籍資料的 SQLite 儲存
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#101:</span> 定義 IBookRepository 介面（必須先完成）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/infrastructure/repositories/`</span> 建立 <span class="sb">`sqlite_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 實作 <span class="sb">`getBookByIsbn`</span> 方法
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> SQL 查詢邏輯
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換（DTO → Entity）
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> 錯誤處理（Database Exception）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`saveBook`</span> 方法
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> SQL 插入/更新邏輯
</span></span><span class="line"><span class="ln">20</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換（Entity → DTO）
</span></span><span class="line"><span class="ln">21</span><span class="cl">   <span class="k">-</span> 錯誤處理
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">4.</span> 實作 <span class="sb">`deleteBook`</span> 方法
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">5.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">24</span><span class="cl">   <span class="k">-</span> 正常流程：CRUD 操作成功
</span></span><span class="line"><span class="ln">25</span><span class="cl">   <span class="k">-</span> 異常處理：資料庫錯誤、資料不存在
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">6.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 實作所有 IBookRepository 方法
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整（DatabaseException, ValidationException）
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 6 個測試案例）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 3 個（CRUD 實作、Mapper、異常處理）
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 行數: ~80 行（含測試）
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> 測試: 6 個
</span></span><span class="line"><span class="ln">39</span><span class="cl">→ Level 2（中等）</span></span></code></pre></div><hr>
<h4 id="策略-3測試驗證-ticket">策略 3：測試驗證 Ticket</h4>
<p><strong>定義</strong>: 撰寫一組相關的測試用例，補強現有實作的測試覆蓋率。</p>
<p><strong>適用時機</strong>:</p>
<ul>
<li>現有實作缺乏完整測試</li>
<li>需要補充邊界測試和異常測試</li>
<li>TDD 紅綠燈循環中的「紅燈」階段</li>
</ul>
<p><strong>職責範圍</strong>:</p>
<ul>
<li>撰寫單元測試</li>
<li>覆蓋正常流程</li>
<li>覆蓋邊界條件</li>
<li>覆蓋異常處理</li>
<li>確保測試通過</li>
</ul>
<p><strong>禁止包含</strong>:</p>
<ul>
<li>修改生產程式碼（除非是修正測試發現的 Bug）</li>
<li>新增功能</li>
<li>重構（測試 Ticket 專注於驗證，不做重構）</li>
</ul>
<p><strong>Ticket 範本</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #NNN: 撰寫 {功能} 測試
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>[引用業務需求編號]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>撰寫 <span class="sb">`{類別名稱}`</span> 的完整測試用例，確保 {業務功能} 正確性
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#XXX:</span> 實作 {類別名稱}（必須先完成）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立測試檔案 <span class="sb">`{class}_test.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 撰寫正常流程測試
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">3.</span> 撰寫邊界條件測試
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">4.</span> 撰寫異常處理測試
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 測試檔案建立在正確位置
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> 至少 N 個測試用例
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 覆蓋正常流程、邊界條件和異常處理
</span></span><span class="line"><span class="ln">23</span><span class="cl">- [ ] 所有測試 100% 通過</span></span></code></pre></div><p><strong>實務範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #103: 撰寫 BookRepository 整合測試
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料存取功能
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>撰寫 <span class="sb">`BookRepository`</span> 的整合測試，驗證資料庫操作正確性
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#102:</span> 實作 SQLiteBookRepository（必須先完成）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立測試檔案 <span class="sb">`test/integration/repositories/book_repository_test.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 撰寫 <span class="sb">`getBookByIsbn`</span> 測試
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> 測試 1: 成功取得存在的書籍
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> 測試 2: 書籍不存在回傳 null
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> 測試 3: 無效 ISBN 拋出異常
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 撰寫 <span class="sb">`saveBook`</span> 測試
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 測試 4: 新增書籍成功
</span></span><span class="line"><span class="ln">20</span><span class="cl">   <span class="k">-</span> 測試 5: 更新現有書籍成功
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">4.</span> 撰寫 <span class="sb">`deleteBook`</span> 測試
</span></span><span class="line"><span class="ln">22</span><span class="cl">   <span class="k">-</span> 測試 6: 刪除存在的書籍
</span></span><span class="line"><span class="ln">23</span><span class="cl">   <span class="k">-</span> 測試 7: 刪除不存在的書籍無異常
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 測試檔案建立在 <span class="sb">`test/integration/repositories/`</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> 至少 7 個整合測試案例
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">- [ ]</span> 覆蓋正常流程和異常處理
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> 所有測試 100% 通過
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 1 個（撰寫測試）
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 行數: ~60 行（純測試）
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">-</span> 檔案: 1 個（測試檔案）
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 測試: 7 個
</span></span><span class="line"><span class="ln">37</span><span class="cl">→ Level 2（中等）</span></span></code></pre></div><hr>
<h4 id="策略-4整合連接-ticket">策略 4：整合連接 Ticket</h4>
<p><strong>定義</strong>: 連接兩個模組並驗證整合，實現端到端流程。</p>
<p><strong>適用時機</strong>:</p>
<ul>
<li>需要連接 Layer 3 (UseCase) 和 Layer 5 (Repository)</li>
<li>需要連接 Layer 2 (Controller) 和 Layer 3 (UseCase)</li>
<li>完成分層實作後的整合階段</li>
</ul>
<p><strong>職責範圍</strong>:</p>
<ul>
<li>連接 Use Case 和 Repository</li>
<li>實作依賴注入</li>
<li>撰寫整合測試</li>
<li>驗證端到端流程</li>
</ul>
<p><strong>禁止包含</strong>:</p>
<ul>
<li>修改核心業務邏輯（應在具體實作 Ticket 完成）</li>
<li>跨越超過 2 層的整合</li>
<li>UI 實作（應獨立為 Presentation 層 Ticket）</li>
</ul>
<p><strong>Ticket 範本</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #NNN: 整合 {UseCase} 到 {Repository}
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>[引用業務需求編號]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>將 <span class="sb">`{Repository}`</span> 整合到 <span class="sb">`{UseCase}`</span>，實現 {業務功能} 完整流程
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#XXX:</span> 定義 {Interface}（必須先完成）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#YYY:</span> 實作 {Repository}（必須先完成）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`{UseCase}`</span> 注入 <span class="sb">`{Interface}`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 在 <span class="sb">`execute`</span> 方法中呼叫 Repository 方法
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 處理 Repository 異常
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">4.</span> 撰寫整合測試驗證端到端流程
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> <span class="sb">`{UseCase}`</span> 正確注入 <span class="sb">`{Interface}`</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 端到端流程正常運作
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> 整合測試 100% 通過
</span></span><span class="line"><span class="ln">24</span><span class="cl">- [ ] 異常處理完整</span></span></code></pre></div><p><strong>實務範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #104: 整合 BookRepository 到 GetBookUseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍查詢功能
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>將 <span class="sb">`BookRepository`</span> 整合到 <span class="sb">`GetBookUseCase`</span>，實現完整書籍查詢流程
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#101:</span> 定義 IBookRepository 介面（必須先完成）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#102:</span> 實作 SQLiteBookRepository（必須先完成）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`GetBookInteractor`</span> 注入 <span class="sb">`IBookRepository`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">   ```dart
</span></span><span class="line"><span class="ln">16</span><span class="cl">   class GetBookInteractor implements GetBookUseCase {
</span></span><span class="line"><span class="ln">17</span><span class="cl">     final IBookRepository _repository;
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">     GetBookInteractor(this._repository);
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">     <span class="ni">@override</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">     Future<span class="p">&lt;</span><span class="nt">Book</span><span class="err">?</span><span class="p">&gt;</span> execute(String isbn) async {
</span></span><span class="line"><span class="ln">23</span><span class="cl">       // 呼叫 Repository
</span></span><span class="line"><span class="ln">24</span><span class="cl">     }
</span></span><span class="line"><span class="ln">25</span><span class="cl">   }</span></span></code></pre></div><ol start="2">
<li>在 <code>execute</code> 方法中呼叫 <code>repository.getBookByIsbn</code></li>
<li>處理 Repository 異常（ValidationException, StorageException）</li>
<li>撰寫整合測試
<ul>
<li>測試 1: 成功查詢書籍</li>
<li>測試 2: 書籍不存在</li>
<li>測試 3: Repository 異常處理</li>
</ul>
</li>
<li>確保所有測試通過</li>
</ol>
<h3 id="驗收條件整合應用案例">驗收條件（整合應用案例）</h3>
<ul>
<li><input disabled="" type="checkbox"> <code>GetBookInteractor</code> 正確注入 <code>IBookRepository</code></li>
<li><input disabled="" type="checkbox"> 端到端流程正常運作</li>
<li><input disabled="" type="checkbox"> 整合測試 100% 通過（至少 3 個測試案例）</li>
<li><input disabled="" type="checkbox"> 異常處理完整</li>
</ul>
<h3 id="指標評估整合應用案例">指標評估（整合應用案例）</h3>
<ul>
<li>職責: 2 個（注入、整合）</li>
<li>行數: ~50 行（含測試）</li>
<li>檔案: 1 個（修改 UseCase）</li>
<li>測試: 3 個</li>
</ul>
<p>→ Level 2（中等）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">### 3.3 分層拆分決策指引
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">**決策流程圖**:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">```text
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">[分析 Ticket 涉及的架構層級]
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ├─ 單層修改？
</span></span><span class="line"><span class="ln">12</span><span class="cl">    │   ├─ Yes → 選擇對應策略
</span></span><span class="line"><span class="ln">13</span><span class="cl">    │   │   ├─ Layer 4 Interface → 策略 1: Interface 定義
</span></span><span class="line"><span class="ln">14</span><span class="cl">    │   │   ├─ Layer 5 Implementation → 策略 2: 具體實作
</span></span><span class="line"><span class="ln">15</span><span class="cl">    │   │   └─ 補充測試 → 策略 3: 測試驗證
</span></span><span class="line"><span class="ln">16</span><span class="cl">    │   │
</span></span><span class="line"><span class="ln">17</span><span class="cl">    │   └─ No → 跨層修改？
</span></span><span class="line"><span class="ln">18</span><span class="cl">    │       ├─ 相鄰兩層整合 → 策略 4: 整合連接
</span></span><span class="line"><span class="ln">19</span><span class="cl">    │       └─ 跨越超過 2 層 → 必須拆分為多個 Ticket</span></span></code></pre></div><p><strong>決策準則</strong>:</p>
<h4 id="準則-1-優先單層修改">準則 1: 優先單層修改</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">問題: Ticket 是否只修改單一架構層級？
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Yes → 選擇策略 1-3（Interface / 實作 / 測試）
</span></span><span class="line"><span class="ln">4</span><span class="cl">- No → 評估是否可拆分為多個單層 Ticket</span></span></code></pre></div><h5 id="準則-2-相鄰層整合可接受">準則 2: 相鄰層整合可接受</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">問題: 是否為相鄰兩層的整合？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> Layer 3 (UseCase) + Layer 4-5 (Repository) 可接受
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> Layer 2 (Controller) + Layer 3 (UseCase) 可接受
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> Layer 1 (UI) + Layer 5 (Domain) 禁止（跨越太多層）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">決策：
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 相鄰兩層整合 → 策略 4: 整合連接
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 跨越超過 2 層 → 必須拆分為多個 Ticket</span></span></code></pre></div><h6 id="準則-3-interface-先行">準則 3: Interface 先行</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">原則: Interface 定義必須先於實作
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">範例拆分順序：
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">1.</span> Ticket A: 定義 IBookRepository（策略 1）
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">2.</span> Ticket B: 實作 SQLiteBookRepository（策略 2）
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">3.</span> Ticket C: 整合到 GetBookUseCase（策略 4）
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">依賴關係: Ticket C 依賴 B，B 依賴 A</span></span></code></pre></div><h6 id="準則-4-測試獨立或整合">準則 4: 測試獨立或整合</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">問題: 測試應該獨立 Ticket 還是整合到實作 Ticket？
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">決策：
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 實作 Ticket <span class="p">&lt;</span> <span class="nt">Level</span> <span class="na">2</span> <span class="err">→</span> <span class="na">整合測試到實作</span> <span class="na">Ticket</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> <span class="na">實作</span> <span class="na">Ticket </span><span class="o">=</span> <span class="s">Level</span> <span class="na">2-3</span> <span class="err">→</span> <span class="na">可選擇獨立測試</span> <span class="na">Ticket</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="na">-</span> <span class="na">測試案例</span> <span class="p">&gt;</span> 10 個 → 必須獨立測試 Ticket（策略 3）</span></span></code></pre></div><hr>
<h3 id="34-分層拆分實務案例">3.4 分層拆分實務案例</h3>
<h4 id="完整案例書籍評分功能實作">完整案例：書籍評分功能實作</h4>
<h4 id="原始-god-ticket-分析">原始 God Ticket 分析</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">原始任務: 實作完整書籍評分功能（UI + UseCase + Repository）
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">指標評估:
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 職責: 8 個
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 行數: ~300 行
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 檔案: 8 個
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> 測試: 20 個
</span></span><span class="line"><span class="ln">8</span><span class="cl">→ Level 4（所有指標都超標）必須拆分</span></span></code></pre></div><h4 id="分層拆分方案">分層拆分方案</h4>
<p><strong>拆分為 6 個 Ticket（按 Clean Architecture 分層）</strong>:</p>
<hr>
<h5 id="ticket-1-定義-rating-domain-模型layer-5---domain-implementation">Ticket 1: 定義 Rating Domain 模型（Layer 5 - Domain Implementation）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #201: 定義 Rating Value Object
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 5 (Domain Implementation)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`Rating`</span> Value Object，封裝評分規則（1-5 分）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domain/value_objects/rating.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 實作 Rating 類別
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> 建構子驗證（1-5 分）
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> equals / hashCode
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> toString
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 撰寫單元測試（正常、邊界、異常）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">4.</span> 確保測試通過
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Rating 類別實作完整
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> 驗證邏輯正確（1-5 分）
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 5 個測試）
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 1 個
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 行數: ~50 行
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> 測試: 5 個
</span></span><span class="line"><span class="ln">32</span><span class="cl">→ Level 2（中等）</span></span></code></pre></div><hr>
<h4 id="ticket-2-定義-rating-entitylayer-5---domain-implementation">Ticket 2: 定義 Rating Entity（Layer 5 - Domain Implementation）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #202: 定義 Rating Entity
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 5 (Domain Implementation)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴: Ticket #201（Rating Value Object）
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`Rating`</span> Entity，包含評分和評論資訊
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domain/entities/rating.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 實作 Rating Entity
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> 欄位: ratingValue (Rating VO), comment (String), userId, bookIsbn
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> equals / hashCode
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">4.</span> 確保測試通過
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Rating Entity 實作完整
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> 使用 Rating Value Object
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 3 個測試）
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 1 個
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 行數: ~40 行
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> 測試: 3 個
</span></span><span class="line"><span class="ln">32</span><span class="cl">→ Level 1（簡單）</span></span></code></pre></div><hr>
<h4 id="ticket-3-定義-iratingrepository-介面layer-4---domain-interfaces">Ticket 3: 定義 IRatingRepository 介面（Layer 4 - Domain Interfaces）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #203: 定義 IRatingRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 4 (Domain Interfaces)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 1（Interface 定義）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IRatingRepository`</span> 介面，定義評分資料存取契約
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domain/repositories/i_rating_repository.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`saveRating`</span> 方法簽名
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`getRatingsByBookIsbn`</span> 方法簽名
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">4.</span> 撰寫文檔註解
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Interface 檔案建立在正確位置
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 2 個方法簽名完整
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> 文檔註解包含需求編號
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 1 個
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> 行數: ~20 行
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 測試: 0 個
</span></span><span class="line"><span class="ln">29</span><span class="cl">→ Level 1（簡單）</span></span></code></pre></div><hr>
<h4 id="ticket-4-實作-sqliteratingrepositorylayer-5---infrastructure">Ticket 4: 實作 SQLiteRatingRepository（Layer 5 - Infrastructure）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #204: 實作 SQLiteRatingRepository
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 5 (Infrastructure)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴: Ticket #202, #203
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SQLiteRatingRepository`</span>，提供評分資料的 SQLite 儲存
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/infrastructure/repositories/sqlite_rating_repository.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 實作 <span class="sb">`saveRating`</span> 方法
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> SQL 插入邏輯
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 錯誤處理
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`getRatingsByBookIsbn`</span> 方法
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 撰寫單元測試（CRUD + 異常）
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 確保測試通過
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 實作所有 IRatingRepository 方法
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 6 個測試）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 3 個（CRUD、Mapper、異常）
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> 行數: ~90 行
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 測試: 6 個
</span></span><span class="line"><span class="ln">34</span><span class="cl">→ Level 3（複雜）可接受</span></span></code></pre></div><hr>
<h4 id="ticket-5-實作-ratebookusecaselayer-3---usecase">Ticket 5: 實作 RateBookUseCase（Layer 3 - UseCase）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #205: 實作 RateBookUseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 3 (UseCase)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 4（整合連接）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴: Ticket #203, #204
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`RateBookUseCase`</span>，協調書籍評分業務流程
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/application/use_cases/rate_book_use_case.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 注入 <span class="sb">`IRatingRepository`</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`execute`</span> 方法
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> 建立 Rating Entity
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 呼叫 Repository 儲存
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 處理異常
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 撰寫整合測試
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 確保測試通過
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> UseCase 正確注入 IRatingRepository
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> 業務流程完整
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> 整合測試 100% 通過（至少 4 個測試）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 2 個（協調、整合）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 行數: ~60 行
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 檔案: 1 個
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 測試: 4 個
</span></span><span class="line"><span class="ln">35</span><span class="cl">→ Level 2（中等）</span></span></code></pre></div><hr>
<h4 id="ticket-6-實作-ratingwidget-uilayer-1-2---presentation">Ticket 6: 實作 RatingWidget UI（Layer 1-2 - Presentation）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #206: 實作 RatingWidget 和 RatingController
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級: Layer 1 (UI) + Layer 2 (Behavior)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略: 策略 2 + 策略 4（實作 + 整合）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴: Ticket #205
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作評分 UI 元件和行為控制
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/presentation/widgets/rating_widget.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 實作 RatingWidget（5 星評分 UI）
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 建立 <span class="sb">`lib/presentation/controllers/rating_controller.dart`</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">4.</span> 實作 RatingController
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 注入 RateBookUseCase
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 呼叫 UseCase 評分
</span></span><span class="line"><span class="ln">20</span><span class="cl">   <span class="k">-</span> 狀態管理
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 撰寫 Widget 測試
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">6.</span> 確保測試通過
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> RatingWidget UI 正確顯示
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> RatingController 正確整合 UseCase
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> Widget 測試 100% 通過（至少 4 個測試）
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責: 2 個（UI、Controller）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 行數: ~80 行
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 檔案: 2 個
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 測試: 4 個
</span></span><span class="line"><span class="ln">35</span><span class="cl">→ Level 2（中等）</span></span></code></pre></div><hr>
<h4 id="拆分結果總結">拆分結果總結</h4>
<p><strong>拆分前後對比</strong>:</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>拆分前（God Ticket）</th>
          <th>拆分後（6 個 Ticket）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>職責數量</td>
          <td>8 個</td>
          <td>平均 1.7 個</td>
      </tr>
      <tr>
          <td>程式碼行數</td>
          <td>~300 行</td>
          <td>平均 57 行</td>
      </tr>
      <tr>
          <td>檔案數量</td>
          <td>8 個</td>
          <td>平均 1.2 個</td>
      </tr>
      <tr>
          <td>測試數量</td>
          <td>20 個</td>
          <td>平均 4 個</td>
      </tr>
      <tr>
          <td>複雜度等級</td>
          <td>Level 4</td>
          <td>5 個 Level 1-2<br>1 個 Level 3</td>
      </tr>
  </tbody>
</table>
<p><strong>拆分效益</strong>:</p>
<ul>
<li><strong>風險降低</strong>: 從 1 個高風險任務 → 6 個低風險任務</li>
<li><strong>並行開發</strong>: 可 2-3 人同時開發不同層級</li>
<li><strong>易於 Review</strong>: 每個 PR 範圍小，Review 時間縮短</li>
<li><strong>依賴明確</strong>: 清楚的 Ticket 依賴順序</li>
<li><strong>測試獨立</strong>: 每層可獨立測試，Mock 簡單</li>
</ul>
<p><strong>執行順序</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 1（可並行）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├─ Ticket #201: Rating Value Object
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">└─ Ticket #202: Rating Entity （依賴 #201）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Phase 2（可並行）:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├─ Ticket #203: IRatingRepository Interface
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">└─ Ticket #204: SQLiteRatingRepository （依賴 #202, #203）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Phase 3:
</span></span><span class="line"><span class="ln">10</span><span class="cl">└─ Ticket #205: RateBookUseCase （依賴 #204）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">Phase 4:
</span></span><span class="line"><span class="ln">13</span><span class="cl">└─ Ticket #206: RatingWidget UI （依賴 #205）</span></span></code></pre></div><hr>
<h2 id="第四章ticket-大小標準與範例">第四章：Ticket 大小標準與範例</h2>
<h3 id="41-簡單-ticket-標準與範例level-1">4.1 簡單 Ticket 標準與範例（Level 1）</h3>
<p><strong>Level 1 特徵</strong>：</p>
<ul>
<li>職責：1 個明確職責</li>
<li>行數：&lt; 30 行</li>
<li>檔案：1 個</li>
<li>測試：1-3 個</li>
</ul>
<p><strong>適用場景</strong>：</p>
<ul>
<li>Interface 定義</li>
<li>單一 Value Object</li>
<li>單一方法實作</li>
<li>簡單配置修改</li>
</ul>
<hr>
<h4 id="範例-1定義-ibookrepository-介面">範例 1：定義 IBookRepository 介面</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #1: 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 4 (Domain Interfaces)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料存取功能
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IBookRepository`</span> 介面，定義書籍資料存取的契約
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domain/repositories/`</span> 建立 <span class="sb">`i_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`getBookByIsbn(String isbn)`</span> 方法簽名
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`saveBook(Book book)`</span> 方法簽名
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">4.</span> 定義 <span class="sb">`deleteBook(String isbn)`</span> 方法簽名
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">5.</span> 撰寫文檔註解（含需求編號）
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Interface 檔案建立在 <span class="sb">`lib/domain/repositories/`</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 3 個方法簽名完整且明確
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> 輸入輸出類型定義清楚
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 文檔註解包含需求編號 REQ-LIB-001
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：1 個（定義介面契約）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 程式碼行數：~20 行
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 測試用例：0 個（Interface 不需單元測試）
</span></span><span class="line"><span class="ln">30</span><span class="cl">→ Level 1（簡單）
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="gu"></span>10-15 分鐘</span></span></code></pre></div><hr>
<h4 id="範例-2建立-rating-value-object">範例 2：建立 Rating Value Object</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #2: 建立 Rating Value Object
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 5 (Domain Implementation)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>REQ-RATING-001: 書籍評分功能
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`Rating`</span> Value Object，封裝評分規則（1-5 分）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domain/value_objects/`</span> 建立 <span class="sb">`rating.dart`</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">2.</span> 實作 Rating 類別
</span></span><span class="line"><span class="ln">14</span><span class="cl">   <span class="k">-</span> 建構子驗證（1-5 分範圍）
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> equals / hashCode
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> toString
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">3.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 測試 1：建立有效評分
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 測試 2：評分過低拋出異常
</span></span><span class="line"><span class="ln">20</span><span class="cl">   <span class="k">-</span> 測試 3：評分過高拋出異常
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">4.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Rating 類別實作完整
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> 驗證邏輯正確（1-5 分）
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> equals / hashCode / toString 實作正確
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 3 個測試）
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：1 個（實作 Value Object）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 程式碼行數：~25 行（含測試）
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 測試用例：3 個
</span></span><span class="line"><span class="ln">35</span><span class="cl">→ Level 1（簡單）
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="gu"></span>15-20 分鐘</span></span></code></pre></div><hr>
<h3 id="42-中等-ticket-標準與範例level-2">4.2 中等 Ticket 標準與範例（Level 2）</h3>
<p><strong>Level 2 特徵</strong>：</p>
<ul>
<li>職責：2-3 個相關職責</li>
<li>行數：30-70 行</li>
<li>檔案：2-3 個</li>
<li>測試：3-6 個</li>
</ul>
<p><strong>適用場景</strong>：</p>
<ul>
<li>含業務邏輯的 Entity</li>
<li>基礎 Repository 方法</li>
<li>簡單 UseCase</li>
<li>單一功能的 Controller</li>
</ul>
<hr>
<h4 id="範例-3實作-book-entity">範例 3：實作 Book Entity</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #3: 實作 Book Entity
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 5 (Domain Implementation)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料模型
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`Book`</span> Entity，封裝書籍核心資料
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domain/entities/`</span> 建立 <span class="sb">`book.dart`</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">2.</span> 定義 Entity 欄位
</span></span><span class="line"><span class="ln">14</span><span class="cl">   <span class="k">-</span> isbn (String, required)
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> title (String, required)
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> author (String, required)
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> publishDate (DateTime, optional)
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 實作 equals / hashCode（基於 isbn）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">4.</span> 實作 toString
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">5.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">21</span><span class="cl">   <span class="k">-</span> 測試 1：建立有效 Book
</span></span><span class="line"><span class="ln">22</span><span class="cl">   <span class="k">-</span> 測試 2：equals 正確比較（相同 ISBN）
</span></span><span class="line"><span class="ln">23</span><span class="cl">   <span class="k">-</span> 測試 3：equals 正確比較（不同 ISBN）
</span></span><span class="line"><span class="ln">24</span><span class="cl">   <span class="k">-</span> 測試 4：hashCode 一致性
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">6.</span> 確保測試通過
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Book Entity 欄位定義完整
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">- [ ]</span> equals / hashCode 實作正確（基於 isbn）
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> toString 回傳清楚的字串表示
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 4 個測試）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：3 個（欄位定義、equals/hashCode、toString）
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 程式碼行數：~50 行（含測試）
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> 測試用例：4 個
</span></span><span class="line"><span class="ln">39</span><span class="cl">→ Level 2（中等）
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="gu"></span>25-35 分鐘</span></span></code></pre></div><hr>
<h4 id="範例-4實作-getbookusecase">範例 4：實作 GetBookUseCase</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #4: 實作 GetBookUseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 3 (UseCase)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 查詢書籍功能
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`GetBookUseCase`</span>，協調書籍查詢業務流程
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookRepository 介面（必須先完成）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/application/use_cases/`</span> 建立 <span class="sb">`get_book_use_case.dart`</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`GetBookUseCase`</span> 介面
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`GetBookInteractor`</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 注入 <span class="sb">`IBookRepository`</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 實作 <span class="sb">`execute(String isbn)`</span> 方法
</span></span><span class="line"><span class="ln">20</span><span class="cl">   <span class="k">-</span> 處理 Repository 異常
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">4.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">22</span><span class="cl">   <span class="k">-</span> 測試 1：成功查詢書籍
</span></span><span class="line"><span class="ln">23</span><span class="cl">   <span class="k">-</span> 測試 2：書籍不存在回傳 null
</span></span><span class="line"><span class="ln">24</span><span class="cl">   <span class="k">-</span> 測試 3：無效 ISBN 拋出 ValidationException
</span></span><span class="line"><span class="ln">25</span><span class="cl">   <span class="k">-</span> 測試 4：Repository 異常正確處理
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">5.</span> 確保測試通過
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> GetBookInteractor 正確注入 IBookRepository
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> execute 方法實作正確
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 4 個測試）
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：2 個（業務協調、異常處理）
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 程式碼行數：~55 行（含測試）
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> 涉及檔案：1 個
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">-</span> 測試用例：4 個
</span></span><span class="line"><span class="ln">40</span><span class="cl">→ Level 2（中等）
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="gu"></span>30-40 分鐘</span></span></code></pre></div><hr>
<h3 id="43-複雜-ticket-標準與範例level-3">4.3 複雜 Ticket 標準與範例（Level 3）</h3>
<p><strong>Level 3 特徵</strong>：</p>
<ul>
<li>職責：3-5 個相關職責</li>
<li>行數：70-100 行</li>
<li>檔案：3-5 個</li>
<li>測試：6-10 個</li>
</ul>
<p><strong>適用場景</strong>：</p>
<ul>
<li>完整 Repository CRUD</li>
<li>複雜 UseCase（含多重驗證）</li>
<li>複雜業務邏輯實作</li>
</ul>
<p><strong>注意</strong>：Level 3 Ticket 建議優先評估是否可拆分為更小 Ticket</p>
<hr>
<h4 id="範例-5實作-bookrepository-crud建議拆分">範例 5：實作 BookRepository CRUD（建議拆分）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #5: 實作 SQLiteBookRepository CRUD
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 5 (Infrastructure)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>REQ-LIB-001: 書籍資料存取功能
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SQLiteBookRepository`</span>，提供完整的 CRUD 操作
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookRepository 介面（必須先完成）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 複雜度警告
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span>此 Ticket 為 Level 3（複雜），建議拆分為 3 個 Level 2 Ticket：
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Ticket A: 實作 getBookByIsbn + 測試
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> Ticket B: 實作 saveBook + 測試
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> Ticket C: 實作 deleteBook + Data Mapper + 測試
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 步驟（如不拆分）
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/infrastructure/repositories/`</span> 建立 <span class="sb">`sqlite_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">2.</span> 在 <span class="sb">`lib/infrastructure/mappers/`</span> 建立 <span class="sb">`book_mapper.dart`</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`getBookByIsbn`</span> 方法
</span></span><span class="line"><span class="ln">25</span><span class="cl">   <span class="k">-</span> SQL 查詢邏輯
</span></span><span class="line"><span class="ln">26</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換（DTO → Entity）
</span></span><span class="line"><span class="ln">27</span><span class="cl">   <span class="k">-</span> 錯誤處理
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">4.</span> 實作 <span class="sb">`saveBook`</span> 方法
</span></span><span class="line"><span class="ln">29</span><span class="cl">   <span class="k">-</span> SQL 插入/更新邏輯
</span></span><span class="line"><span class="ln">30</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換（Entity → DTO）
</span></span><span class="line"><span class="ln">31</span><span class="cl">   <span class="k">-</span> 錯誤處理
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">5.</span> 實作 <span class="sb">`deleteBook`</span> 方法
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">6.</span> 撰寫完整測試
</span></span><span class="line"><span class="ln">34</span><span class="cl">   <span class="k">-</span> getBookByIsbn: 成功、不存在、異常（3 個測試）
</span></span><span class="line"><span class="ln">35</span><span class="cl">   <span class="k">-</span> saveBook: 新增、更新、異常（3 個測試）
</span></span><span class="line"><span class="ln">36</span><span class="cl">   <span class="k">-</span> deleteBook: 成功、不存在、異常（3 個測試）
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">7.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 實作所有 IBookRepository 方法
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="k">- [ ]</span> Data Mapper 轉換正確
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 9 個測試）
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：5 個（get、save、delete、mapper、異常處理）
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">-</span> 程式碼行數：~160 行（含測試）
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">-</span> 涉及檔案：2 個
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="k">-</span> 測試用例：9 個
</span></span><span class="line"><span class="ln">51</span><span class="cl">→ Level 3（複雜）建議拆分
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="gu"></span>60-90 分鐘
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="gu">### 建議拆分方案
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="gu"></span>拆分為 3 個 Ticket（詳見第五章決策樹）</span></span></code></pre></div><hr>
<h3 id="44-必須拆分標準level-4">4.4 必須拆分標準（Level 4）</h3>
<p><strong>Level 4 特徵</strong>：</p>
<ul>
<li>職責：&gt; 5 個</li>
<li>行數：&gt; 100 行</li>
<li>檔案：&gt; 5 個</li>
<li>測試：&gt; 10 個</li>
</ul>
<p><strong>處理方式</strong>：</p>
<ul>
<li><strong>禁止建立 Level 4 Ticket</strong></li>
<li><strong>必須拆分為多個 Level 1-2 Ticket</strong></li>
<li><strong>拆分後可接受少數 Level 3 Ticket</strong></li>
</ul>
<hr>
<h4 id="範例-6god-ticket-檢測與拆分">範例 6：God Ticket 檢測與拆分</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 禁止：實作完整書籍評分功能
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 原始任務描述
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>實作書籍評分功能，包含 UI、Controller、UseCase、Repository
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### God Ticket 檢測結果
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責數量：8 個 → Level 4
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 程式碼行數：~300 行 → Level 4
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 涉及檔案：8 個 → Level 4
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 測試用例：20 個 → Level 4
</span></span><span class="line"><span class="ln">11</span><span class="cl">→ <span class="gs">**所有指標都超標，必須拆分**</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 拆分決策
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span>使用「Clean Architecture 分層拆分策略」（詳見第三章 3.4）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">拆分為 6 個 Ticket：
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">1.</span> Ticket <span class="ni">#201:</span> Rating Value Object（Level 2）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">2.</span> Ticket <span class="ni">#202:</span> Rating Entity（Level 1）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> Ticket <span class="ni">#203:</span> IRatingRepository Interface（Level 1）
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> Ticket <span class="ni">#204:</span> SQLiteRatingRepository（Level 3）
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> Ticket <span class="ni">#205:</span> RateBookUseCase（Level 2）
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">6.</span> Ticket <span class="ni">#206:</span> RatingWidget UI（Level 2）
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">結果：5 個 Level 1-2 + 1 個 Level 3 全部可接受</span></span></code></pre></div><hr>
<h3 id="45-ticket-大小對照表">4.5 Ticket 大小對照表</h3>
<p><strong>快速參考表</strong>：</p>
<table>
  <thead>
      <tr>
          <th>Level</th>
          <th>職責</th>
          <th>行數</th>
          <th>檔案</th>
          <th>測試</th>
          <th>預估時間</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>1 簡單</strong></td>
          <td>1</td>
          <td>&lt;30</td>
          <td>1</td>
          <td>1-3</td>
          <td>5-20分鐘</td>
          <td>Interface 定義、簡單 VO</td>
      </tr>
      <tr>
          <td><strong>2 中等</strong></td>
          <td>2-3</td>
          <td>30-70</td>
          <td>2-3</td>
          <td>3-6</td>
          <td>20-40分鐘</td>
          <td>Entity、基礎 UseCase</td>
      </tr>
      <tr>
          <td><strong>3 複雜</strong></td>
          <td>3-5</td>
          <td>70-100</td>
          <td>3-5</td>
          <td>6-10</td>
          <td>40-90分鐘</td>
          <td>完整 Repository CRUD</td>
      </tr>
      <tr>
          <td><strong>4 超標</strong></td>
          <td>&gt;5</td>
          <td>&gt;100</td>
          <td>&gt;5</td>
          <td>&gt;10</td>
          <td>N/A</td>
          <td>禁止建立</td>
      </tr>
  </tbody>
</table>
<p><strong>決策建議</strong>：</p>
<ul>
<li>Level 1-2：直接建立 Ticket</li>
<li>Level 3：優先評估是否可拆分</li>
<li>Level 4：必須拆分，無例外</li>
</ul>
<hr>
<h2 id="第五章拆分決策樹">第五章：拆分決策樹</h2>
<h3 id="51-決策樹總覽">5.1 決策樹總覽</h3>
<p><strong>完整決策流程</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">[Ticket 設計階段]
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[計算 4 個量化指標]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">[取最高複雜度等級]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    ├─ Level 1-2
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    │   [直接建立 Ticket]
</span></span><span class="line"><span class="ln">10</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">    │   [進入 Phase 2（測試設計）]
</span></span><span class="line"><span class="ln">12</span><span class="cl">    │
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ├─ Level 3
</span></span><span class="line"><span class="ln">14</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">    │   [拆分評估決策]
</span></span><span class="line"><span class="ln">16</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">    │   ├─ 可拆分？
</span></span><span class="line"><span class="ln">18</span><span class="cl">    │   │   ├─ Yes → [執行拆分策略] → [重新評估]
</span></span><span class="line"><span class="ln">19</span><span class="cl">    │   │   └─ No → [勉強接受] → [標記高風險]
</span></span><span class="line"><span class="ln">20</span><span class="cl">    │   ↓
</span></span><span class="line"><span class="ln">21</span><span class="cl">    │   [進入 Phase 2]
</span></span><span class="line"><span class="ln">22</span><span class="cl">    │
</span></span><span class="line"><span class="ln">23</span><span class="cl">    └─ Level 4
</span></span><span class="line"><span class="ln">24</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">25</span><span class="cl">        [阻止建立 Ticket]
</span></span><span class="line"><span class="ln">26</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">27</span><span class="cl">        [執行強制拆分]
</span></span><span class="line"><span class="ln">28</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">29</span><span class="cl">        [選擇拆分策略]
</span></span><span class="line"><span class="ln">30</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">31</span><span class="cl">        ├─ 優先：按架構分層拆分
</span></span><span class="line"><span class="ln">32</span><span class="cl">        ├─ 次之：按職責拆分
</span></span><span class="line"><span class="ln">33</span><span class="cl">        └─ 最後：按檔案拆分
</span></span><span class="line"><span class="ln">34</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">35</span><span class="cl">        [重新評估所有子 Ticket]
</span></span><span class="line"><span class="ln">36</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">37</span><span class="cl">        [確保所有子 Ticket ≤ Level 3]
</span></span><span class="line"><span class="ln">38</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">39</span><span class="cl">        [可建立 Ticket]</span></span></code></pre></div><hr>
<h3 id="52-level-3-拆分評估決策">5.2 Level 3 拆分評估決策</h3>
<p><strong>決策問題</strong>：此 Level 3 Ticket 是否應該拆分？</p>
<p><strong>評估準則</strong>：</p>
<h4 id="準則-1職責可分離性">準則 1：職責可分離性</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">問題：職責是否可獨立分離為多個 Ticket？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">評估方法：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">1. 列出所有職責
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">2. 檢查職責之間的依賴關係
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">3. 判斷是否可獨立完成
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">原始任務：實作 BookRepository CRUD
</span></span><span class="line"><span class="ln">10</span><span class="cl">職責分析：
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 職責 1：getBookByIsbn → 可獨立
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 職責 2：saveBook → 可獨立（依賴職責 1 的測試模式）
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 職責 3：deleteBook → 可獨立
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 職責 4：Data Mapper → 可整合到職責 1-3
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 職責 5：異常處理 → 可整合到職責 1-3
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">結論：可拆分為 3 個 Ticket（每個職責對應 1 個 Ticket）</span></span></code></pre></div><h4 id="準則-2拆分後複雜度降低">準則 2：拆分後複雜度降低</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">問題：拆分後每個子 Ticket 是否 ≤ Level 2？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">評估方法：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">對每個拆分後的子 Ticket 重新計算 4 個指標
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">原始 Ticket（Level 3）：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 職責：5 個
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 行數：160 行
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 檔案：2 個
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 測試：9 個
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">拆分後 Ticket A：getBookByIsbn
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 職責：2 個（查詢 + 轉換）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 行數：50 行
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 檔案：2 個
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 測試：3 個
</span></span><span class="line"><span class="ln">19</span><span class="cl">→ Level 2
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">拆分後 Ticket B：saveBook
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 職責：2 個（儲存 + 異常處理）
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 行數：60 行
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 檔案：1 個（修改）
</span></span><span class="line"><span class="ln">25</span><span class="cl">- 測試：3 個
</span></span><span class="line"><span class="ln">26</span><span class="cl">→ Level 2
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">拆分後 Ticket C：deleteBook
</span></span><span class="line"><span class="ln">29</span><span class="cl">- 職責：2 個（刪除 + 異常處理）
</span></span><span class="line"><span class="ln">30</span><span class="cl">- 行數：50 行
</span></span><span class="line"><span class="ln">31</span><span class="cl">- 檔案：1 個（修改）
</span></span><span class="line"><span class="ln">32</span><span class="cl">- 測試：3 個
</span></span><span class="line"><span class="ln">33</span><span class="cl">→ Level 2
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">結論：拆分成功，所有子 Ticket 都降為 Level 2</span></span></code></pre></div><h4 id="準則-3拆分成本合理性">準則 3：拆分成本合理性</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">問題：拆分帶來的管理成本是否合理？
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">成本考量：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 收益：風險降低、易於 Review、可並行開發
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- 成本：增加 Ticket 數量、需要管理依賴關係
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">決策：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 拆分收益 &gt; 拆分成本 → 建議拆分
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 拆分收益 ≈ 拆分成本 → 可選擇
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 拆分收益 &lt; 拆分成本 → 不建議拆分
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">15</span><span class="cl">原始 Ticket：Level 3（複雜 Repository CRUD）
</span></span><span class="line"><span class="ln">16</span><span class="cl">拆分收益：
</span></span><span class="line"><span class="ln">17</span><span class="cl">+ 風險降低：1 個高風險 → 3 個低風險
</span></span><span class="line"><span class="ln">18</span><span class="cl">+ Review 效率：每次 Review 50-60 行 vs 160 行
</span></span><span class="line"><span class="ln">19</span><span class="cl">+ 可並行：3 個 Ticket 可依序快速開發
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">拆分成本：
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 管理成本：需要管理 3 個 Ticket 依賴
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 整合測試：需要額外的整合測試（但本來就需要）
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">結論：拆分收益 &gt; 拆分成本，建議拆分</span></span></code></pre></div><hr>
<h3 id="53-level-4-強制拆分策略">5.3 Level 4 強制拆分策略</h3>
<h4 id="level-4-無需評估必須拆分">Level 4 無需評估，必須拆分</h4>
<h4 id="拆分策略優先順序">拆分策略優先順序</h4>
<h5 id="策略-1按-clean-architecture-分層拆分優先">策略 1：按 Clean Architecture 分層拆分（優先）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">適用情況：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Ticket 跨越多個架構層級（Layer 1-5）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 檔案分布在不同層級目錄
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 涉及 UI、UseCase、Repository 等多層
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">拆分方法：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">1. 按照 Layer 1 → Layer 5 順序分組檔案
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">2. 每個 Layer 建立獨立 Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">3. 相鄰兩層可合併為單一 Ticket（如 Interface + Implementation）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">範例（書籍評分功能）：
</span></span><span class="line"><span class="ln">13</span><span class="cl">原始：8 個檔案，跨越 4 層
</span></span><span class="line"><span class="ln">14</span><span class="cl">拆分後：
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Ticket 1: Layer 5 Domain（Rating VO + Entity）
</span></span><span class="line"><span class="ln">17</span><span class="cl">- Ticket 2: Layer 5 + 4 Repository（Interface + Impl）
</span></span><span class="line"><span class="ln">18</span><span class="cl">- Ticket 3: Layer 3 UseCase
</span></span><span class="line"><span class="ln">19</span><span class="cl">- Ticket 4: Layer 1-2 Presentation（UI + Controller）
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">結果：4 個 Level 1-2 Ticket</span></span></code></pre></div><h6 id="策略-2按職責拆分次之">策略 2：按職責拆分（次之）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">適用情況：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 所有檔案在同一層級，但職責過多
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 單一 Repository 包含過多 CRUD 方法
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 單一 UseCase 包含過多業務邏輯
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">拆分方法：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">1. 列出所有職責
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">2. 每個獨立職責建立 Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">3. 相關職責可合併（最多 2-3 個職責）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">範例（BookRepository CRUD）：
</span></span><span class="line"><span class="ln">13</span><span class="cl">原始：5 個職責（get + save + delete + mapper + error）
</span></span><span class="line"><span class="ln">14</span><span class="cl">拆分後：
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Ticket A: getBookByIsbn + mapper + error（2-3 職責）
</span></span><span class="line"><span class="ln">17</span><span class="cl">- Ticket B: saveBook + error（2 職責）
</span></span><span class="line"><span class="ln">18</span><span class="cl">- Ticket C: deleteBook + error（2 職責）
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">結果：3 個 Level 2 Ticket</span></span></code></pre></div><h6 id="策略-3按檔案拆分最後">策略 3：按檔案拆分（最後）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">適用情況：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 前兩種策略都無法適用
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 檔案之間相對獨立
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 每個檔案本身就是一個完整單元
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">拆分方法：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">1. 每個檔案建立獨立 Ticket
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">2. 相關檔案可合併（最多 2-3 個檔案）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">12</span><span class="cl">原始：7 個檔案
</span></span><span class="line"><span class="ln">13</span><span class="cl">拆分後：
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">- Ticket 1: file1.dart + file2.dart（相關）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Ticket 2: file3.dart
</span></span><span class="line"><span class="ln">17</span><span class="cl">- Ticket 3: file4.dart + file5.dart（相關）
</span></span><span class="line"><span class="ln">18</span><span class="cl">- Ticket 4: file6.dart + file7.dart（相關）
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">結果：4 個 Ticket</span></span></code></pre></div><hr>
<h3 id="54-拆分決策檢查清單">5.4 拆分決策檢查清單</h3>
<p><strong>Level 3 拆分評估檢查清單</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已列出所有職責
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已評估職責可分離性
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已計算拆分後每個子 Ticket 的指標
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已確認所有子 Ticket ≤ Level 2
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 已評估拆分成本 vs 收益
</span></span><span class="line"><span class="ln">6</span><span class="cl">□ 已確定是否拆分的最終決策</span></span></code></pre></div><p><strong>Level 4 強制拆分檢查清單</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已識別 Ticket 為 Level 4（任一指標超標）
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已選擇拆分策略（分層 / 職責 / 檔案）
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已執行拆分（列出所有子 Ticket）
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已重新評估所有子 Ticket
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 已確認所有子 Ticket ≤ Level 3
</span></span><span class="line"><span class="ln">6</span><span class="cl">□ 已標記子 Ticket 依賴關係
</span></span><span class="line"><span class="ln">7</span><span class="cl">□ 已確認子 Ticket 總和涵蓋原始功能</span></span></code></pre></div><hr>
<h2 id="第六章ticket-拆分檢查清單">第六章：Ticket 拆分檢查清單</h2>
<h3 id="61-拆分前檢查清單">6.1 拆分前檢查清單</h3>
<h4 id="階段-1需求理解">階段 1：需求理解</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已閱讀完整的業務需求
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已理解 Ticket 的業務目標
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已確認 Ticket 的驗收條件
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已識別所有需要完成的功能點
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 已確認與其他 Ticket 的依賴關係</span></span></code></pre></div><h5 id="階段-2指標計算">階段 2：指標計算</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已列出所有職責（功能點 + 邊界條件 + 異常處理）
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已估算程式碼行數（參考類似任務）
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已列出所有涉及檔案（含新建和修改）
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已估算測試用例數（正常 + 邊界 + 異常）
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 已取最高複雜度等級作為最終評估</span></span></code></pre></div><h6 id="階段-3複雜度確認">階段 3：複雜度確認</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已確定 Ticket 的複雜度等級（Level 1-4）
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 如為 Level 4，已阻止建立並準備拆分
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 如為 Level 3，已評估是否拆分
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 如為 Level 1-2，已確認可直接建立</span></span></code></pre></div><hr>
<h3 id="62-拆分過程檢查清單">6.2 拆分過程檢查清單</h3>
<h4 id="階段-4拆分策略選擇">階段 4：拆分策略選擇</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已分析 Ticket 涉及的架構層級
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已確定拆分策略（分層 / 職責 / 檔案）
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已列出所有拆分後的子 Ticket
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已為每個子 Ticket 撰寫初步描述</span></span></code></pre></div><h5 id="階段-5子-ticket-設計">階段 5：子 Ticket 設計</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 每個子 Ticket 都有明確的目標
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 每個子 Ticket 都有清楚的步驟
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 每個子 Ticket 都有具體的驗收條件
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 每個子 Ticket 都標記了層級（[Layer X]）
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 每個子 Ticket 都標記了依賴關係</span></span></code></pre></div><h6 id="階段-6依賴關係確認">階段 6：依賴關係確認</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已識別所有子 Ticket 之間的依賴
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已確保依賴方向符合 Clean Architecture 規則
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已標記依賴順序（Phase 1 → Phase 2 → ...）
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已確認可並行執行的 Ticket</span></span></code></pre></div><hr>
<h3 id="63-拆分後驗證清單">6.3 拆分後驗證清單</h3>
<h4 id="階段-7指標重新評估">階段 7：指標重新評估</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已重新計算每個子 Ticket 的 4 個指標
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已確認所有子 Ticket ≤ Level 3
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已確認大多數子 Ticket ≤ Level 2
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 如有 Level 3 子 Ticket，已評估合理性</span></span></code></pre></div><h5 id="階段-8完整性驗證">階段 8：完整性驗證</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 所有子 Ticket 功能總和 = 原始 Ticket 功能
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 沒有遺漏任何功能點
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 沒有重複的功能實作
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 所有檔案都被包含在某個子 Ticket 中
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 所有測試案例都被包含</span></span></code></pre></div><h6 id="階段-9品質檢查">階段 9：品質檢查</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 每個子 Ticket 都有業務需求引用
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 每個子 Ticket 都有指標評估
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 每個子 Ticket 都有預估時間
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 每個子 Ticket 的職責明確且不重疊
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 每個子 Ticket 的標題包含 [Layer X] 標籤</span></span></code></pre></div><hr>
<h3 id="64-特殊情況檢查清單">6.4 特殊情況檢查清單</h3>
<h4 id="情況-1無法拆分的-level-3-ticket">情況 1：無法拆分的 Level 3 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已明確記錄為何無法拆分
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已標記為「高風險 Ticket」
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已安排額外的 Code Review
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已增加測試覆蓋率要求（&gt; 90%）
</span></span><span class="line"><span class="ln">5</span><span class="cl">□ 已準備更長的開發時間</span></span></code></pre></div><h5 id="情況-2跨層整合-ticket">情況 2：跨層整合 Ticket</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已確認只涉及相鄰兩層（如 Layer 3 + Layer 4-5）
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ 已確認符合依賴規則（外層 → 內層）
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已明確標記為「整合 Ticket」
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已包含整合測試驗收條件</span></span></code></pre></div><h6 id="情況-3測試獨立-ticket">情況 3：測試獨立 Ticket</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">□ 已確認生產程式碼已完成（依賴 Ticket）
</span></span><span class="line"><span class="ln">2</span><span class="cl">□ Ticket 只包含測試程式碼
</span></span><span class="line"><span class="ln">3</span><span class="cl">□ 已列出所有測試案例（正常 + 邊界 + 異常）
</span></span><span class="line"><span class="ln">4</span><span class="cl">□ 已確認測試數量在合理範圍（<span class="p">&lt;</span> <span class="nt">10</span> <span class="na">個</span><span class="err">）</span></span></span></code></pre></div><hr>
<h2 id="第七章實務案例與最佳實踐">第七章：實務案例與最佳實踐</h2>
<h3 id="71-完整案例書籍搜尋功能">7.1 完整案例：書籍搜尋功能</h3>
<p><strong>業務需求</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">REQ-SEARCH-001: 實作書籍搜尋功能
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">-</span> 使用者可輸入關鍵字搜尋書籍
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 支援書名、作者、ISBN 搜尋
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 顯示搜尋結果列表</span></span></code></pre></div><hr>
<h4 id="初步評估識別為-god-ticket">初步評估：識別為 God Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">原始任務分析：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">實作完整書籍搜尋功能（包含 UI、Controller、UseCase、Repository）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">指標計算：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 職責數量：10 個
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">1.</span> SearchBar UI 元件
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">2.</span> SearchResultList UI 元件
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">3.</span> SearchController 狀態管理
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">4.</span> SearchBookUseCase 業務協調
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">5.</span> Repository 查詢方法（書名搜尋）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">6.</span> Repository 查詢方法（作者搜尋）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="k">7.</span> Repository 查詢方法（ISBN 搜尋）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">8.</span> 錯誤處理
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="k">9.</span> 載入狀態處理
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">10.</span> 空結果處理
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 程式碼行數：~400 行
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> 涉及檔案：10 個
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 測試用例：25 個
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">複雜度評估：
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> 職責：10 個 → Level 4
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">-</span> 行數：400 行 → Level 4
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> 檔案：10 個 → Level 4
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 測試：25 個 → Level 4
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">結論：God Ticket 必須拆分</span></span></code></pre></div><hr>
<h4 id="拆分策略clean-architecture-分層拆分">拆分策略：Clean Architecture 分層拆分</h4>
<h5 id="第一步按層級分組檔案">第一步：按層級分組檔案</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Layer 5 (Domain + Infrastructure):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- SearchQuery Value Object
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- IBookRepository.searchByTitle()
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- IBookRepository.searchByAuthor()
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- IBookRepository.searchByIsbn()
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- SQLiteBookRepository 查詢實作
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Layer 3 (UseCase):
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- SearchBookUseCase
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Layer 2 (Behavior):
</span></span><span class="line"><span class="ln">12</span><span class="cl">- SearchController
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">Layer 1 (UI):
</span></span><span class="line"><span class="ln">15</span><span class="cl">- SearchBar Widget
</span></span><span class="line"><span class="ln">16</span><span class="cl">- SearchResultList Widget</span></span></code></pre></div><h6 id="第二步設計拆分後的-ticket">第二步：設計拆分後的 Ticket</h6>
<hr>
<h6 id="ticket-1-定義-searchquery-value-objectlayer-5">Ticket 1: 定義 SearchQuery Value Object（Layer 5）</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #301: 定義 SearchQuery Value Object
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 5 (Domain Implementation)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`SearchQuery`</span> Value Object，封裝搜尋條件驗證
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domain/value_objects/search_query.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 實作 SearchQuery 類別
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> 驗證查詢字串長度（最少 2 個字元）
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> trim() 處理空白
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> equals / hashCode
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">3.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">4.</span> 確保測試通過
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> SearchQuery 類別實作完整
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> 驗證邏輯正確（最少 2 字元）
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 4 個測試）
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：1 個
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 行數：~30 行
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> 檔案：1 個
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> 測試：4 個
</span></span><span class="line"><span class="ln">32</span><span class="cl">→ Level 1（簡單）
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span>15-20 分鐘</span></span></code></pre></div><hr>
<h4 id="ticket-2-擴充-ibookrepository-查詢方法layer-4">Ticket 2: 擴充 IBookRepository 查詢方法（Layer 4）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #302: 定義 IBookRepository 搜尋方法
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 4 (Domain Interfaces)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 1（Interface 定義）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>在 <span class="sb">`IBookRepository`</span> 介面新增搜尋方法簽名
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`lib/domain/repositories/i_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 新增 <span class="sb">`searchByTitle(String query)`</span> 方法簽名
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">3.</span> 新增 <span class="sb">`searchByAuthor(String query)`</span> 方法簽名
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">4.</span> 新增 <span class="sb">`searchByIsbn(String query)`</span> 方法簽名
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">5.</span> 撰寫文檔註解
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 3 個搜尋方法簽名完整
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> 回傳類型為 <span class="sb">`Future&lt;List&lt;Book&gt;&gt;`</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 文檔註解包含需求編號
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：1 個（定義介面）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 行數：~15 行
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 檔案：1 個（修改）
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 測試：0 個
</span></span><span class="line"><span class="ln">30</span><span class="cl">→ Level 1（簡單）
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="gu"></span>10 分鐘</span></span></code></pre></div><hr>
<h4 id="ticket-3-實作-bookrepository-搜尋方法layer-5">Ticket 3: 實作 BookRepository 搜尋方法（Layer 5）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #303: 實作 SQLiteBookRepository 搜尋
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 5 (Infrastructure)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴：Ticket #301, #302
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SQLiteBookRepository`</span> 的 3 個搜尋方法
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`lib/infrastructure/repositories/sqlite_book_repository.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 實作 <span class="sb">`searchByTitle`</span> 方法
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> SQL LIKE 查詢
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> Data Mapper 轉換
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 錯誤處理
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`searchByAuthor`</span> 方法
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 實作 <span class="sb">`searchByIsbn`</span> 方法
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 撰寫單元測試（每個方法 2-3 個測試）
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">6.</span> 確保測試通過
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 3 個搜尋方法實作完整
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> SQL 查詢使用 LIKE 模糊搜尋
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 8 個測試）
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：4 個（3 個搜尋方法 + 異常處理）
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 行數：~95 行
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 檔案：1 個（修改）
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">-</span> 測試：8 個
</span></span><span class="line"><span class="ln">36</span><span class="cl">→ Level 3（複雜）可接受
</span></span><span class="line"><span class="ln">37</span><span class="cl">（註：3 個搜尋方法邏輯相似，整合實作較有效率）
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu"></span>60-75 分鐘</span></span></code></pre></div><hr>
<h4 id="ticket-4-實作-searchbookusecaselayer-3">Ticket 4: 實作 SearchBookUseCase（Layer 3）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #304: 實作 SearchBookUseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 3 (UseCase)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 4（整合連接）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴：Ticket #302, #303
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SearchBookUseCase`</span>，協調搜尋業務流程
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/application/use_cases/search_book_use_case.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 注入 <span class="sb">`IBookRepository`</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`execute(SearchQuery query, SearchType type)`</span> 方法
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> 根據 SearchType 呼叫對應的 Repository 方法
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 處理空結果
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> 處理異常
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 撰寫整合測試
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 確保測試通過
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> UseCase 正確注入 IBookRepository
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">- [ ]</span> 支援 3 種搜尋類型（Title / Author / ISBN）
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> 整合測試 100% 通過（至少 5 個測試）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：2 個（業務協調、異常處理）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 行數：~65 行
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 檔案：1 個
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 測試：5 個
</span></span><span class="line"><span class="ln">35</span><span class="cl">→ Level 2（中等）
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="gu"></span>35-45 分鐘</span></span></code></pre></div><hr>
<h4 id="ticket-5-實作-searchcontrollerlayer-2">Ticket 5: 實作 SearchController（Layer 2）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #305: 實作 SearchController
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 2 (Behavior)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 4（整合連接）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴：Ticket #304
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SearchController`</span>，管理搜尋狀態
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/presentation/controllers/search_controller.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 注入 <span class="sb">`SearchBookUseCase`</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 實作狀態管理
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> isLoading（載入中）
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> searchResults（搜尋結果）
</span></span><span class="line"><span class="ln">19</span><span class="cl">   <span class="k">-</span> errorMessage（錯誤訊息）
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 實作 <span class="sb">`search(String query, SearchType type)`</span> 方法
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">5.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">6.</span> 確保測試通過
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> SearchController 正確注入 UseCase
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">- [ ]</span> 狀態管理正確（loading / success / error）
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 5 個測試）
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：2 個（狀態管理、UseCase 整合）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 行數：~70 行
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 檔案：1 個
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">-</span> 測試：5 個
</span></span><span class="line"><span class="ln">35</span><span class="cl">→ Level 2（中等）
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="gu"></span>40 分鐘</span></span></code></pre></div><hr>
<h4 id="ticket-6-實作搜尋-ui-元件layer-1">Ticket 6: 實作搜尋 UI 元件（Layer 1）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #306: 實作 SearchBar 和 SearchResultList
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer 1 (UI)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 2（具體實作）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 依賴：Ticket #305
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 業務需求
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>REQ-SEARCH-001: 書籍搜尋功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>實作搜尋介面的 UI 元件
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/presentation/widgets/search_bar.dart`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 實作 SearchBar Widget
</span></span><span class="line"><span class="ln">16</span><span class="cl">   <span class="k">-</span> TextField 輸入框
</span></span><span class="line"><span class="ln">17</span><span class="cl">   <span class="k">-</span> SearchType 選擇器
</span></span><span class="line"><span class="ln">18</span><span class="cl">   <span class="k">-</span> 搜尋按鈕
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> 建立 <span class="sb">`lib/presentation/widgets/search_result_list.dart`</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 實作 SearchResultList Widget
</span></span><span class="line"><span class="ln">21</span><span class="cl">   <span class="k">-</span> 顯示搜尋結果
</span></span><span class="line"><span class="ln">22</span><span class="cl">   <span class="k">-</span> 載入中狀態
</span></span><span class="line"><span class="ln">23</span><span class="cl">   <span class="k">-</span> 空結果提示
</span></span><span class="line"><span class="ln">24</span><span class="cl">   <span class="k">-</span> 錯誤提示
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">5.</span> 撰寫 Widget 測試
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">6.</span> 確保測試通過
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> SearchBar UI 正確顯示和互動
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> SearchResultList 正確顯示各種狀態
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">- [ ]</span> Widget 測試 100% 通過（至少 6 個測試）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">### 指標評估
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span><span class="k">-</span> 職責：2 個（SearchBar、SearchResultList）
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 行數：~90 行
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 檔案：2 個
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> 測試：6 個
</span></span><span class="line"><span class="ln">39</span><span class="cl">→ Level 2（中等）
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="gu">### 預估時間
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="gu"></span>50-60 分鐘</span></span></code></pre></div><hr>
<h4 id="拆分結果總結-1">拆分結果總結</h4>
<p><strong>拆分前後對比</strong>：</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>拆分前（God Ticket）</th>
          <th>拆分後（6 個 Ticket）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>職責數量</td>
          <td>10 個</td>
          <td>平均 1.8 個</td>
      </tr>
      <tr>
          <td>程式碼行數</td>
          <td>~400 行</td>
          <td>平均 61 行</td>
      </tr>
      <tr>
          <td>檔案數量</td>
          <td>10 個</td>
          <td>平均 1.2 個</td>
      </tr>
      <tr>
          <td>測試數量</td>
          <td>25 個</td>
          <td>平均 4.7 個</td>
      </tr>
      <tr>
          <td>複雜度等級</td>
          <td>Level 4</td>
          <td>5 個 Level 1-2<br>1 個 Level 3</td>
      </tr>
      <tr>
          <td>預估總時間</td>
          <td>N/A（無法估算）</td>
          <td>210-250 分鐘</td>
      </tr>
  </tbody>
</table>
<p><strong>拆分效益分析</strong>：</p>
<ol>
<li>
<p><strong>風險大幅降低</strong>：</p>
<ul>
<li>從 1 個無法管理的 God Ticket → 6 個可控 Ticket</li>
<li>單一 Ticket 失敗不影響其他 Ticket</li>
</ul>
</li>
<li>
<p><strong>支援並行開發</strong>：</p>
<ul>
<li>Phase 1: Ticket #301, #302 可並行</li>
<li>Phase 2: Ticket #303 執行</li>
<li>Phase 3: Ticket #304 執行</li>
<li>Phase 4: Ticket #305, #306 可部分並行</li>
</ul>
</li>
<li>
<p><strong>Review 效率提升</strong>：</p>
<ul>
<li>每次 Review 平均 61 行（vs 400 行）</li>
<li>Review 時間縮短 85%</li>
</ul>
</li>
<li>
<p><strong>測試獨立性</strong>：</p>
<ul>
<li>每層可獨立測試</li>
<li>Mock 依賴簡單明確</li>
</ul>
</li>
</ol>
<hr>
<h3 id="72-常見錯誤與解決方案">7.2 常見錯誤與解決方案</h3>
<h4 id="錯誤-1過度拆分">錯誤 1：過度拆分</h4>
<p><strong>問題描述</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">將 Level 2（中等）Ticket 拆分為多個 Level 1 Ticket，
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">反而增加管理成本且沒有實質收益。
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">原始 Ticket（Level 2）：實作 Rating Value Object
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 職責：2 個（建立 + 驗證）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 行數：50 行
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試：5 個
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">過度拆分為 2 個 Ticket：
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> Ticket A：實作 Rating 建構子（Level 1，25 行）
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> Ticket B：實作 Rating 驗證邏輯（Level 1，25 行）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">問題：
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> 兩個 Ticket 高度耦合，必須連續執行
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 增加了額外的管理成本
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 沒有實質的風險降低</span></span></code></pre></div><p><strong>解決方案</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">保持原始 Level 2 Ticket 不拆分
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">判斷準則：
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Level 1-2 Ticket 通常不需要拆分
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 只有 Level 3-4 才需要評估拆分
</span></span><span class="line"><span class="ln">7</span><span class="cl">- 拆分後的子 Ticket 應該相對獨立</span></span></code></pre></div><hr>
<h4 id="錯誤-2拆分後依賴過於複雜">錯誤 2：拆分後依賴過於複雜</h4>
<p><strong>問題描述</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">拆分方式導致依賴關係過於複雜，
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">難以理解和管理執行順序。
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">拆分為 8 個 Ticket，依賴關係如下：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Ticket 1 → Ticket 2 → Ticket 4
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Ticket 1 → Ticket 3 → Ticket 5
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Ticket 2 → Ticket 6
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Ticket 3 → Ticket 6
</span></span><span class="line"><span class="ln">10</span><span class="cl">Ticket 5 → Ticket 7
</span></span><span class="line"><span class="ln">11</span><span class="cl">Ticket 6 → Ticket 7 → Ticket 8
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">問題：
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 依賴網絡複雜，難以追蹤
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> 執行順序不明確
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 容易遺漏依賴</span></span></code></pre></div><p><strong>解決方案</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">使用線性或樹狀依賴結構
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">優化後的依賴關係：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Phase 1（並行）:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├─ Ticket 1, Ticket 2
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Phase 2（並行）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├─ Ticket 3, Ticket 4（依賴 Phase 1）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">Phase 3:
</span></span><span class="line"><span class="ln">11</span><span class="cl">└─ Ticket 5（依賴 Phase 2）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Phase 4:
</span></span><span class="line"><span class="ln">14</span><span class="cl">└─ Ticket 6（依賴 Phase 3）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">判斷準則：
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 優先使用 Phase 分組（線性依賴）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> 同 Phase 內的 Ticket 可並行
</span></span><span class="line"><span class="ln">20</span><span class="cl">- 避免跨 Phase 的複雜依賴</span></span></code></pre></div><hr>
<h4 id="錯誤-3拆分邊界不清晰">錯誤 3：拆分邊界不清晰</h4>
<p><strong>問題描述</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">拆分後的子 Ticket 職責重疊或邊界模糊，
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">導致實作時不知道該在哪個 Ticket 完成。
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">原始：實作完整 BookRepository
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">拆分後：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> Ticket A：實作 getBookByIsbn
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> Ticket B：實作 Data Mapper
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> Ticket C：實作異常處理
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">問題：
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> Ticket A 需要 Data Mapper（依賴 Ticket B）
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> Ticket A 需要異常處理（依賴 Ticket C）
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> 但 Ticket A 應該先完成？
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 邊界模糊，無法獨立完成</span></span></code></pre></div><p><strong>解決方案</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">確保每個子 Ticket 可獨立完成
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">正確拆分：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> Ticket A：實作 getBookByIsbn（含 Mapper + 異常處理）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> Ticket B：實作 saveBook（含 Mapper + 異常處理）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> Ticket C：實作 deleteBook（含異常處理）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">判斷準則：
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 每個子 Ticket 應該是「最小可交付單元」
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 避免將共用邏輯獨立為 Ticket
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 共用邏輯應整合到使用它的 Ticket 中</span></span></code></pre></div><hr>
<h3 id="73-拆分最佳實踐">7.3 拆分最佳實踐</h3>
<h4 id="實踐-1優先選擇架構分層拆分">實踐 1：優先選擇架構分層拆分</h4>
<p><strong>原則</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">當 Ticket 跨越多個架構層級時，
</span></span><span class="line"><span class="ln">2</span><span class="cl">優先按照 Clean Architecture 分層拆分。
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：
</span></span><span class="line"><span class="ln">5</span><span class="cl">依賴方向清晰（內層不依賴外層）
</span></span><span class="line"><span class="ln">6</span><span class="cl">測試策略明確（每層有標準測試方法）
</span></span><span class="line"><span class="ln">7</span><span class="cl">可並行開發（不同層可由不同開發者負責）
</span></span><span class="line"><span class="ln">8</span><span class="cl">符合單一職責原則</span></span></code></pre></div><p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">原始任務：實作書籍評分功能（跨越 4 層）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">錯誤：按功能模組拆分
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> Ticket A：評分輸入功能（UI + Controller + UseCase）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> Ticket B：評分儲存功能（Repository + Mapper）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">問題：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> Ticket A 跨越 3 層，依賴關係複雜
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 測試困難（需要 Mock 多層）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">正確：按架構分層拆分
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> Ticket 1：Rating Domain 模型（Layer 5）
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> Ticket 2：IRatingRepository + Impl（Layer 4-5）
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> Ticket 3：RateBookUseCase（Layer 3）
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> Ticket 4：RatingController + Widget（Layer 1-2）
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">優點：
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 每個 Ticket 職責單一
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> 依賴方向清楚
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 測試獨立簡單</span></span></code></pre></div><hr>
<h4 id="實踐-2保持線性依賴順序">實踐 2：保持線性依賴順序</h4>
<p><strong>原則</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">優先設計線性或樹狀依賴結構，
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">避免複雜的網狀依賴關係。
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">建議結構：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">線性依賴：Ticket 1 → Ticket 2 → Ticket 3 → Ticket 4
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">樹狀依賴：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   Ticket 1
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ├─ Ticket 2
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   │  └─ Ticket 4
</span></span><span class="line"><span class="ln">10</span><span class="cl">   └─ Ticket 3
</span></span><span class="line"><span class="ln">11</span><span class="cl">      └─ Ticket 5
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">避免結構：
</span></span><span class="line"><span class="ln">14</span><span class="cl">網狀依賴：多個交叉依賴，難以追蹤</span></span></code></pre></div><p><strong>實施方法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1：識別「核心 Ticket」
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">-</span> Domain 層 Ticket 通常是核心
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> 其他層依賴 Domain 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2：按照 Clean Architecture 依賴方向排序
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> Layer 5 → Layer 4 → Layer 3 → Layer 2 → Layer 1
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 3：標記 Phase
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> Phase 1：Layer 5（Domain）
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> Phase 2：Layer 4-5（Repository）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> Phase 3：Layer 3（UseCase）
</span></span><span class="line"><span class="ln">12</span><span class="cl">- Phase 4：Layer 1-2（Presentation）</span></span></code></pre></div><hr>
<h4 id="實踐-3每個-ticket-包含完整測試">實踐 3：每個 Ticket 包含完整測試</h4>
<p><strong>原則</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">每個 Ticket 應該包含對應的測試，
</span></span><span class="line"><span class="ln">2</span><span class="cl">不要將測試獨立為單獨 Ticket（除非測試數量 &gt; 10）。
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">優點：
</span></span><span class="line"><span class="ln">5</span><span class="cl">TDD 紅綠燈循環完整
</span></span><span class="line"><span class="ln">6</span><span class="cl">每個 Ticket 交付時就是可測試的
</span></span><span class="line"><span class="ln">7</span><span class="cl">避免「實作完成但測試缺失」的情況</span></span></code></pre></div><p><strong>實施方法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">在 Ticket 驗收條件中明確測試要求：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">## Ticket #X: 實作 Rating Value Object
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Rating 類別實作完整
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過（至少 3 個測試）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">-</span> 測試 1：建立有效評分
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">-</span> 測試 2：評分過低拋出異常
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">-</span> 測試 3：評分過高拋出異常
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">- [ ]</span> 測試覆蓋率 &gt; 90%
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">例外情況（測試獨立 Ticket）：
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> 測試用例 &gt; 10 個（測試複雜度 Level 4）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 補充現有程式碼的測試（非新功能）
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 整合測試（跨多層驗證）</span></span></code></pre></div><hr>
<h4 id="實踐-4明確標記層級和策略">實踐 4：明確標記層級和策略</h4>
<p><strong>原則</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">每個 Ticket 標題應包含：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">1.</span> [Layer X] 標籤（標示架構層級）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">2.</span> 策略類型（Interface / 實作 / 整合）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">格式：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">## Ticket #NNN: [Layer X] {動詞} {目標}
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">好的標題：
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> [Layer 4] 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> [Layer 5] 實作 SQLiteBookRepository
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> [Layer 3] 實作 GetBookUseCase
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">不好的標題：
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> 書籍資料存取功能（沒有層級、目標不明確）
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 實作 Repository（沒有層級、範圍不清楚）</span></span></code></pre></div><p><strong>實施方法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">在 Ticket 範本中包含：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 層級：Layer X (層級名稱)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu">### 策略：策略 N（策略名稱）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">範例：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">## Ticket #101: [Layer 4] 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 層級：Layer 4 (Domain Interfaces)
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 策略：策略 1（Interface 定義）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IBookRepository`</span> 介面，定義書籍資料存取的契約
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">這樣可以：
</span></span><span class="line"><span class="ln">16</span><span class="cl">快速識別 Ticket 的架構位置
</span></span><span class="line"><span class="ln">17</span><span class="cl">理解 Ticket 的目的（定義 / 實作 / 整合）
</span></span><span class="line"><span class="ln">18</span><span class="cl">追蹤依賴關係（內層 → 外層）</span></span></code></pre></div><hr>
<h2 id="附錄-a術語表">附錄 A：術語表</h2>
<table>
  <thead>
      <tr>
          <th>術語</th>
          <th>英文</th>
          <th>定義</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>職責</strong></td>
          <td>Responsibility</td>
          <td>Ticket 需要完成的獨立功能點或邊界條件</td>
          <td>定義介面、實作方法、處理異常</td>
      </tr>
      <tr>
          <td><strong>複雜度等級</strong></td>
          <td>Complexity Level</td>
          <td>基於 4 個量化指標計算的任務複雜度（Level 1-4）</td>
          <td>Level 2（中等）</td>
      </tr>
      <tr>
          <td><strong>God Ticket</strong></td>
          <td>God Ticket</td>
          <td>範圍過大、無法管理的任務（Level 4）</td>
          <td>包含 8 個檔案、300 行程式碼的任務</td>
      </tr>
      <tr>
          <td><strong>單層修改原則</strong></td>
          <td>Single Layer Modification</td>
          <td>每個 Ticket 應只修改單一架構層級</td>
          <td>Ticket 只修改 Domain 層</td>
      </tr>
      <tr>
          <td><strong>依賴規則</strong></td>
          <td>Dependency Rule</td>
          <td>Clean Architecture 的依賴方向規則（外層→內層）</td>
          <td>UseCase 依賴 Repository Interface</td>
      </tr>
      <tr>
          <td><strong>最小可交付單元</strong></td>
          <td>Minimum Deliverable Unit</td>
          <td>可獨立完成、測試、交付的最小功能單元</td>
          <td>實作單一 Value Object</td>
      </tr>
      <tr>
          <td><strong>指標整合評估</strong></td>
          <td>Integrated Indicator Assessment</td>
          <td>取 4 個指標中最高的複雜度等級</td>
          <td>max(Level 2, Level 3, Level 1, Level 2) = Level 3</td>
      </tr>
      <tr>
          <td><strong>拆分策略</strong></td>
          <td>Splitting Strategy</td>
          <td>將大任務拆分為小任務的標準方法</td>
          <td>按架構分層拆分</td>
      </tr>
      <tr>
          <td><strong>Phase</strong></td>
          <td>Phase</td>
          <td>依賴順序的執行階段</td>
          <td>Phase 1（Domain 層）→ Phase 2（Repository 層）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="附錄-b拆分決策模板">附錄 B：拆分決策模板</h2>
<p><strong>模板用途</strong>：用於記錄 Ticket 拆分的決策過程</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># Ticket 拆分決策記錄
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 基本資訊
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> **原始任務**：[任務描述]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> **業務需求**：[需求編號]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> **評估日期**：YYYY-MM-DD
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> **評估人員**：[姓名]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">## 指標評估
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 職責數量
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> **職責列表**：
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="k">1.</span> [職責 1]
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">2.</span> [職責 2]
</span></span><span class="line"><span class="ln">15</span><span class="cl">  ...
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> **總計**：X 個
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> **等級**：Level X
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 程式碼行數
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">-</span> **預估行數**：X 行
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> **依據**：[參考類似任務或經驗估算]
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">-</span> **等級**：Level X
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 涉及檔案
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">-</span> **檔案列表**：
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="k">1.</span> [檔案路徑 1]
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="k">2.</span> [檔案路徑 2]
</span></span><span class="line"><span class="ln">28</span><span class="cl">  ...
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> **總計**：X 個
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> **等級**：Level X
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu">### 測試用例數
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="gu"></span><span class="k">-</span> **測試列表**：
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="k">1.</span> [測試案例 1]
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="k">2.</span> [測試案例 2]
</span></span><span class="line"><span class="ln">36</span><span class="cl">  ...
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> **總計**：X 個
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> **等級**：Level X
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu">## 複雜度結論
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="gu"></span><span class="k">-</span> **最高等級**：Level X
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">-</span> **最終判定**：[簡單 / 中等 / 複雜 / 必須拆分]
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="gu">## 拆分決策
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="gu">### 決策結果
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 不拆分（Level 1-2）
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">- [ ]</span> 評估拆分（Level 3）
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">- [ ]</span> 強制拆分（Level 4）
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="gu">### 拆分原因（如適用）
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="gu"></span>[說明為什麼需要拆分]
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="gu">### 拆分策略（如適用）
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 按架構分層拆分（優先）
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">- [ ]</span> 按職責拆分（次之）
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="k">- [ ]</span> 按檔案拆分（最後）
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="gu">## 拆分方案（如適用）
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="gu">### 子 Ticket 列表
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="gs">**Ticket A**</span>：[標題]
</span></span><span class="line"><span class="ln">63</span><span class="cl">   <span class="k">-</span> 層級：Layer X
</span></span><span class="line"><span class="ln">64</span><span class="cl">   <span class="k">-</span> 指標：職責 X、行數 X、檔案 X、測試 X
</span></span><span class="line"><span class="ln">65</span><span class="cl">   <span class="k">-</span> 等級：Level X
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="k">2.</span> <span class="gs">**Ticket B**</span>：[標題]
</span></span><span class="line"><span class="ln">68</span><span class="cl">   <span class="k">-</span> 層級：Layer X
</span></span><span class="line"><span class="ln">69</span><span class="cl">   <span class="k">-</span> 指標：職責 X、行數 X、檔案 X、測試 X
</span></span><span class="line"><span class="ln">70</span><span class="cl">   <span class="k">-</span> 等級：Level X
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="gu">### 依賴關係
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="gu"></span>```text
</span></span><span class="line"><span class="ln">74</span><span class="cl">[依賴關係圖或順序說明]</span></span></code></pre></div><h3 id="執行順序">執行順序</h3>
<ul>
<li>Phase 1：[Ticket A, Ticket B]</li>
<li>Phase 2：[Ticket C]</li>
<li>&hellip;</li>
</ul>
<h2 id="驗證清單">驗證清單</h2>
<h3 id="拆分前檢查">拆分前檢查</h3>
<ul>
<li><input disabled="" type="checkbox"> 已計算所有 4 個指標</li>
<li><input disabled="" type="checkbox"> 已確定複雜度等級</li>
<li><input disabled="" type="checkbox"> 已確定是否拆分</li>
</ul>
<h3 id="拆分後檢查如適用">拆分後檢查（如適用）</h3>
<ul>
<li><input disabled="" type="checkbox"> 所有子 Ticket 已設計</li>
<li><input disabled="" type="checkbox"> 所有子 Ticket ≤ Level 3</li>
<li><input disabled="" type="checkbox"> 依賴關係明確</li>
<li><input disabled="" type="checkbox"> 功能完整性確認</li>
</ul>
<h2 id="備註">備註</h2>
<p>[其他需要記錄的資訊]</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">## 附錄 C：與其他方法論的整合指引
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">### C.1 與 TDD 四階段方法論整合
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">**整合點**：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">```markdown
</span></span><span class="line"><span class="ln">11</span><span class="cl">本方法論位於 TDD Phase 1（設計階段）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">TDD 流程整合：
</span></span><span class="line"><span class="ln">14</span><span class="cl">Phase 1: Ticket 設計
</span></span><span class="line"><span class="ln">15</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">16</span><span class="cl">  使用本方法論評估 Ticket 複雜度
</span></span><span class="line"><span class="ln">17</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">18</span><span class="cl">  如為 Level 3-4，執行拆分
</span></span><span class="line"><span class="ln">19</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">20</span><span class="cl">  所有 Ticket 確認為 Level 1-2
</span></span><span class="line"><span class="ln">21</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">22</span><span class="cl">Phase 2: 測試設計（sage-test-architect）
</span></span><span class="line"><span class="ln">23</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">24</span><span class="cl">Phase 3: 實作（pepper → parsley）
</span></span><span class="line"><span class="ln">25</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">26</span><span class="cl">Phase 4: 重構（cinnamon-refactor-owl）</span></span></code></pre></div><p><strong>協作方式</strong>：</p>
<ul>
<li>Phase 1 完成時，所有 Ticket 都應該 ≤ Level 3</li>
<li>理想狀態：所有 Ticket 都是 Level 1-2</li>
<li>如有 Level 3 Ticket，應標記為「需要額外 Review」</li>
</ul>
<hr>
<h3 id="c2-與層級隔離派工方法論整合">C.2 與層級隔離派工方法論整合</h3>
<p><strong>整合點</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本方法論提供「拆分標準」
</span></span><span class="line"><span class="ln">2</span><span class="cl">層級隔離派工方法論提供「派工策略」
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">協作流程：
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">1.</span> 使用本方法論拆分 Ticket（按架構分層）
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">2.</span> 使用層級隔離派工方法論決定執行順序
</span></span><span class="line"><span class="ln">7</span><span class="cl">   <span class="k">-</span> 優先派發內層 Ticket（Layer 5）
</span></span><span class="line"><span class="ln">8</span><span class="cl">   <span class="k">-</span> 逐步派發外層 Ticket（Layer 1）
</span></span><span class="line"><span class="ln">9</span><span class="cl">3. 確保依賴順序符合 Clean Architecture 規則</span></span></code></pre></div><p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">拆分結果（本方法論）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> Ticket 1: Layer 5 Domain
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> Ticket 2: Layer 4-5 Repository
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> Ticket 3: Layer 3 UseCase
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> Ticket 4: Layer 1-2 Presentation
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">派工順序（層級隔離派工方法論）：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Week 1:
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ 派發 Ticket 1（Layer 5）給 Developer A
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├─ Ticket 1 完成後，派發 Ticket 2（Layer 4-5）給 Developer A
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Week 2:
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ├─ Ticket 2 完成後，派發 Ticket 3（Layer 3）給 Developer B
</span></span><span class="line"><span class="ln">15</span><span class="cl">  └─ 可同時派發 Ticket 4（Layer 1-2）給 Developer C（部分並行）</span></span></code></pre></div><hr>
<h3 id="c3-與-code-smell-品質閘門檢測方法論整合">C.3 與 Code Smell 品質閘門檢測方法論整合</h3>
<p><strong>整合點</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">本方法論在 Ticket 設計階段執行（Design Time）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Code Smell 品質閘門在 Ticket 執行後檢測（Runtime）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">協作流程：
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">設計階段（本方法論）：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 計算 4 個指標
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ├─ 評估複雜度等級
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ 決定是否拆分
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ 確保 Ticket ≤ Level 3
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">執行階段（Code Smell 檢測）：
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ C1 檢測：God Ticket（檔案數、層級跨度）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ├─ C2 檢測：Incomplete Ticket（缺失元素）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ C3 檢測：Ambiguous Responsibility（職責不明）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">如果 Code Smell 檢測失敗：
</span></span><span class="line"><span class="ln">17</span><span class="cl">  ├─ C1 失敗 → 返回本方法論重新拆分
</span></span><span class="line"><span class="ln">18</span><span class="cl">  ├─ C2 失敗 → 補充缺失元素
</span></span><span class="line"><span class="ln">19</span><span class="cl">  └─ C3 失敗 → 明確職責定義</span></span></code></pre></div><p><strong>防範措施</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">如果在設計階段使用本方法論：
</span></span><span class="line"><span class="ln">2</span><span class="cl">C1 God Ticket 失敗率應該降至 0%
</span></span><span class="line"><span class="ln">3</span><span class="cl">C3 Ambiguous Responsibility 失敗率應該降至接近 0%
</span></span><span class="line"><span class="ln">4</span><span class="cl">C2 Incomplete Ticket 仍需要 Code Smell 檢測補強</span></span></code></pre></div><hr>
<h3 id="c4-與-clean-architecture-實作方法論整合">C.4 與 Clean Architecture 實作方法論整合</h3>
<p><strong>整合點</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本方法論的「架構分層拆分策略」（第三章）
</span></span><span class="line"><span class="ln">2</span><span class="cl">完全基於 Clean Architecture 實作方法論的五層架構。
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">依賴關係：
</span></span><span class="line"><span class="ln">5</span><span class="cl">本方法論 → 依賴 Clean Architecture 實作方法論
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 採用相同的五層架構定義
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> 遵循相同的依賴規則
</span></span><span class="line"><span class="ln">8</span><span class="cl">- 使用相同的檔案路徑規範</span></span></code></pre></div><p><strong>參考章節對應</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">本方法論第三章「Clean Architecture 分層拆分策略」
</span></span><span class="line"><span class="ln">2</span><span class="cl">↓ 引用自
</span></span><span class="line"><span class="ln">3</span><span class="cl">Clean Architecture 實作方法論：
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 第一章：五層架構定義
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 第二章：依賴規則
</span></span><span class="line"><span class="ln">7</span><span class="cl">- 第三章：檔案組織規範</span></span></code></pre></div><hr>
<h2 id="總結">總結</h2>
<h3 id="方法論核心價值">方法論核心價值</h3>
<p>本方法論提供了<strong>量化、客觀、可執行</strong>的 Ticket 拆分標準，解決了傳統拆分方法的主觀性和不一致性問題。</p>
<h3 id="快速開始指引">快速開始指引</h3>
<p><strong>第一步</strong>：評估 Ticket 複雜度</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">1.</span> 計算 4 個指標（職責、行數、檔案、測試）
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">2.</span> 取最高複雜度等級
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 判斷是否需要拆分</span></span></code></pre></div><p><strong>第二步</strong>：執行拆分（如需要）</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">1.</span> 選擇拆分策略（優先：架構分層）
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">2.</span> 設計子 Ticket
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">3.</span> 重新評估每個子 Ticket
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 確認所有子 Ticket ≤ Level 3</span></span></code></pre></div><p><strong>第三步</strong>：驗證和記錄</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">1.</span> 使用檢查清單驗證（第六章）
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">2.</span> 記錄決策過程（附錄 B 模板）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 進入 TDD Phase 2（測試設計）</span></span></code></pre></div><h3 id="相關方法論引用">相關方法論引用</h3>
<p>完整的 Ticket 設計派工流程請參考：</p>
<ul>
<li><a href="/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/" data-link-title="Ticket 設計派工方法論" data-link-desc="我們如何用 Ticket 機制解決大型開發任務的協作困境：從工作日誌臃腫到積極派發的實踐歷程。">Ticket 設計派工方法論</a> - 主方法論</li>
<li><a href="/blog/record/clean-architecture-%E5%AF%A6%E4%BD%9C%E6%8C%87%E5%BC%95/" data-link-title="Clean Architecture 實作指引" data-link-desc="我們在 AI 協作開發中引入 Clean Architecture 作為任務分派的核心判斷框架。這篇文章整理了四層架構的設計順序、實作順序，以及我們實際執行時的關鍵檢查點。">Clean Architecture 實作方法論</a> - 架構基礎</li>
<li><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> - 派工策略</li>
<li>TDD 四階段方法論 - 開發流程</li>
</ul>
<hr>
]]></content:encoded></item><item><title>Code Smell 檢查清單</title><link>https://tarrragon.github.io/blog/record/code-smell-%E6%AA%A2%E6%9F%A5%E6%B8%85%E5%96%AE/</link><pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/code-smell-%E6%AA%A2%E6%9F%A5%E6%B8%85%E5%96%AE/</guid><description>&lt;h2 id="基本資訊">基本資訊&lt;/h2>
&lt;p>&lt;strong>目的&lt;/strong>: 提供基於 Ticket 粒度的 Code Smell 檢測標準和檢查清單&lt;/p>
&lt;p>&lt;strong>與其他方法論的關係&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>引用&lt;a href="https://tarrragon.github.io/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論&lt;/a> 的五層架構定義&lt;/li>
&lt;li>配合 TDD 四階段流程使用&lt;/li>
&lt;li>整合到 Hook 系統自動化檢測&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>核心理念&lt;/strong>:
Code Smell 檢查清單是基於層級隔離原則的程式品質檢測工具，從 Ticket 設計階段就能發現潛在的架構問題，實現「預防勝於治療」的品質管理策略。&lt;/p>
&lt;hr>
&lt;h2 id="第一章code-smell-概述和分類">第一章：Code Smell 概述和分類&lt;/h2>
&lt;h3 id="11-什麼是-code-smell">1.1 什麼是 Code Smell&lt;/h3>
&lt;p>&lt;strong>定義&lt;/strong>: Code Smell（程式異味）是指程式碼中表面上看似正常，但實際上暗示設計問題或潛在缺陷的特徵。&lt;/p>
&lt;p>&lt;strong>核心特性&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>不是 Bug&lt;/strong>: Code Smell 不會導致程式崩潰或功能錯誤&lt;/li>
&lt;li>&lt;strong>設計問題&lt;/strong>: 暗示程式架構或設計上的缺陷&lt;/li>
&lt;li>&lt;strong>可檢測&lt;/strong>: 透過明確的指標可以識別&lt;/li>
&lt;li>&lt;strong>可修正&lt;/strong>: 透過重構可以消除&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>與 Bug 的區別&lt;/strong>:&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">Bug（程式錯誤）:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">- 導致功能失敗或程式崩潰
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">- 需要立即修正
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">- 透過測試失敗發現
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">Code Smell（程式異味）:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">- 程式功能正常運作
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">- 暗示設計問題，未來可能導致維護困難
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">- 透過程式碼檢視或靜態分析發現
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">- 透過重構改善&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>為什麼 Code Smell 重要&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>降低維護成本&lt;/strong>: 及早發現設計問題，避免技術債務累積&lt;/li>
&lt;li>&lt;strong>提升程式碼品質&lt;/strong>: 改善可讀性、可測試性、可擴展性&lt;/li>
&lt;li>&lt;strong>預防未來問題&lt;/strong>: 在問題惡化前進行修正&lt;/li>
&lt;li>&lt;strong>團隊協作&lt;/strong>: 提供統一的品質標準和溝通語言&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h3 id="12-為什麼需要-code-smell-檢查清單">1.2 為什麼需要 Code Smell 檢查清單&lt;/h3>
&lt;p>&lt;strong>傳統問題&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>依賴個人經驗判斷 Code Smell（主觀且不一致）&lt;/li>
&lt;li>問題發現太晚（實作完成後才發現設計缺陷）&lt;/li>
&lt;li>缺少量化標準（難以判斷是否需要重構）&lt;/li>
&lt;li>修正成本高（架構問題需要大規模修改）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>檢查清單優勢&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>標準化&lt;/strong>: 提供統一的檢測標準，避免主觀判斷&lt;/li>
&lt;li>&lt;strong>及早發現&lt;/strong>: 從 Ticket 設計階段就能發現問題&lt;/li>
&lt;li>&lt;strong>量化指標&lt;/strong>: 明確的數字標準（如行數、層級跨度）&lt;/li>
&lt;li>&lt;strong>降低成本&lt;/strong>: 設計階段修正成本遠低於實作後修正&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Ticket 粒度檢測的價值&lt;/strong>:&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">設計階段檢測 vs 實作階段檢測：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">設計階段（Ticket 粒度）:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">- 修正成本: 低（只需要調整設計）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">- 影響範圍: 小（尚未實作程式碼）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">- 風險: 低（無需修改既有程式碼）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">實作階段（Code Review）:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">- 修正成本: 中（需要重寫部分程式碼）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">- 影響範圍: 中（可能影響多個檔案）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">- 風險: 中（需要回歸測試）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">維護階段（上線後）:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">- 修正成本: 高（需要大規模重構）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">- 影響範圍: 大（可能影響多個模組）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">- 風險: 高（可能引入新 Bug）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h3 id="13-code-smell-分類體系">1.3 Code Smell 分類體系&lt;/h3>
&lt;p>基於&lt;a href="https://tarrragon.github.io/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論&lt;/a> 的五層架構，本檢查清單將 Code Smell 分為三大類：&lt;/p>
&lt;h4 id="分類-a跨層級-code-smells違反層級隔離原則">&lt;strong>分類 A：跨層級 Code Smells&lt;/strong>（違反層級隔離原則）&lt;/h4>
&lt;p>這類 Code Smell 涉及多個架構層級，違反層級隔離和單層修改原則：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>A1. Shotgun Surgery&lt;/strong>（散彈槍手術）- 單一變更需要修改多個層級&lt;/li>
&lt;li>&lt;strong>A2. Feature Envy&lt;/strong>（功能嫉妒）- 外層過度依賴內層實作細節&lt;/li>
&lt;li>&lt;strong>A3. Inappropriate Intimacy&lt;/strong>（不當親密關係）- 層級間過度耦合&lt;/li>
&lt;li>&lt;strong>A4. Leaky Abstraction&lt;/strong>（抽象滲漏）- 內層實作細節洩漏到外層&lt;/li>
&lt;/ul>
&lt;h4 id="分類-b單層級-code-smells違反單一職責原則">&lt;strong>分類 B：單層級 Code Smells&lt;/strong>（違反單一職責原則）&lt;/h4>
&lt;p>這類 Code Smell 發生在單一層級內，違反單一職責原則（SRP）：&lt;/p></description><content:encoded><![CDATA[<h2 id="基本資訊">基本資訊</h2>
<p><strong>目的</strong>: 提供基於 Ticket 粒度的 Code Smell 檢測標準和檢查清單</p>
<p><strong>與其他方法論的關係</strong>:</p>
<ul>
<li>引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a>  的五層架構定義</li>
<li>配合 TDD 四階段流程使用</li>
<li>整合到 Hook 系統自動化檢測</li>
</ul>
<p><strong>核心理念</strong>:
Code Smell 檢查清單是基於層級隔離原則的程式品質檢測工具，從 Ticket 設計階段就能發現潛在的架構問題，實現「預防勝於治療」的品質管理策略。</p>
<hr>
<h2 id="第一章code-smell-概述和分類">第一章：Code Smell 概述和分類</h2>
<h3 id="11-什麼是-code-smell">1.1 什麼是 Code Smell</h3>
<p><strong>定義</strong>: Code Smell（程式異味）是指程式碼中表面上看似正常，但實際上暗示設計問題或潛在缺陷的特徵。</p>
<p><strong>核心特性</strong>:</p>
<ul>
<li><strong>不是 Bug</strong>: Code Smell 不會導致程式崩潰或功能錯誤</li>
<li><strong>設計問題</strong>: 暗示程式架構或設計上的缺陷</li>
<li><strong>可檢測</strong>: 透過明確的指標可以識別</li>
<li><strong>可修正</strong>: 透過重構可以消除</li>
</ul>
<p><strong>與 Bug 的區別</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Bug（程式錯誤）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 導致功能失敗或程式崩潰
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 需要立即修正
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 透過測試失敗發現
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Code Smell（程式異味）:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 程式功能正常運作
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 暗示設計問題，未來可能導致維護困難
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 透過程式碼檢視或靜態分析發現
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 透過重構改善</span></span></code></pre></div><p><strong>為什麼 Code Smell 重要</strong>:</p>
<ol>
<li><strong>降低維護成本</strong>: 及早發現設計問題，避免技術債務累積</li>
<li><strong>提升程式碼品質</strong>: 改善可讀性、可測試性、可擴展性</li>
<li><strong>預防未來問題</strong>: 在問題惡化前進行修正</li>
<li><strong>團隊協作</strong>: 提供統一的品質標準和溝通語言</li>
</ol>
<hr>
<h3 id="12-為什麼需要-code-smell-檢查清單">1.2 為什麼需要 Code Smell 檢查清單</h3>
<p><strong>傳統問題</strong>:</p>
<ul>
<li>依賴個人經驗判斷 Code Smell（主觀且不一致）</li>
<li>問題發現太晚（實作完成後才發現設計缺陷）</li>
<li>缺少量化標準（難以判斷是否需要重構）</li>
<li>修正成本高（架構問題需要大規模修改）</li>
</ul>
<p><strong>檢查清單優勢</strong>:</p>
<ul>
<li><strong>標準化</strong>: 提供統一的檢測標準，避免主觀判斷</li>
<li><strong>及早發現</strong>: 從 Ticket 設計階段就能發現問題</li>
<li><strong>量化指標</strong>: 明確的數字標準（如行數、層級跨度）</li>
<li><strong>降低成本</strong>: 設計階段修正成本遠低於實作後修正</li>
</ul>
<p><strong>Ticket 粒度檢測的價值</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">設計階段檢測 vs 實作階段檢測：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">設計階段（Ticket 粒度）:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 修正成本: 低（只需要調整設計）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 影響範圍: 小（尚未實作程式碼）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- 風險: 低（無需修改既有程式碼）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">實作階段（Code Review）:
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 修正成本: 中（需要重寫部分程式碼）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 影響範圍: 中（可能影響多個檔案）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 風險: 中（需要回歸測試）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">維護階段（上線後）:
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 修正成本: 高（需要大規模重構）
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 影響範圍: 大（可能影響多個模組）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 風險: 高（可能引入新 Bug）</span></span></code></pre></div><hr>
<h3 id="13-code-smell-分類體系">1.3 Code Smell 分類體系</h3>
<p>基於<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a>  的五層架構，本檢查清單將 Code Smell 分為三大類：</p>
<h4 id="分類-a跨層級-code-smells違反層級隔離原則"><strong>分類 A：跨層級 Code Smells</strong>（違反層級隔離原則）</h4>
<p>這類 Code Smell 涉及多個架構層級，違反層級隔離和單層修改原則：</p>
<ul>
<li><strong>A1. Shotgun Surgery</strong>（散彈槍手術）- 單一變更需要修改多個層級</li>
<li><strong>A2. Feature Envy</strong>（功能嫉妒）- 外層過度依賴內層實作細節</li>
<li><strong>A3. Inappropriate Intimacy</strong>（不當親密關係）- 層級間過度耦合</li>
<li><strong>A4. Leaky Abstraction</strong>（抽象滲漏）- 內層實作細節洩漏到外層</li>
</ul>
<h4 id="分類-b單層級-code-smells違反單一職責原則"><strong>分類 B：單層級 Code Smells</strong>（違反單一職責原則）</h4>
<p>這類 Code Smell 發生在單一層級內，違反單一職責原則（SRP）：</p>
<ul>
<li><strong>B1. Divergent Change</strong>（發散式變更）- 單一類別承擔多個職責</li>
<li><strong>B2. Large Class</strong>（大類別）- 類別過大，職責不清</li>
<li><strong>B3. Long Method</strong>（長方法）- 方法過長，難以理解</li>
<li><strong>B4. Dead Code</strong>（死程式碼）- 永遠不會執行的程式碼</li>
</ul>
<h4 id="分類-cticket-粒度相關-code-smells"><strong>分類 C：Ticket 粒度相關 Code Smells</strong></h4>
<p>這類 Code Smell 與 Ticket 設計和粒度相關：</p>
<ul>
<li><strong>C1. God Ticket</strong>（全能 Ticket）- Ticket 範圍過大，修改過多檔案</li>
<li><strong>C2. Incomplete Ticket</strong>（不完整 Ticket）- Ticket 缺少必要測試或文件</li>
<li><strong>C3. Ambiguous Responsibility</strong>（職責模糊 Ticket）- Ticket 職責定義不明確</li>
</ul>
<h4 id="分類樹狀結構">分類樹狀結構</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Code Smell 分類體系（基於[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 2.2 節五層架構定義）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">A. 跨層級 Code Smells（違反層級隔離）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ├─ A1. Shotgun Surgery（散彈槍手術）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ├─ A2. Feature Envy（功能嫉妒）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ├─ A3. Inappropriate Intimacy（不當親密關係）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   └─ A4. Leaky Abstraction（抽象滲漏）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">B. 單層級 Code Smells（違反單一職責）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ├─ B1. Divergent Change（發散式變更）
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ├─ B2. Large Class（大類別）
</span></span><span class="line"><span class="ln">12</span><span class="cl">   ├─ B3. Long Method（長方法）
</span></span><span class="line"><span class="ln">13</span><span class="cl">   └─ B4. Dead Code（死程式碼）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">C. Ticket 粒度相關 Code Smells
</span></span><span class="line"><span class="ln">16</span><span class="cl">   ├─ C1. God Ticket（全能 Ticket）
</span></span><span class="line"><span class="ln">17</span><span class="cl">   ├─ C2. Incomplete Ticket（不完整 Ticket）
</span></span><span class="line"><span class="ln">18</span><span class="cl">   └─ C3. Ambiguous Responsibility（職責模糊 Ticket）</span></span></code></pre></div><hr>
<h3 id="14-與層級隔離派工方法論-的關係">1.4 與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關係</h3>
<p><strong>互補關係</strong>:</p>
<ul>
<li>
<p><strong><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a></strong>: 定義「應該怎麼做」（正面原則）</p>
<ul>
<li>五層架構定義（Layer 1-5）</li>
<li>單層修改原則</li>
<li>Ticket 粒度標準</li>
</ul>
</li>
<li>
<p><strong>本檢查清單</strong>: 定義「不應該怎麼做」（負面模式識別）</p>
<ul>
<li>Code Smell 檢測方法</li>
<li>違規模式識別</li>
<li>重構策略</li>
</ul>
</li>
</ul>
<p><strong>引用關係</strong>:
本檢查清單引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a>  的以下章節：</p>
<ul>
<li><strong>2.2 節</strong>: 五層架構完整定義</li>
<li><strong>2.3 節</strong>: 依賴方向規則</li>
<li><strong>3.1 節</strong>: 單層修改原則定義</li>
<li><strong>5.2 節</strong>: Ticket 粒度量化指標</li>
<li><strong>6.2 節</strong>: 檔案路徑分析法</li>
<li><strong>6.5 節</strong>: 違規模式識別</li>
</ul>
<p><strong>無重複定義</strong>:
本文件不重複定義五層架構，所有層級定義都引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 2.2 節。</p>
<hr>
<h2 id="第二章基於層級隔離的-code-smell-定義">第二章：基於層級隔離的 Code Smell 定義</h2>
<h3 id="21-a-類-code-smell跨層級問題">2.1 A 類 Code Smell（跨層級問題）</h3>
<h4 id="211-a1-shotgun-surgery散彈槍手術">2.1.1 A1. Shotgun Surgery（散彈槍手術）</h4>
<p><strong>定義</strong>:
單一邏輯變更需要同時修改多個架構層級的程式碼，違反「單層修改原則」（<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 3.1 節）。</p>
<p><strong>特徵識別</strong>:</p>
<ol>
<li>一個小需求需要修改 UI、Behavior、UseCase、Domain 多層</li>
<li>層級間缺乏適當的抽象介面</li>
<li>變更影響範圍不可控</li>
<li>檔案修改數量 &gt; 5 個且跨 2 個以上層級</li>
</ol>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">單層修改原則</a>（3.1 節）</li>
<li>違反<a href="/blog/record/clean-architecture-%E5%AF%A6%E4%BD%9C%E6%8C%87%E5%BC%95/" data-link-title="Clean Architecture 實作指引" data-link-desc="我們在 AI 協作開發中引入 Clean Architecture 作為任務分派的核心判斷框架。這篇文章整理了四層架構的設計順序、實作順序，以及我們實際執行時的關鍵檢查點。">從外而內實作順序</a>（4.1 節）</li>
<li>未遵循<a href="/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/" data-link-title="Ticket 設計派工方法論" data-link-desc="本方法論提供完整的 Ticket 設計和派工機制，解決大型開發任務的協作效率問題，降低實作偏差風險">Ticket 粒度標準</a>（5.2 節）</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">需求：書籍新增「出版社」欄位
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">反例 Shotgun Surgery 模式：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- Layer 1 (UI): BookDetailWidget 新增 publisher Text
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- Layer 2 (Behavior): BookDetailController 新增 publisher 屬性
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- Layer 3 (UseCase): GetBookDetailUseCase 新增 publisher 參數
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- Layer 5 (Domain): Book Entity 新增 publisher 欄位
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">問題分析：
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 修改 4 個層級（Layer 1, 2, 3, 5）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 修改至少 4 個檔案
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 違反單層修改原則
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 風險：任一層級修改錯誤都會影響整個功能
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">正例 正確做法（引入 Facade）：
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Phase 1 [Layer 5]: Book Entity 新增 publisher 欄位
</span></span><span class="line"><span class="ln">17</span><span class="cl">- Phase 2 [Layer 3]: BookDetailFacade 更新回傳資料
</span></span><span class="line"><span class="ln">18</span><span class="cl">- Phase 3 [Layer 2]: Presenter 轉換新增 publisher
</span></span><span class="line"><span class="ln">19</span><span class="cl">- Phase 4 [Layer 1]: UI 顯示 publisher
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">改善效果：
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 每個 Phase 只修改單一層級
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 變更影響範圍可控
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 風險降低</span></span></code></pre></div><p><strong>好壞對比程式碼</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Shotgun Surgery：新增欄位需要修改 4 層
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Layer 5 (Domain)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="n">ISBN</span> <span class="n">isbn</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">publisher</span><span class="p">;</span> <span class="c1">// 新增欄位
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// Layer 3 (UseCase)
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">GetBookDetailUseCase</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">repository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1">// 需要處理 publisher
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">book</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">// Layer 2 (Behavior)
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="kt">String</span><span class="o">?</span> <span class="n">publisher</span><span class="p">;</span> <span class="c1">// 新增屬性
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">getBookDetailUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">publisher</span> <span class="o">=</span> <span class="n">book</span><span class="p">.</span><span class="n">publisher</span><span class="p">;</span> <span class="c1">// 新增處理
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1">// Layer 1 (UI)
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">controller</span><span class="p">.</span><span class="n">title</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">controller</span><span class="p">.</span><span class="n">publisher</span> <span class="o">??</span> <span class="s1">&#39;&#39;</span><span class="p">),</span> <span class="c1">// 新增顯示
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1">// 正例：引入 Facade 隔離變更
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">// Layer 4 (Domain Interface)
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"></span><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">IBookDetailFacade</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookDetailViewModel</span><span class="o">&gt;</span> <span class="n">getBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1">// Layer 3 (UseCase - Facade Implementation)
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailFacade</span> <span class="kd">implements</span> <span class="n">IBookDetailFacade</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookDetailViewModel</span><span class="o">&gt;</span> <span class="n">getBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">return</span> <span class="n">BookPresenter</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">book</span><span class="p">);</span> <span class="c1">// 統一轉換
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="c1">// Layer 2 (Behavior - Presenter)
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookPresenter</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">  <span class="kd">static</span> <span class="n">BookDetailViewModel</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">return</span> <span class="n">BookDetailViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">      <span class="nl">title:</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">      <span class="nl">isbn:</span> <span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">      <span class="nl">publisher:</span> <span class="n">book</span><span class="p">.</span><span class="n">publisher</span><span class="p">,</span> <span class="c1">// 新增欄位在這裡處理
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="c1"></span>    <span class="p">);</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1">// Layer 1 (UI) - 無需修改
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">title</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">isbn</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">publisher</span><span class="p">),</span> <span class="c1">// 直接使用 ViewModel
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">
</span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="o">-</span> <span class="err">未來新增欄位只需要修改</span> <span class="n">Layer</span> <span class="m">3</span> <span class="p">(</span><span class="n">Facade</span><span class="p">)</span> <span class="err">和</span> <span class="n">Layer</span> <span class="m">2</span> <span class="p">(</span><span class="n">Presenter</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="o">-</span> <span class="n">Layer</span> <span class="m">1</span> <span class="p">(</span><span class="n">UI</span><span class="p">)</span> <span class="err">和</span> <span class="n">Layer</span> <span class="m">5</span> <span class="p">(</span><span class="n">Domain</span><span class="p">)</span> <span class="err">的修改影響已隔離</span></span></span></code></pre></div><hr>
<h4 id="212-a2-feature-envy功能嫉妒">2.1.2 A2. Feature Envy（功能嫉妒）</h4>
<p><strong>定義</strong>:
某層級過度依賴其他層級的實作細節，而非依賴抽象介面。外層直接存取內層的內部狀態，缺乏適當的 DTO 或 ViewModel 轉換。</p>
<p><strong>特徵識別</strong>:</p>
<ol>
<li>外層直接存取內層的內部狀態（如 <code>book.isbn.value</code>）</li>
<li>缺乏適當的 DTO 或 ViewModel 轉換</li>
<li>跨層級的緊耦合</li>
<li>UI 層直接 import Domain Entity</li>
<li>外層存取內層內部欄位次數 &gt; 3 次</li>
</ol>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反「依賴倒置原則」（2.3 節）</li>
<li>違反 Layer 2 的「資料轉換職責」（2.2 節 Layer 2 定義）</li>
<li>缺少 Presenter 轉換層</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Feature Envy：UI 直接存取 Domain Entity
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 反例：UI 層不應 import Domain Entity
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">final</span> <span class="n">Book</span> <span class="n">book</span><span class="p">;</span> <span class="c1">// 反例：直接依賴 Domain Entity
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">),</span>        <span class="c1">// 反例：存取內部欄位
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">value</span><span class="p">),</span>         <span class="c1">// 反例：存取內部欄位
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">author</span><span class="p">.</span><span class="n">name</span><span class="p">),</span>        <span class="c1">// 反例：存取內部欄位
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">isNewRelease</span><span class="p">()</span> <span class="o">?</span> <span class="s1">&#39;新書&#39;</span> <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">),</span> <span class="c1">// 反例：呼叫 Domain 方法
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="o">-</span> <span class="n">UI</span> <span class="err">層</span> <span class="k">import</span> <span class="n">Domain</span> <span class="n">Entity</span><span class="err">（違反依賴方向）</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="err">-</span> <span class="n">UI</span> <span class="err">直接存取</span> <span class="n">Entity</span> <span class="err">內部欄位（緊耦合）</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="err">-</span> <span class="n">UI</span> <span class="err">呼叫</span> <span class="n">Domain</span> <span class="err">業務方法（職責混亂）</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="err">-</span> <span class="n">Domain</span> <span class="err">修改會影響</span> <span class="n">UI</span><span class="err">（高風險）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="err">//</span> <span class="err">正例：透過</span> <span class="n">ViewModel</span> <span class="err">轉換</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="err">//</span> <span class="n">Layer</span> <span class="err">2:</span> <span class="err">定義</span> <span class="n">ViewModel</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="n">import</span> <span class="s1">&#39;package:book_overview_app/presentation/view_models/book_view_model.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="kd">class</span> <span class="nc">BookViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">isbn</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">author</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="kd">final</span> <span class="kt">bool</span> <span class="n">isNew</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="n">BookViewModel</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">title</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">isbn</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">author</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">isNew</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1">// Layer 2: Presenter 轉換（資料轉換職責）
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookPresenter</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="kd">static</span> <span class="n">BookViewModel</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">return</span> <span class="n">BookViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">      <span class="nl">title:</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>      <span class="c1">// 提取內部欄位
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1"></span>      <span class="nl">isbn:</span> <span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>        <span class="c1">// 提取內部欄位
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="c1"></span>      <span class="nl">author:</span> <span class="n">book</span><span class="p">.</span><span class="n">author</span><span class="p">.</span><span class="n">name</span><span class="p">,</span>     <span class="c1">// 提取內部欄位
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1"></span>      <span class="nl">isNew:</span> <span class="n">book</span><span class="p">.</span><span class="n">isNewRelease</span><span class="p">(),</span>   <span class="c1">// 執行 Domain 方法
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="c1"></span>    <span class="p">);</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="c1">// Layer 1: UI 使用 ViewModel
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">  <span class="kd">final</span> <span class="n">BookViewModel</span> <span class="n">viewModel</span><span class="p">;</span> <span class="c1">// 正例：依賴 ViewModel
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">title</span><span class="p">),</span>    <span class="c1">// 正例：使用轉換後的資料
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">isbn</span><span class="p">),</span>     <span class="c1">// 正例：使用轉換後的資料
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">author</span><span class="p">),</span>   <span class="c1">// 正例：使用轉換後的資料
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">isNew</span> <span class="o">?</span> <span class="s1">&#39;新書&#39;</span> <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">),</span> <span class="c1">// 正例：使用轉換後的狀態
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="o">-</span> <span class="n">UI</span> <span class="err">層不依賴</span> <span class="n">Domain</span> <span class="n">Entity</span><span class="err">（降低耦合）</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="o">-</span> <span class="n">Presenter</span> <span class="err">集中處理資料轉換（符合</span> <span class="n">Layer</span> <span class="m">2</span> <span class="err">職責）</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="o">-</span> <span class="n">Domain</span> <span class="err">修改不影響</span> <span class="n">UI</span><span class="err">（只需調整</span> <span class="n">Presenter</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="o">-</span> <span class="err">測試更容易（</span><span class="n">Mock</span> <span class="n">ViewModel</span> <span class="err">即可）</span></span></span></code></pre></div><hr>
<h4 id="213-a3-inappropriate-intimacy不當親密關係">2.1.3 A3. Inappropriate Intimacy（不當親密關係）</h4>
<p><strong>定義</strong>:
層級間過度耦合，內層知道外層的存在或依賴外層，違反依賴方向規則。</p>
<p><strong>特徵識別</strong>:</p>
<ol>
<li>Domain 層依賴 UseCase 或 UI 層</li>
<li>依賴方向錯誤（內層依賴外層）</li>
<li>存在循環依賴</li>
<li>Domain Entity 包含 UI 或 Infrastructure 的 import</li>
</ol>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反「依賴方向規則」（2.3 節）</li>
<li>違反「Layer 5 不依賴任何層級」原則</li>
<li>正確依賴方向：Layer 1 → Layer 2 → Layer 3 → Layer 4 ← Layer 5</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Inappropriate Intimacy：Domain 依賴 UseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Layer 5 (Domain)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/application/use_cases/add_book_to_favorite_use_case.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 反例：Domain 不應 import UseCase
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="kd">final</span> <span class="n">Title</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kd">final</span> <span class="n">AddBookToFavoriteUseCase</span> <span class="n">favoriteUseCase</span><span class="p">;</span> <span class="c1">// 反例：Domain 依賴 UseCase
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kt">void</span> <span class="n">addToFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">favoriteUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">id</span><span class="p">);</span> <span class="c1">// 反例：Domain 不應呼叫 UseCase
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="o">-</span> <span class="n">Domain</span> <span class="err">依賴外層（</span><span class="n">UseCase</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="o">-</span> <span class="err">違反依賴方向規則</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="o">-</span> <span class="n">Domain</span> <span class="err">失去獨立性和可重用性</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="o">-</span> <span class="err">測試困難（</span><span class="n">Domain</span> <span class="err">測試需要</span> <span class="n">Mock</span> <span class="n">UseCase</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">// 正例：Domain 只定義業務邏輯
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">// Layer 5 (Domain) - 獨立且純淨
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kd">final</span> <span class="n">Title</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="kt">bool</span> <span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="c1">// 正例：只記錄狀態
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="kt">void</span> <span class="n">markAsFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// 正例：只處理業務邏輯
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="kt">void</span> <span class="n">unmarkFromFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">// Layer 3 (UseCase) - 協調業務流程
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">AddBookToFavoriteUseCase</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">  <span class="kd">final</span> <span class="n">IBookRepository</span> <span class="n">bookRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">  <span class="kd">final</span> <span class="n">IFavoriteRepository</span> <span class="n">favoriteRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="kt">String</span> <span class="n">bookId</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="c1">// 1. 取得書籍
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="c1">// 2. 執行 Domain 方法
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"></span>    <span class="n">book</span><span class="p">.</span><span class="n">markAsFavorite</span><span class="p">();</span> <span class="c1">// 正例：UseCase 呼叫 Domain 方法
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="c1">// 3. 儲存狀態
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1"></span>    <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="kd">await</span> <span class="n">favoriteRepository</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="c1">// Layer 2 (Behavior/Controller) - 觸發 UseCase
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">  <span class="kd">final</span> <span class="n">AddBookToFavoriteUseCase</span> <span class="n">addToFavoriteUseCase</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">  <span class="kt">void</span> <span class="n">onFavoriteButtonPressed</span><span class="p">(</span><span class="kt">String</span> <span class="n">bookId</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="kd">await</span> <span class="n">addToFavoriteUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span> <span class="c1">// 正例：呼叫方向
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="o">-</span> <span class="n">Domain</span> <span class="err">獨立且純淨（不依賴外層）</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="o">-</span> <span class="err">依賴方向正確（</span><span class="n">Layer</span> <span class="m">2</span> <span class="err">→</span> <span class="n">Layer</span> <span class="m">3</span> <span class="err">→</span> <span class="n">Layer</span> <span class="m">5</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="o">-</span> <span class="n">Domain</span> <span class="err">可重用性高</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="o">-</span> <span class="err">測試容易（</span><span class="n">Domain</span> <span class="err">無外部依賴）</span></span></span></code></pre></div><hr>
<h4 id="214-a4-leaky-abstraction抽象滲漏">2.1.4 A4. Leaky Abstraction（抽象滲漏）</h4>
<p><strong>定義</strong>:
內層的實作細節透過介面洩漏到外層，介面不夠抽象。</p>
<p><strong>特徵識別</strong>:</p>
<ol>
<li>Repository 介面包含資料庫特定參數（如 SQL 語句）</li>
<li>Domain Event 包含 UI 特定資料（如 Widget 狀態）</li>
<li>抽象介面不夠抽象，包含實作關鍵字</li>
<li>介面方法名稱洩漏實作細節</li>
</ol>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反 Layer 4「介面契約」的職責定義（2.2 節）</li>
<li>介面應該隱藏實作細節</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Leaky Abstraction：介面洩漏實作細節
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Layer 4 (Domain Interface)
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findBySql</span><span class="p">(</span><span class="kt">String</span> <span class="n">sql</span><span class="p">);</span>        <span class="c1">// 反例：洩漏 SQL 實作
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">queryWithCursor</span><span class="p">(</span><span class="n">Cursor</span> <span class="n">cursor</span><span class="p">);</span> <span class="c1">// 反例：洩漏資料庫 Cursor
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">saveToSqlite</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">);</span>      <span class="c1">// 反例：洩漏 SQLite 實作
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="o">-</span> <span class="err">介面包含「</span><span class="n">SQL</span><span class="err">」、「</span><span class="n">Cursor</span><span class="err">」、「</span><span class="n">Sqlite</span><span class="err">」等實作關鍵字</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="o">-</span> <span class="err">外層（</span><span class="n">UseCase</span><span class="err">）需要知道使用</span> <span class="n">SQL</span> <span class="err">資料庫</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="o">-</span> <span class="err">無法更換實作（綁定</span> <span class="n">SQLite</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="o">-</span> <span class="err">違反介面契約原則</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">// 正例：抽象介面
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1">// Layer 4 (Domain Interface) - 抽象且純淨
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findById</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">);</span>          <span class="c1">// 正例：隱藏實作細節
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">findByAuthor</span><span class="p">(</span><span class="kt">String</span> <span class="n">author</span><span class="p">);</span> <span class="c1">// 正例：業務概念
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">findAll</span><span class="p">();</span>              <span class="c1">// 正例：簡單明確
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">save</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">);</span>              <span class="c1">// 正例：抽象操作
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">delete</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">);</span>            <span class="c1">// 正例：抽象操作
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1">// Layer 5 (Infrastructure) - 具體實作可替換
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">SqliteBookRepository</span> <span class="kd">implements</span> <span class="n">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findById</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="c1">// SQL 實作細節在這裡
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">db</span><span class="p">.</span><span class="n">query</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">      <span class="s1">&#39;books&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">      <span class="nl">where:</span> <span class="s1">&#39;id = ?&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">      <span class="nl">whereArgs:</span> <span class="p">[</span><span class="n">id</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">fromJson</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">first</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1">// Layer 5 (Infrastructure) - 另一種實作
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">FirestoreBookRepository</span> <span class="kd">implements</span> <span class="n">IBookRepository</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">  <span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findById</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="c1">// Firestore 實作細節在這裡
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">doc</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">firestore</span><span class="p">.</span><span class="n">collection</span><span class="p">(</span><span class="s1">&#39;books&#39;</span><span class="p">).</span><span class="n">doc</span><span class="p">(</span><span class="n">id</span><span class="p">).</span><span class="kd">get</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">fromJson</span><span class="p">(</span><span class="n">doc</span><span class="p">.</span><span class="n">data</span><span class="p">()</span><span class="o">!</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="o">-</span> <span class="err">介面抽象且純淨（不包含實作細節）</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="o">-</span> <span class="err">可輕鬆更換實作（</span><span class="n">SQLite</span> <span class="err">→</span> <span class="n">Firestore</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="o">-</span> <span class="n">UseCase</span> <span class="err">不需要知道資料庫實作</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="o">-</span> <span class="err">符合依賴倒置原則</span></span></span></code></pre></div><hr>
<h3 id="22-b-類-code-smell單層級問題">2.2 B 類 Code Smell（單層級問題）</h3>
<h4 id="221-b1-divergent-change發散式變更">2.2.1 B1. Divergent Change（發散式變更）</h4>
<p><strong>定義</strong>:
單一類別因不同原因需要修改，違反 Single Responsibility Principle（SRP）。</p>
<p><strong>特徵識別</strong>:</p>
<ol>
<li>一個 Controller 同時負責多個頁面的邏輯</li>
<li>一個 UseCase 處理多個不相關的業務流程</li>
<li>變更原因不單一（有 2+ 個變更原因）</li>
<li>類別方法可以明確分組（2+ 個分組）</li>
</ol>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反「單層修改原則」的 SRP 理論依據（3.2 節）</li>
<li>違反「變更原因單一」要求（3.1 節）</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Divergent Change：單一 Controller 承擔多個職責
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">class</span> <span class="nc">BookController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="c1">// 群組 A：列表頁面邏輯（3 個方法）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">bookList</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookList</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">// 載入書籍列表
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="kt">void</span> <span class="n">refreshBookList</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1">// 重新整理列表
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="kt">void</span> <span class="n">sortBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">sortBy</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1">// 排序列表
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="c1">// 群組 B：詳情頁面邏輯（3 個方法）
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="n">BookViewModel</span><span class="o">?</span> <span class="n">bookDetail</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1">// 載入書籍詳情
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="kt">void</span> <span class="n">updateBookDetail</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="c1">// 更新書籍詳情
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteBook</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="c1">// 刪除書籍
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="c1">// 群組 C：搜尋邏輯（2 個方法）
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1"></span>  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">searchResults</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="kt">void</span> <span class="n">searchBooks</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1">// 搜尋書籍
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="kt">void</span> <span class="n">clearSearchResults</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="c1">// 清空搜尋結果
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="o">-</span> <span class="m">3</span> <span class="err">個方法群組（列表、詳情、搜尋）</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="o">-</span> <span class="m">3</span> <span class="err">種變更原因（列表變更、詳情變更、搜尋變更）</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="o">-</span> <span class="err">類別名稱過於籠統（</span><span class="n">BookController</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="o">-</span> <span class="err">違反</span> <span class="n">SRP</span> <span class="err">原則</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="c1">// 正例：拆分為多個單一職責 Controller
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="c1">// Controller 1：只負責列表
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">bookList</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">  <span class="kt">void</span> <span class="n">refreshBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">  <span class="kt">void</span> <span class="n">sortBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">sortBy</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="c1">// Controller 2：只負責詳情
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">  <span class="n">BookViewModel</span><span class="o">?</span> <span class="n">bookDetail</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">  <span class="kt">void</span> <span class="n">updateBookDetail</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1">// Controller 3：只負責搜尋
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookSearchController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">searchResults</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">
</span></span><span class="line"><span class="ln">76</span><span class="cl">  <span class="kt">void</span> <span class="n">searchBooks</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">  <span class="kt">void</span> <span class="n">clearSearchResults</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">
</span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="o">-</span> <span class="err">每個</span> <span class="n">Controller</span> <span class="err">只有</span> <span class="m">1</span> <span class="err">個變更原因</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="o">-</span> <span class="err">職責明確且單一</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="o">-</span> <span class="err">可讀性提升</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl"><span class="o">-</span> <span class="err">測試更容易（測試範圍更小）</span></span></span></code></pre></div><hr>
<h4 id="222-b2-large-class大類別">2.2.2 B2. Large Class（大類別）</h4>
<p><strong>定義</strong>:
類別過大，包含過多方法和屬性，職責不清。</p>
<p><strong>特徵識別（量化標準，引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節）</strong>:</p>
<ul>
<li>總行數: &gt; 300 行</li>
<li>public 方法: &gt; 15 個</li>
<li>屬性: &gt; 12 個</li>
<li>類別職責無法用一句話清楚描述</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Large Class：職責過多（500+ 行）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">class</span> <span class="nc">BookService</span> <span class="p">{</span> <span class="c1">// 總行數：500+ 行
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="c1">// 新增書籍（20 個方法）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">addBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">addMultipleBooks</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">books</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">importBooksFromCsv</span><span class="p">(</span><span class="kt">String</span> <span class="n">filePath</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="c1">// ... 17 個其他方法
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="c1">// 查詢書籍（15 個方法）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">findBooksByAuthor</span><span class="p">(</span><span class="kt">String</span> <span class="n">author</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">searchBooks</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="c1">// ... 12 個其他方法
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="c1">// 統計分析（10 個方法）
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookStats</span><span class="o">&gt;</span> <span class="n">getStatistics</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">getBooksByGenre</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">getMostPopular</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="c1">// ... 7 個其他方法
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="c1">// 匯出報表（8 個方法）
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span>  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportReport</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportToPdf</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportToExcel</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="c1">// ... 5 個其他方法
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="o">-</span> <span class="err">總行數</span><span class="o">:</span> <span class="m">500</span><span class="o">+</span> <span class="err">行（超過</span> <span class="m">300</span> <span class="err">行標準）</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="o">-</span> <span class="n">public</span> <span class="err">方法</span><span class="o">:</span> <span class="m">53</span> <span class="err">個（超過</span> <span class="m">15</span> <span class="err">個標準）</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="o">-</span> <span class="m">4</span> <span class="err">種不同職責（新增、查詢、統計、匯出）</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="o">-</span> <span class="err">違反</span> <span class="n">SRP</span> <span class="err">原則</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1">// 正例：拆分為多個職責明確的 Service
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1">// Service 1：書籍管理（新增、更新、刪除）
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookManagementService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">addBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">updateBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">deleteBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1">// Service 2：書籍查詢
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookQueryService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">findById</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">findByAuthor</span><span class="p">(</span><span class="kt">String</span> <span class="n">author</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">search</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1">// Service 3：書籍統計
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookStatisticsService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookStats</span><span class="o">&gt;</span> <span class="n">getStatistics</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">getBooksByGenre</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;&gt;</span> <span class="n">getMostPopular</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="c1">// Service 4：報表匯出
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookReportService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportToPdf</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportToExcel</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">exportToCsv</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="o">-</span> <span class="err">每個</span> <span class="n">Service</span> <span class="o">&lt;</span> <span class="m">200</span> <span class="err">行</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="o">-</span> <span class="err">職責單一且明確</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="o">-</span> <span class="err">可測試性提升</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="o">-</span> <span class="err">可維護性提升</span></span></span></code></pre></div><hr>
<h4 id="223-b3-long-method長方法">2.2.3 B3. Long Method（長方法）</h4>
<p><strong>定義</strong>:
方法過長，難以理解和測試。</p>
<p><strong>特徵識別（量化標準）</strong>:</p>
<ul>
<li>方法行數: &gt; 50 行</li>
<li>巢狀層級: &gt; 3 層</li>
<li>邏輯區塊: &gt; 4 個（用註解分隔）</li>
<li>方法名稱包含「And」（如 <code>validateAndSaveBook</code>）</li>
</ul>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1">// 反例：Long Method：80 行方法
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">processBookOrder</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">  <span class="c1">// 驗證訂單（20 行）
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="c1"></span>  <span class="k">if</span> <span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">    <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;訂單不能為空&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">quantity</span> <span class="o">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">      <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;數量必須大於 0&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">price</span> <span class="o">&lt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">      <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;價格不能為負數&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">  <span class="c1">// 計算價格（20 行）
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="c1"></span>  <span class="kt">double</span> <span class="n">total</span> <span class="o">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">  <span class="kt">double</span> <span class="n">discount</span> <span class="o">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">total</span> <span class="o">+=</span> <span class="n">item</span><span class="p">.</span><span class="n">price</span> <span class="o">*</span> <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">total</span> <span class="o">&gt;</span> <span class="m">1000</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">discount</span> <span class="o">=</span> <span class="n">total</span> <span class="o">*</span> <span class="m">0.1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">total</span> <span class="o">&gt;</span> <span class="m">500</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">discount</span> <span class="o">=</span> <span class="n">total</span> <span class="o">*</span> <span class="m">0.05</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">  <span class="n">total</span> <span class="o">-=</span> <span class="n">discount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">  <span class="c1">// 庫存檢查（20 行）
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="c1"></span>  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="kd">final</span> <span class="n">stock</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">inventoryRepository</span><span class="p">.</span><span class="n">getStock</span><span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">stock</span> <span class="o">&lt;</span> <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">      <span class="k">throw</span> <span class="n">InsufficientStockException</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="s1">&#39;書籍 </span><span class="si">${</span><span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="si">}</span><span class="s1"> 庫存不足&#39;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">  <span class="c1">// 建立訂單（20 行）
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="c1"></span>  <span class="n">order</span><span class="p">.</span><span class="n">total</span> <span class="o">=</span> <span class="n">total</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">discount</span> <span class="o">=</span> <span class="n">discount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">OrderStatus</span><span class="p">.</span><span class="n">pending</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">createdAt</span> <span class="o">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">now</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">  <span class="kd">await</span> <span class="n">repository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">  <span class="c1">// 扣除庫存
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="c1"></span>  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="kd">await</span> <span class="n">inventoryRepository</span><span class="p">.</span><span class="n">reduceStock</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">      <span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">      <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="err">問題分析：</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="o">-</span> <span class="err">方法行數</span><span class="o">:</span> <span class="m">80</span> <span class="err">行（超過</span> <span class="m">50</span> <span class="err">行標準）</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="o">-</span> <span class="m">4</span> <span class="err">個邏輯區塊（驗證、計算、庫存、建立）</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="o">-</span> <span class="err">巢狀層級</span><span class="o">:</span> <span class="m">3</span> <span class="err">層（</span><span class="k">for</span> <span class="o">+</span> <span class="k">if</span><span class="err">）</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="o">-</span> <span class="err">難以理解和測試</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="c1">// 正例：拆分為多個小方法
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">processBookOrder</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">  <span class="n">_validateOrder</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>              <span class="c1">// 步驟 1
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="n">total</span> <span class="o">=</span> <span class="n">_calculateTotal</span><span class="p">(</span><span class="n">order</span><span class="p">);</span> <span class="c1">// 步驟 2
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="c1"></span>  <span class="kd">await</span> <span class="n">_checkInventory</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>       <span class="c1">// 步驟 3
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="c1"></span>  <span class="kd">await</span> <span class="n">_saveOrder</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">total</span><span class="p">);</span>     <span class="c1">// 步驟 4
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="kt">void</span> <span class="n">_validateOrder</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;訂單不能為空&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">_validateOrderItem</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="kt">void</span> <span class="n">_validateOrderItem</span><span class="p">(</span><span class="n">OrderItem</span> <span class="n">item</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">quantity</span> <span class="o">&lt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;數量必須大於 0&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">price</span> <span class="o">&lt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;價格不能為負數&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="kt">double</span> <span class="n">_calculateTotal</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">  <span class="kt">double</span> <span class="n">total</span> <span class="o">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="n">total</span> <span class="o">+=</span> <span class="n">item</span><span class="p">.</span><span class="n">price</span> <span class="o">*</span> <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl">  <span class="kd">final</span> <span class="n">discount</span> <span class="o">=</span> <span class="n">_calculateDiscount</span><span class="p">(</span><span class="n">total</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">  <span class="k">return</span> <span class="n">total</span> <span class="o">-</span> <span class="n">discount</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="kt">double</span> <span class="n">_calculateDiscount</span><span class="p">(</span><span class="kt">double</span> <span class="n">total</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">total</span> <span class="o">&gt;</span> <span class="m">1000</span><span class="p">)</span> <span class="k">return</span> <span class="n">total</span> <span class="o">*</span> <span class="m">0.1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">total</span> <span class="o">&gt;</span> <span class="m">500</span><span class="p">)</span> <span class="k">return</span> <span class="n">total</span> <span class="o">*</span> <span class="m">0.05</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">  <span class="k">return</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_checkInventory</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="kd">final</span> <span class="n">stock</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">inventoryRepository</span><span class="p">.</span><span class="n">getStock</span><span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">stock</span> <span class="o">&lt;</span> <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">      <span class="k">throw</span> <span class="n">InsufficientStockException</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="s1">&#39;書籍 </span><span class="si">${</span><span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="si">}</span><span class="s1"> 庫存不足&#39;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_saveOrder</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">,</span> <span class="kt">double</span> <span class="n">total</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">total</span> <span class="o">=</span> <span class="n">total</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">OrderStatus</span><span class="p">.</span><span class="n">pending</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">  <span class="n">order</span><span class="p">.</span><span class="n">createdAt</span> <span class="o">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">now</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">  <span class="kd">await</span> <span class="n">repository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">  <span class="kd">await</span> <span class="n">_reduceInventory</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">_reduceInventory</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="kd">await</span> <span class="n">inventoryRepository</span><span class="p">.</span><span class="n">reduceStock</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">      <span class="n">item</span><span class="p">.</span><span class="n">bookId</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">      <span class="n">item</span><span class="p">.</span><span class="n">quantity</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="o">-</span> <span class="err">主方法只有</span> <span class="m">4</span> <span class="err">行（清楚的流程）</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="o">-</span> <span class="err">每個私有方法</span> <span class="o">&lt;</span> <span class="m">20</span> <span class="err">行</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="o">-</span> <span class="err">邏輯分離且可測試</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="o">-</span> <span class="err">可讀性大幅提升</span></span></span></code></pre></div><hr>
<h4 id="224-b4-dead-code死程式碼">2.2.4 B4. Dead Code（死程式碼）</h4>
<p><strong>定義</strong>:
永遠不會執行或使用的程式碼。</p>
<p><strong>特徵識別</strong>:</p>
<ul>
<li>未被呼叫的方法</li>
<li>無法到達的程式碼分支</li>
<li>註解掉的程式碼超過 1 週</li>
<li><code>dart analyze</code> 顯示 <code>unused_element</code> 警告</li>
</ul>
<p><strong>自動化檢測方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 檢測 unused 警告</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">dart analyze <span class="p">|</span> grep <span class="s2">&#34;unused&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 檢測程式碼覆蓋率</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">genhtml coverage/lcov.info -o coverage/html
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 檢查 coverage/html 中 0% 覆蓋率的程式碼</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 搜尋註解掉的程式碼</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">grep -r <span class="s2">&#34;^[[:space:]]*//.*{&#34;</span> lib/</span></span></code></pre></div><p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Dead Code 範例
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">class</span> <span class="nc">BookService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="c1">// 未使用的方法（dart analyze 會警告）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">unusedMethod</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">print</span><span class="p">(</span><span class="s1">&#39;這個方法從未被呼叫&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 無法到達的程式碼
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">processBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span><span class="p">;</span> <span class="c1">// 提前返回
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">// 反例：以下程式碼永遠不會執行
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>    <span class="n">print</span><span class="p">(</span><span class="s1">&#39;處理書籍&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">saveBook</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="c1">// 註解掉的程式碼（已過時）
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span>  <span class="c1">// void oldImplementation() {
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>  <span class="c1">//   // 舊的實作方式，已被新方法取代
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"></span>  <span class="c1">//   // 但程式碼保留了 2 個月
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span>  <span class="c1">// }
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="c1">// 未使用的變數
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="kt">String</span> <span class="n">unusedVariable</span> <span class="o">=</span> <span class="s1">&#39;never used&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1">// 正例：移除 Dead Code
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="kd">class</span> <span class="nc">BookService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="c1">// 只保留實際使用的方法
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">processBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">print</span><span class="p">(</span><span class="s1">&#39;處理書籍&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">saveBook</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="err">改善效果：</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="o">-</span> <span class="err">程式碼簡潔</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="o">-</span> <span class="err">降低維護成本</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="o">-</span> <span class="err">無混淆和誤導</span></span></span></code></pre></div><hr>
<h3 id="23-c-類-code-smellticket-粒度問題">2.3 C 類 Code Smell（Ticket 粒度問題）</h3>
<h4 id="231-c1-god-ticket全能-ticket">2.3.1 C1. God Ticket（全能 Ticket）</h4>
<p><strong>定義</strong>:
單一 Ticket 修改過多檔案和層級，範圍失控。</p>
<p><strong>特徵識別（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節量化標準）</strong>:</p>
<ul>
<li>修改檔案數 &gt; 10 個</li>
<li>跨 3 個以上架構層級</li>
<li>預估工時 &gt; 16 小時（2 天）</li>
</ul>
<p><strong>與<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關聯</strong>:</p>
<ul>
<li>違反「Ticket 粒度標準」（5.2 節）</li>
<li>違反「單層修改原則」（3.1 節）</li>
</ul>
<p><strong>檢測方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">步驟 1: 計算 Ticket 涉及的檔案數
</span></span><span class="line"><span class="ln">2</span><span class="cl">步驟 2: 判斷檔案所屬的層級
</span></span><span class="line"><span class="ln">3</span><span class="cl">步驟 3: 計算跨幾個層級
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ├─ &gt; 3 層級 → God Ticket
</span></span><span class="line"><span class="ln">5</span><span class="cl">  └─ 1 層級 → 良好 Ticket</span></span></code></pre></div><p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket: 新增「書籍評分」完整功能
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檔案清單（12 個檔案）:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">1. lib/presentation/widgets/book_detail_widget.dart (Layer 1)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">2. lib/presentation/widgets/rating_widget.dart (Layer 1)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">3. lib/presentation/controllers/book_detail_controller.dart (Layer 2)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">4. lib/presentation/controllers/rating_controller.dart (Layer 2)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">5. lib/application/use_cases/rate_book_use_case.dart (Layer 3)
</span></span><span class="line"><span class="ln">10</span><span class="cl">6. lib/application/use_cases/get_book_rating_use_case.dart (Layer 3)
</span></span><span class="line"><span class="ln">11</span><span class="cl">7. lib/domain/entities/book.dart (Layer 5)
</span></span><span class="line"><span class="ln">12</span><span class="cl">8. lib/domain/entities/rating.dart (Layer 5)
</span></span><span class="line"><span class="ln">13</span><span class="cl">9. lib/domain/value_objects/rating_value.dart (Layer 5)
</span></span><span class="line"><span class="ln">14</span><span class="cl">10. lib/infrastructure/repositories/book_repository_impl.dart (Layer 5)
</span></span><span class="line"><span class="ln">15</span><span class="cl">11. lib/infrastructure/repositories/rating_repository_impl.dart (Layer 5)
</span></span><span class="line"><span class="ln">16</span><span class="cl">12. lib/infrastructure/database/rating_table.dart (Layer 5)
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">分析結果:
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 檔案數: 12 個（&gt; 10 個標準）
</span></span><span class="line"><span class="ln">20</span><span class="cl">- 層級跨度: 4 層（Layer 1, 2, 3, 5）
</span></span><span class="line"><span class="ln">21</span><span class="cl">- 預估工時: 24 小時（&gt; 16 小時標準）
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 判斷: God Ticket
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">建議拆分（引用[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 5.4 節拆分指引）:
</span></span><span class="line"><span class="ln">25</span><span class="cl">- Ticket 1 [Layer 5]: Rating Value Object 和 Entity 設計
</span></span><span class="line"><span class="ln">26</span><span class="cl">- Ticket 2 [Layer 5]: Rating Repository 實作
</span></span><span class="line"><span class="ln">27</span><span class="cl">- Ticket 3 [Layer 3]: RateBookUseCase 實作
</span></span><span class="line"><span class="ln">28</span><span class="cl">- Ticket 4 [Layer 3]: GetBookRatingUseCase 實作
</span></span><span class="line"><span class="ln">29</span><span class="cl">- Ticket 5 [Layer 2]: Controller 整合 UseCase
</span></span><span class="line"><span class="ln">30</span><span class="cl">- Ticket 6 [Layer 1]: UI 新增評分元件
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">改善效果:
</span></span><span class="line"><span class="ln">33</span><span class="cl">- 每個 Ticket 只修改 1-3 個檔案
</span></span><span class="line"><span class="ln">34</span><span class="cl">- 每個 Ticket 只涉及 1 個層級
</span></span><span class="line"><span class="ln">35</span><span class="cl">- 預估工時: 每個 Ticket 2-4 小時
</span></span><span class="line"><span class="ln">36</span><span class="cl">- 風險可控</span></span></code></pre></div><hr>
<h4 id="232-c2-incomplete-ticket不完整-ticket">2.3.2 C2. Incomplete Ticket（不完整 Ticket）</h4>
<p><strong>定義</strong>:
Ticket 缺少必要的測試、文件或驗收條件。</p>
<p><strong>特徵識別</strong>:</p>
<ul>
<li>沒有測試檔案（Phase 2 缺失）</li>
<li>沒有驗收條件（Phase 1 設計不完整）</li>
<li>沒有更新相關文件</li>
<li>沒有完整的 TDD 四階段記錄</li>
</ul>
<p><strong>檢測方法（基於 TDD 四階段要求）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">完整 Ticket 必須包含:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- Phase 1: 功能設計完成
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Phase 2: 測試設計完成（測試檔案存在）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- Phase 3: 實作完成（程式碼檔案）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- Phase 4: 重構評估完成
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Incomplete Ticket 特徵:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 缺少測試檔案（Phase 2 未完成）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 缺少驗收條件（Phase 1 設計不完整）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 缺少工作日誌（無法追蹤進度）</span></span></code></pre></div><p><strong>檢測流程（Code Review 階段）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 git diff 中的檔案
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 是否包含 test/ 目錄的檔案？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  └─ 測試檔案數量 vs 程式碼檔案數量比例
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 檢查 Ticket 描述
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  └─ 是否包含「驗收條件」章節？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 3: 檢查工作日誌
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ docs/work-logs/vX.Y.Z-*.md 是否存在？
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 是否完成 Phase 1-4 記錄？</span></span></code></pre></div><hr>
<h4 id="233-c3-ambiguous-responsibility職責模糊-ticket">2.3.3 C3. Ambiguous Responsibility（職責模糊 Ticket）</h4>
<p><strong>定義</strong>:
Ticket 的職責定義不明確，無法判斷屬於哪一層級。</p>
<p><strong>特徵識別</strong>:</p>
<ul>
<li>Ticket 標題沒有標明層級（如 [Layer X]）</li>
<li>描述中混合多個層級的職責</li>
<li>驗收條件跨多個層級</li>
</ul>
<p><strong>檢測方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">職責明確 Ticket 格式:
</span></span><span class="line"><span class="ln">2</span><span class="cl">標題: [Layer X] 清楚的功能描述
</span></span><span class="line"><span class="ln">3</span><span class="cl">描述: 明確說明修改哪一層的哪個檔案
</span></span><span class="line"><span class="ln">4</span><span class="cl">驗收: 只驗證該層級的職責
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">職責模糊 Ticket 特徵:
</span></span><span class="line"><span class="ln">7</span><span class="cl">標題: 沒有 [Layer X] 標示
</span></span><span class="line"><span class="ln">8</span><span class="cl">描述: 混合多個層級的職責
</span></span><span class="line"><span class="ln">9</span><span class="cl">驗證: 跨多個層級的驗證</span></span></code></pre></div><p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">反例 職責模糊 Ticket:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">標題: 實作書籍詳情頁面
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">描述: 實作書籍詳情頁面的所有功能
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">驗收: 可以顯示書籍詳情
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">問題分析:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 無層級標示
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 「所有功能」範圍不明確
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 驗收條件過於籠統
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">正例 職責明確 Ticket:
</span></span><span class="line"><span class="ln">12</span><span class="cl">標題: [Layer 2] 實作書籍詳情頁面事件處理
</span></span><span class="line"><span class="ln">13</span><span class="cl">描述: 實作 BookDetailController 的事件處理方法，
</span></span><span class="line"><span class="ln">14</span><span class="cl">      整合 GetBookDetailUseCase 並轉換為 ViewModel
</span></span><span class="line"><span class="ln">15</span><span class="cl">驗收:
</span></span><span class="line"><span class="ln">16</span><span class="cl">  - BookDetailController.loadBookDetail() 呼叫 UseCase
</span></span><span class="line"><span class="ln">17</span><span class="cl">  - BookPresenter.toViewModel() 正確轉換資料
</span></span><span class="line"><span class="ln">18</span><span class="cl">  - 錯誤處理正確顯示錯誤訊息
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">改善效果:
</span></span><span class="line"><span class="ln">21</span><span class="cl">- 層級明確（Layer 2）
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 職責清楚（事件處理 + 資料轉換）
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 驗收條件可驗證</span></span></code></pre></div><hr>
<h2 id="第三章ticket-粒度檢測方法">第三章：Ticket 粒度檢測方法</h2>
<h3 id="31-檢測時機和流程">3.1 檢測時機和流程</h3>
<p><strong>核心理念</strong>: 從 Ticket 設計階段就能發現 Code Smell，比實作完成後才發現更有效率。</p>
<p><strong>檢測時機對應 TDD 四階段</strong>:</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>檢測時機</th>
          <th>檢測重點</th>
          <th>對應 Code Smell</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Phase 1 設計階段</strong></td>
          <td>Ticket 設計完成時</td>
          <td>Ticket 粒度和層級定位</td>
          <td>C1, C2, C3, A1</td>
      </tr>
      <tr>
          <td><strong>Phase 2 測試設計</strong></td>
          <td>測試設計完成時</td>
          <td>測試範圍是否限定在單一層級</td>
          <td>C2, B1</td>
      </tr>
      <tr>
          <td><strong>Phase 3 實作執行</strong></td>
          <td>程式碼提交時</td>
          <td>實作是否產生 Code Smell</td>
          <td>A2, A3, A4, B2, B3</td>
      </tr>
      <tr>
          <td><strong>Code Review</strong></td>
          <td>PR 提交時</td>
          <td>最終驗證</td>
          <td>所有 Code Smell</td>
      </tr>
      <tr>
          <td><strong>Phase 4 重構階段</strong></td>
          <td>重構評估時</td>
          <td>識別需要重構的 Code Smell</td>
          <td>B1, B2, B3, B4</td>
      </tr>
  </tbody>
</table>
<p><strong>檢測流程總覽</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket 設計（Phase 1）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查 Ticket 粒度（C1, C3, A1）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ 通過 → 測試設計（Phase 2）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 失敗 → 拆分 Ticket
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">測試設計（Phase 2）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">檢查測試範圍（C2）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ 通過 → 實作執行（Phase 3）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 失敗 → 補充測試
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">實作執行（Phase 3）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">檢查程式碼品質（A2, A3, A4, B2, B3）
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ├─ 通過 → Code Review
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ 失敗 → 修正程式碼
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">Code Review
</span></span><span class="line"><span class="ln">20</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">21</span><span class="cl">全面檢查所有 Code Smell
</span></span><span class="line"><span class="ln">22</span><span class="cl">  ├─ 通過 → 合併 PR
</span></span><span class="line"><span class="ln">23</span><span class="cl">  └─ 失敗 → 重構
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">Phase 4 重構評估
</span></span><span class="line"><span class="ln">26</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">27</span><span class="cl">識別需要重構的 Code Smell（B1, B2, B3, B4）
</span></span><span class="line"><span class="ln">28</span><span class="cl">  ├─ 有需要 → 執行重構
</span></span><span class="line"><span class="ln">29</span><span class="cl">  └─ 無需要 → 完成</span></span></code></pre></div><hr>
<h3 id="32-a-類-code-smell-檢測方法跨層級問題">3.2 A 類 Code Smell 檢測方法（跨層級問題）</h3>
<h4 id="321-a1-shotgun-surgery-檢測">3.2.1 A1. Shotgun Surgery 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>檔案數量指標</strong>: 單一 Ticket 修改的檔案數</li>
<li><strong>層級跨度指標</strong>: Ticket 涉及的層級數量</li>
<li><strong>依賴鏈長度指標</strong>: 從 UI 到 Domain 的依賴鏈長度</li>
</ol>
<p><strong>判斷標準</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好 Ticket（單層修改）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 檔案數: 1-5 個
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 層級跨度: 1 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 依賴鏈: 不需要修改
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">需要注意（考慮拆分）:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 檔案數: 6-10 個
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 層級跨度: 2 層
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 依賴鏈: 需要修改 1-2 層
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Shotgun Surgery（散彈槍手術）:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 檔案數: &gt; 10 個
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 層級跨度: &gt; 2 層
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 依賴鏈: 需要同步修改多層</span></span></code></pre></div><p><strong>檢測流程</strong>（基於<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 6.2 節檔案路徑分析法）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 列出 Ticket 涉及的所有檔案
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">步驟 2: 使用[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 2.4 節的決策樹判斷每個檔案屬於哪一層
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 3: 統計跨幾個層級
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ 1 層級 → 良好設計
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  ├─ 2 層級 → 需要檢查是否可拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  └─ &gt; 2 層級 → Shotgun Surgery
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 4: 如果檢測到 Shotgun Surgery
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 檢查是否為特殊場景（架構遷移、Hotfix）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ 分析是否可以拆分為多個 Ticket
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 評估架構設計是否有問題（引入 Adapter/Facade）</span></span></code></pre></div><hr>
<h4 id="322-a2-feature-envy-檢測">3.2.2 A2. Feature Envy 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>直接依賴指標</strong>: 外層是否直接依賴內層的具體類別</li>
<li><strong>欄位存取指標</strong>: 外層存取內層的內部欄位次數</li>
<li><strong>ViewModel 缺失指標</strong>: Layer 2 是否缺少資料轉換</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好設計:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- UI 依賴 ViewModel，不依賴 Domain Entity
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Controller 包含 Presenter 轉換邏輯
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 透過介面依賴，不依賴具體實作
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Feature Envy:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- UI 直接依賴 Domain Entity
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 直接存取 Entity 的內部欄位（如 book.isbn.value）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 缺少 ViewModel 或 Presenter 轉換層
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 欄位存取次數 &gt; 3 次</span></span></code></pre></div><p><strong>檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 UI Widget 的依賴
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 是否直接依賴 Domain Entity？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  └─ 是否透過 ViewModel？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 檢查 Controller 是否包含 Presenter
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 是否有 toViewModel() 轉換方法？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  └─ 是否直接將 Entity 傳給 UI？
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 3: 統計內層欄位存取次數
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ 存取 Entity 內部欄位（如 .value）&gt; 3 次 → Feature Envy
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 透過 ViewModel 存取 → 良好設計</span></span></code></pre></div><hr>
<h4 id="323-a3-inappropriate-intimacy-檢測">3.2.3 A3. Inappropriate Intimacy 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>依賴方向檢查</strong>: 內層是否依賴外層</li>
<li><strong>循環依賴檢查</strong>: 是否存在雙向依賴</li>
<li><strong>Domain 純淨度檢查</strong>: Domain 是否包含 UI 或 Infrastructure 依賴</li>
</ol>
<p><strong>判斷標準</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 2.3 節依賴方向規則）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">正確依賴方向（外層→內層）:
</span></span><span class="line"><span class="ln">2</span><span class="cl">Layer 1 → Layer 2 → Layer 3 → Layer 4 ← Layer 5
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">違反依賴方向（Inappropriate Intimacy）:
</span></span><span class="line"><span class="ln">5</span><span class="cl">- Layer 5 → Layer 3（Domain 依賴 UseCase）
</span></span><span class="line"><span class="ln">6</span><span class="cl">- Layer 5 → Layer 2（Domain 依賴 Controller）
</span></span><span class="line"><span class="ln">7</span><span class="cl">- Layer 3 ← → Layer 5（循環依賴）</span></span></code></pre></div><p><strong>檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 Domain Entity 的 import 語句
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 是否 import UseCase？ →
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ├─ 是否 import Controller？ →
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  └─ 只 import 同層或 Layer 4 介面？ →
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">步驟 2: 檢查 UseCase 的依賴
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ├─ 是否依賴 Layer 4 介面？ →
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ 是否依賴 Layer 5 具體類別？ →（應透過介面）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 3: 使用工具檢測循環依賴
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ dart analyze 會報告循環依賴錯誤</span></span></code></pre></div><hr>
<h4 id="324-a4-leaky-abstraction-檢測">3.2.4 A4. Leaky Abstraction 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>介面純淨度</strong>: 介面是否包含實作細節</li>
<li><strong>參數檢查</strong>: 方法參數是否洩漏實作資訊</li>
<li><strong>回傳類型檢查</strong>: 是否回傳 Infrastructure 特定類型</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">良好抽象介面:
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 方法名稱描述「做什麼」，不描述「怎麼做」
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 參數是 Domain 概念，不是技術細節
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 不包含資料庫、網路等實作關鍵字
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">Leaky Abstraction:
</span></span><span class="line"><span class="ln">7</span><span class="cl">- 介面包含 SQL、HTTP、Cache 等關鍵字
</span></span><span class="line"><span class="ln">8</span><span class="cl">- 參數包含資料庫特定類型（如 Cursor）
</span></span><span class="line"><span class="ln">9</span><span class="cl">- 回傳類型包含框架特定類型（如 HttpResponse）</span></span></code></pre></div><p><strong>檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 Repository 介面定義
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 方法名稱是否包含實作關鍵字？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  │  - findBySql() → 洩漏 SQL
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  │  - findById() → 抽象概念
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  └─ 參數類型是否為 Domain 類型？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">     - String sql → 技術細節
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">     - String id → Domain 概念
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 2: 檢查 Event 定義
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 是否包含 UI 特定資料（如 BuildContext）？ →</span></span></code></pre></div><hr>
<h3 id="33-b-類-code-smell-檢測方法單層級問題">3.3 B 類 Code Smell 檢測方法（單層級問題）</h3>
<h4 id="331-b1-divergent-change-檢測">3.3.1 B1. Divergent Change 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>類別職責數量</strong>: 類別承擔幾個不同的職責</li>
<li><strong>變更原因數量</strong>: 有幾種不同的原因需要修改此類別</li>
<li><strong>方法分組檢查</strong>: 方法是否可以明確分組</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">單一職責類別:
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 只有 1 個變更原因
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 類別職責可以用一句話描述
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 所有方法圍繞同一個核心概念
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">Divergent Change:
</span></span><span class="line"><span class="ln">7</span><span class="cl">- &gt; 2 個變更原因（如「列表變更」和「詳情變更」）
</span></span><span class="line"><span class="ln">8</span><span class="cl">- 方法可以分為 2+ 個明確的群組
</span></span><span class="line"><span class="ln">9</span><span class="cl">- 類別名稱過於籠統（如 BookController、BookService）</span></span></code></pre></div><p><strong>檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 分析類別的 public 方法
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 將方法按職責分組
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 統計分組數量
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  ├─ 1 組 → 單一職責
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 2 組 → 考慮拆分
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  └─ &gt; 2 組 → Divergent Change
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 3: 檢查歷史修改記錄
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ git log --oneline {file}
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 分析修改原因是否多樣化</span></span></code></pre></div><hr>
<h4 id="332-b2-large-class-檢測">3.3.2 B2. Large Class 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>程式碼行數</strong>: 類別總行數</li>
<li><strong>方法數量</strong>: public 方法數量</li>
<li><strong>屬性數量</strong>: instance 變數數量</li>
</ol>
<p><strong>判斷標準</strong>（量化指標）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好大小類別:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 總行數: &lt; 200 行
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- public 方法: &lt; 10 個
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 屬性: &lt; 8 個
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">需要注意（考慮拆分）:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 總行數: 200-300 行
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- public 方法: 10-15 個
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 屬性: 8-12 個
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Large Class:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 總行數: &gt; 300 行
</span></span><span class="line"><span class="ln">13</span><span class="cl">- public 方法: &gt; 15 個
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 屬性: &gt; 12 個</span></span></code></pre></div><p><strong>自動化檢測方法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 檢測單一檔案行數</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">wc -l lib/presentation/controllers/book_controller.dart
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 檢測所有 Controller 檔案大小</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">find lib -name <span class="s2">&#34;*_controller.dart&#34;</span> -exec wc -l <span class="o">{}</span> <span class="se">\;</span> <span class="p">|</span> sort -rn
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 使用 dart analyze 檢測複雜度</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># （需要配置 analysis_options.yaml）</span></span></span></code></pre></div><hr>
<h4 id="333-b3-long-method-檢測">3.3.3 B3. Long Method 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>方法行數</strong>: 方法內程式碼行數</li>
<li><strong>巢狀層級</strong>: if/for/while 的巢狀深度</li>
<li><strong>區塊數量</strong>: 方法內邏輯區塊數量（用註解分隔）</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好方法:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 行數: &lt; 30 行
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 巢狀層級: &lt; 3 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 邏輯區塊: &lt; 3 個
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">需要注意:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 行數: 30-50 行
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 巢狀層級: 3 層
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 邏輯區塊: 3-4 個
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Long Method:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 行數: &gt; 50 行
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 巢狀層級: &gt; 3 層
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 邏輯區塊: &gt; 4 個</span></span></code></pre></div><p><strong>檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 統計方法行數
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 從方法簽名到結束大括號的行數
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 分析巢狀層級
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 統計最深的 if/for/while 巢狀深度
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 識別邏輯區塊
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ 統計註解數量（通常用來分隔邏輯區塊）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ &gt; 3 個註解區塊 → 應該拆分方法
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 4: 檢查方法命名
</span></span><span class="line"><span class="ln">12</span><span class="cl">  └─ 方法名稱是否包含「And」？ → 可能做太多事情
</span></span><span class="line"><span class="ln">13</span><span class="cl">     - validateAndSaveBook() → 應拆分為 validate() 和 save()</span></span></code></pre></div><hr>
<h4 id="334-b4-dead-code-檢測">3.3.4 B4. Dead Code 檢測</h4>
<p><strong>檢測方法</strong>:</p>
<ol>
<li><strong>使用 dart analyze 檢測 unused 警告</strong></li>
<li><strong>使用 code coverage 工具檢測 0% 覆蓋率程式碼</strong></li>
<li><strong>手動檢查註解掉的程式碼</strong></li>
</ol>
<p><strong>自動化檢測</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 檢測 unused 警告</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">dart analyze <span class="p">|</span> grep <span class="s2">&#34;unused&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 檢測程式碼覆蓋率</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">genhtml coverage/lcov.info -o coverage/html
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 檢查 coverage/html 中 0% 覆蓋率的程式碼</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 搜尋註解掉的程式碼</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">grep -r <span class="s2">&#34;^[[:space:]]*//.*{&#34;</span> lib/</span></span></code></pre></div><hr>
<h3 id="34-c-類-code-smell-檢測方法ticket-粒度問題">3.4 C 類 Code Smell 檢測方法（Ticket 粒度問題）</h3>
<h4 id="341-c1-god-ticket-檢測">3.4.1 C1. God Ticket 檢測</h4>
<p><strong>檢測指標</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節）:</p>
<ol>
<li><strong>檔案修改數量</strong>: 計算 git diff 涉及的檔案數</li>
<li><strong>層級跨度</strong>: 涉及幾個架構層級</li>
<li><strong>預估工時</strong>: Ticket 的預估完成時間</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好 Ticket 粒度:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 檔案數: 1-5 個
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 層級跨度: 1 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 預估工時: 2-8 小時（1 個工作天內）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">需要拆分:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 檔案數: 6-10 個
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 層級跨度: 2 層
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 預估工時: 8-16 小時（1-2 天）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">God Ticket:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 檔案數: &gt; 10 個
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 層級跨度: &gt; 2 層
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 預估工時: &gt; 16 小時（&gt; 2 天）</span></span></code></pre></div><p><strong>檢測流程</strong>（Ticket 設計階段）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 列出 Ticket 需要修改的檔案清單
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">步驟 2: 統計檔案數量
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 3: 使用[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 2.4 節決策樹判斷每個檔案的層級
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 4: 計算層級跨度
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 5: 評估預估工時
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">判斷:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ 符合良好標準 → 可執行
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 符合需要拆分標準 → 建議拆分
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 符合 God Ticket 標準 → 強制拆分</span></span></code></pre></div><hr>
<h4 id="342-c2-incomplete-ticket-檢測">3.4.2 C2. Incomplete Ticket 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>測試檔案檢查</strong>: 是否有對應的測試檔案</li>
<li><strong>驗收條件檢查</strong>: Ticket 描述是否包含驗收條件</li>
<li><strong>工作日誌檢查</strong>: 是否完成 TDD 四階段記錄</li>
</ol>
<p><strong>判斷標準</strong>（基於 TDD 四階段要求）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">完整 Ticket:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- Phase 1: 功能設計完成
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Phase 2: 測試設計完成（測試檔案存在）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- Phase 3: 實作完成（程式碼檔案）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- Phase 4: 重構評估完成
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Incomplete Ticket:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 缺少測試檔案（Phase 2 未完成）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 缺少驗收條件（Phase 1 設計不完整）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 缺少工作日誌（無法追蹤進度）</span></span></code></pre></div><p><strong>檢測流程</strong>（Code Review 階段）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 git diff 中的檔案
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 是否包含 test/ 目錄的檔案？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  └─ 測試檔案數量 vs 程式碼檔案數量比例
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 檢查 Ticket 描述
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  └─ 是否包含「驗收條件」章節？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 3: 檢查工作日誌
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ docs/work-logs/vX.Y.Z-*.md 是否存在？
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 是否完成 Phase 1-4 記錄？</span></span></code></pre></div><hr>
<h4 id="343-c3-ambiguous-responsibility-檢測">3.4.3 C3. Ambiguous Responsibility 檢測</h4>
<p><strong>檢測指標</strong>:</p>
<ol>
<li><strong>Ticket 標題格式</strong>: 是否包含層級標示</li>
<li><strong>職責描述清晰度</strong>: 是否明確說明修改哪一層</li>
<li><strong>驗收條件對應性</strong>: 驗收條件是否對應單一層級</li>
</ol>
<p><strong>判斷標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">職責明確 Ticket:
</span></span><span class="line"><span class="ln">2</span><span class="cl">- 標題: [Layer X] 清楚的功能描述
</span></span><span class="line"><span class="ln">3</span><span class="cl">- 描述: 明確說明修改哪一層的哪個檔案
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 驗收: 只驗證該層級的職責
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">職責模糊 Ticket:
</span></span><span class="line"><span class="ln">7</span><span class="cl">- 標題: 沒有 [Layer X] 標示
</span></span><span class="line"><span class="ln">8</span><span class="cl">- 描述: 混合多個層級的職責
</span></span><span class="line"><span class="ln">9</span><span class="cl">- 驗收: 跨多個層級的驗證</span></span></code></pre></div><p><strong>檢測流程</strong>（Ticket 設計階段）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查 Ticket 標題格式
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 符合 [Layer X] 格式？ →
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  └─ 無層級標示？ →
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 分析 Ticket 描述
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  └─ 能否用一句話描述單一層級的職責？
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 3: 檢查驗收條件
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 所有驗收條件都屬於同一層級？ →
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 驗收條件跨多層？ →</span></span></code></pre></div><hr>
<h3 id="35-檢測方法總結表">3.5 檢測方法總結表</h3>
<table>
  <thead>
      <tr>
          <th>Code Smell</th>
          <th>檢測時機</th>
          <th>檢測指標</th>
          <th>判斷標準</th>
          <th>引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>A1. Shotgun Surgery</strong></td>
          <td>Ticket 設計</td>
          <td>層級跨度</td>
          <td>&gt; 2 層</td>
          <td>3.1 單層修改原則</td>
      </tr>
      <tr>
          <td><strong>A2. Feature Envy</strong></td>
          <td>Code Review</td>
          <td>直接依賴 Domain</td>
          <td>UI 存取 Entity</td>
          <td>2.2 Layer 2 職責</td>
      </tr>
      <tr>
          <td><strong>A3. Inappropriate Intimacy</strong></td>
          <td>Code Review</td>
          <td>依賴方向</td>
          <td>內層依賴外層</td>
          <td>2.3 依賴方向規則</td>
      </tr>
      <tr>
          <td><strong>A4. Leaky Abstraction</strong></td>
          <td>介面設計</td>
          <td>介面純淨度</td>
          <td>包含實作關鍵字</td>
          <td>2.2 Layer 4 職責</td>
      </tr>
      <tr>
          <td><strong>B1. Divergent Change</strong></td>
          <td>Phase 4 重構</td>
          <td>方法分組數</td>
          <td>&gt; 2 組</td>
          <td>3.2 SRP 理論</td>
      </tr>
      <tr>
          <td><strong>B2. Large Class</strong></td>
          <td>Phase 4 重構</td>
          <td>程式碼行數</td>
          <td>&gt; 300 行</td>
          <td>5.2 量化指標</td>
      </tr>
      <tr>
          <td><strong>B3. Long Method</strong></td>
          <td>Phase 3 實作</td>
          <td>方法行數</td>
          <td>&gt; 50 行</td>
          <td>5.2 量化指標</td>
      </tr>
      <tr>
          <td><strong>B4. Dead Code</strong></td>
          <td>Phase 4 重構</td>
          <td>unused 警告</td>
          <td>dart analyze</td>
          <td>-</td>
      </tr>
      <tr>
          <td><strong>C1. God Ticket</strong></td>
          <td>Ticket 設計</td>
          <td>檔案數</td>
          <td>&gt; 10 個</td>
          <td>5.2 Ticket 粒度</td>
      </tr>
      <tr>
          <td><strong>C2. Incomplete Ticket</strong></td>
          <td>Code Review</td>
          <td>測試檔案</td>
          <td>缺少測試</td>
          <td>TDD 四階段</td>
      </tr>
      <tr>
          <td><strong>C3. Ambiguous Responsibility</strong></td>
          <td>Ticket 設計</td>
          <td>標題格式</td>
          <td>無層級標示</td>
          <td>5.3 Ticket 範例</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="第四章重構建議和策略">第四章：重構建議和策略</h2>
<h3 id="41-重構模式對應表">4.1 重構模式對應表</h3>
<p>每種 Code Smell 都有對應的標準重構模式（引用 Martin Fowler 的 Refactoring 書籍）:</p>
<table>
  <thead>
      <tr>
          <th>Code Smell</th>
          <th>重構模式</th>
          <th>重構策略</th>
          <th>預期效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>A1. Shotgun Surgery</strong></td>
          <td>Extract Interface + Introduce Facade</td>
          <td>引入抽象層隔離變更</td>
          <td>單層修改</td>
      </tr>
      <tr>
          <td><strong>A2. Feature Envy</strong></td>
          <td>Move Method + Extract ViewModel</td>
          <td>移動邏輯到正確層級</td>
          <td>職責對齊</td>
      </tr>
      <tr>
          <td><strong>A3. Inappropriate Intimacy</strong></td>
          <td>Introduce Parameter Object</td>
          <td>打破循環依賴</td>
          <td>依賴方向正確</td>
      </tr>
      <tr>
          <td><strong>A4. Leaky Abstraction</strong></td>
          <td>Extract Interface</td>
          <td>重新設計抽象介面</td>
          <td>隱藏實作細節</td>
      </tr>
      <tr>
          <td><strong>B1. Divergent Change</strong></td>
          <td>Extract Class</td>
          <td>拆分為多個單一職責類別</td>
          <td>符合 SRP</td>
      </tr>
      <tr>
          <td><strong>B2. Large Class</strong></td>
          <td>Extract Class + Move Method</td>
          <td>拆分大類別</td>
          <td>降低複雜度</td>
      </tr>
      <tr>
          <td><strong>B3. Long Method</strong></td>
          <td>Extract Method</td>
          <td>拆分長方法</td>
          <td>提升可讀性</td>
      </tr>
      <tr>
          <td><strong>B4. Dead Code</strong></td>
          <td>Remove Dead Code</td>
          <td>直接刪除</td>
          <td>程式碼簡潔</td>
      </tr>
      <tr>
          <td><strong>C1. God Ticket</strong></td>
          <td>Split Ticket</td>
          <td>拆分為多個單層 Ticket</td>
          <td>降低風險</td>
      </tr>
      <tr>
          <td><strong>C2. Incomplete Ticket</strong></td>
          <td>Add Missing Tests</td>
          <td>補充測試和文件</td>
          <td>完整性</td>
      </tr>
      <tr>
          <td><strong>C3. Ambiguous Responsibility</strong></td>
          <td>Clarify Responsibility</td>
          <td>明確層級和職責</td>
          <td>職責清晰</td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="42-重構策略詳細說明">4.2 重構策略詳細說明</h3>
<h4 id="421-a1-shotgun-surgery--extract-interface--introduce-facade">4.2.1 A1. Shotgun Surgery → Extract Interface + Introduce Facade</h4>
<p><strong>問題</strong>: 單一變更需要同時修改多個層級</p>
<p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 分析變更的共同點
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 識別哪些變更是因為相同的業務需求
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 引入 Facade 層
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 建立統一的介面封裝跨層操作
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 重構為單層修改
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ Layer 4: 定義 Facade 介面
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ Layer 3: 實作 Facade
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ Layer 2: 呼叫 Facade
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">步驟 4: 驗證重構結果
</span></span><span class="line"><span class="ln">13</span><span class="cl">  └─ 未來相同變更只需要修改 Facade 實作</span></span></code></pre></div><p><strong>完整範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：Before: 新增欄位需要修改 4 層
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// Layer 1: UI 新增 Widget
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Layer 2: Controller 新增屬性
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// Layer 3: UseCase 新增參數
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// Layer 5: Entity 新增欄位
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// 正例：After: 引入 BookDetailFacade
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// Layer 4: 定義介面
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">IBookDetailFacade</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookDetailViewModel</span><span class="o">&gt;</span> <span class="n">getBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// Layer 3: 實作 Facade（統一處理資料整合）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailFacade</span> <span class="kd">implements</span> <span class="n">IBookDetailFacade</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="kd">final</span> <span class="n">IBookRepository</span> <span class="n">bookRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="kd">final</span> <span class="n">IRatingRepository</span> <span class="n">ratingRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">BookDetailViewModel</span><span class="o">&gt;</span> <span class="n">getBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="kd">final</span> <span class="n">rating</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">ratingRepository</span><span class="p">.</span><span class="n">findByBookId</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="n">BookPresenter</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">book</span><span class="p">,</span> <span class="n">rating</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1">// 重構效果:
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1">// 未來新增欄位只需要修改 Facade 實作（Layer 3）
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="n">Layer</span> <span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">5</span> <span class="err">都不需要修改</span></span></span></code></pre></div><hr>
<h4 id="422-a2-feature-envy--move-method--extract-viewmodel">4.2.2 A2. Feature Envy → Move Method + Extract ViewModel</h4>
<p><strong>問題</strong>: 外層過度依賴內層的實作細節</p>
<p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 識別 Feature Envy 位置
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 外層存取內層內部欄位 &gt; 3 次
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 引入 ViewModel
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ Layer 2 建立 ViewModel 類別
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 建立 Presenter 轉換方法
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ Layer 2 實作 toViewModel(Entity) → ViewModel
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 4: 重構 UI 依賴
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ UI 改為依賴 ViewModel，不依賴 Entity</span></span></code></pre></div><hr>
<h4 id="423-b1-divergent-change--extract-class">4.2.3 B1. Divergent Change → Extract Class</h4>
<p><strong>問題</strong>: 單一類別承擔多個職責</p>
<p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 分析方法分組
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 將 public 方法按職責分組
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 為每個分組建立新類別
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 拆分為 BookListController, BookDetailController, BookSearchController
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 移動方法到新類別
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ Move Method 重構
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 4: 更新依賴關係
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 更新 Widget 的依賴</span></span></code></pre></div><hr>
<h4 id="424-b3-long-method--extract-method">4.2.4 B3. Long Method → Extract Method</h4>
<p><strong>問題</strong>: 方法過長（&gt; 50 行）</p>
<p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 識別邏輯區塊
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ 統計註解數量（每個註解代表一個邏輯區塊）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 為每個區塊建立私有方法
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ Extract Method 重構
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 重新命名方法
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ 使用動詞片語描述方法功能
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 4: 驗證重構結果
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ 主方法 &lt; 30 行
</span></span><span class="line"><span class="ln">12</span><span class="cl">  └─ 每個私有方法 &lt; 20 行</span></span></code></pre></div><hr>
<h3 id="43-重構優先級評估標準">4.3 重構優先級評估標準</h3>
<p><strong>評估維度</strong>:</p>
<ol>
<li><strong>影響範圍</strong>: 影響多少檔案和層級（1-5 分）</li>
<li><strong>業務風險</strong>: 是否影響核心業務流程（1-5 分）</li>
<li><strong>累積速度</strong>: 不修正會多快惡化（1-5 分）</li>
</ol>
<p><strong>優先級評估公式</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">優先級分數 = (影響範圍 × 3) + (業務風險 × 2) + (累積速度 × 1)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">影響範圍評分:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">1 分: 單一檔案
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">2 分: 2-3 個檔案
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">3 分: 4-5 個檔案（單層）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">4 分: 6-10 個檔案（跨 2 層）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">5 分: &gt; 10 個檔案（跨 3+ 層）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">業務風險評分:
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">1 分: 輔助功能（UI 優化）
</span></span><span class="line"><span class="ln">14</span><span class="cl">2 分: 次要功能（搜尋）
</span></span><span class="line"><span class="ln">15</span><span class="cl">3 分: 常用功能（列表顯示）
</span></span><span class="line"><span class="ln">16</span><span class="cl">4 分: 重要功能（新增書籍）
</span></span><span class="line"><span class="ln">17</span><span class="cl">5 分: 核心功能（資料同步）
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">累積速度評分:
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">1 分: 已穩定，不再惡化
</span></span><span class="line"><span class="ln">22</span><span class="cl">2 分: 偶爾新增（每季 1 次）
</span></span><span class="line"><span class="ln">23</span><span class="cl">3 分: 定期新增（每月 1-2 次）
</span></span><span class="line"><span class="ln">24</span><span class="cl">4 分: 頻繁新增（每週 1 次）
</span></span><span class="line"><span class="ln">25</span><span class="cl">5 分: 持續惡化（每天都在新增）
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">優先級判斷:
</span></span><span class="line"><span class="ln">28</span><span class="cl">分數 &gt; 20 → 高優先級（立即修正）
</span></span><span class="line"><span class="ln">29</span><span class="cl">分數 10-20 → 中優先級（排入下個版本）
</span></span><span class="line"><span class="ln">30</span><span class="cl">分數 &lt; 10 → 低優先級（重構階段處理）</span></span></code></pre></div><p><strong>優先級矩陣範例</strong>:</p>
<table>
  <thead>
      <tr>
          <th>Code Smell</th>
          <th>影響範圍</th>
          <th>業務風險</th>
          <th>累積速度</th>
          <th>分數</th>
          <th>優先級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Inappropriate Intimacy</td>
          <td>4</td>
          <td>5</td>
          <td>3</td>
          <td>26</td>
          <td>高</td>
      </tr>
      <tr>
          <td>Shotgun Surgery</td>
          <td>5</td>
          <td>4</td>
          <td>2</td>
          <td>25</td>
          <td>高</td>
      </tr>
      <tr>
          <td>God Ticket</td>
          <td>5</td>
          <td>3</td>
          <td>3</td>
          <td>24</td>
          <td>高</td>
      </tr>
      <tr>
          <td>Feature Envy</td>
          <td>3</td>
          <td>3</td>
          <td>3</td>
          <td>15</td>
          <td>中</td>
      </tr>
      <tr>
          <td>Large Class</td>
          <td>2</td>
          <td>3</td>
          <td>4</td>
          <td>16</td>
          <td>中</td>
      </tr>
      <tr>
          <td>Long Method</td>
          <td>1</td>
          <td>2</td>
          <td>3</td>
          <td>8</td>
          <td>低</td>
      </tr>
      <tr>
          <td>Dead Code</td>
          <td>1</td>
          <td>1</td>
          <td>1</td>
          <td>4</td>
          <td>低</td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="44-重構風險控制策略">4.4 重構風險控制策略</h3>
<p><strong>風險控制原則</strong>:</p>
<ol>
<li><strong>測試覆蓋率要求</strong>: 重構前必須確保測試覆蓋率 100%</li>
<li><strong>漸進式重構</strong>: 每次只重構一個 Code Smell</li>
<li><strong>回滾計畫</strong>: 準備 git revert 的回滾點</li>
</ol>
<p><strong>漸進式重構流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 建立 feature branch
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ git checkout -b refactor/fix-shotgun-surgery
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 確保測試 100% 通過
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ flutter test（重構前基準）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 執行重構
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ 每完成一個小步驟都執行測試
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">步驟 4: 提交重構結果
</span></span><span class="line"><span class="ln">11</span><span class="cl">  └─ git commit -m &#34;refactor: extract BookDetailFacade&#34;
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">步驟 5: Code Review
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ 確保重構符合層級隔離原則
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 6: 合併到主線
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ git merge --no-ff refactor/fix-shotgun-surgery</span></span></code></pre></div><p><strong>測試覆蓋率監控</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 重構前</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 記錄覆蓋率基準（如 85%）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 重構後</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 確保覆蓋率不降低（≥ 85%）</span></span></span></code></pre></div><hr>
<h2 id="第五章開發階段檢查清單">第五章：開發階段檢查清單</h2>
<h3 id="51-phase-1-設計階段檢查清單ticket-設計">5.1 Phase 1 設計階段檢查清單（Ticket 設計）</h3>
<p><strong>目標</strong>: 在設計階段就發現 Code Smell，避免實作後才修正</p>
<p><strong>檢查項目</strong>:</p>
<h4 id="層級定位檢查">層級定位檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> Ticket 標題包含層級標示（如 [Layer 2]）</li>
<li><input disabled="" type="checkbox"> 職責描述清楚說明修改哪一層</li>
<li><input disabled="" type="checkbox"> 使用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 2.4 節決策樹確認層級定位正確</li>
</ul>
<p><strong>單層修改檢查</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 3.1 節）</p>
<ul>
<li><input disabled="" type="checkbox"> 所有檔案都屬於同一層級</li>
<li><input disabled="" type="checkbox"> 變更原因單一且明確</li>
<li><input disabled="" type="checkbox"> 不需要同步修改其他層級</li>
</ul>
<p><strong>Ticket 粒度檢查</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節）</p>
<ul>
<li><input disabled="" type="checkbox"> 檔案數: 1-5 個</li>
<li><input disabled="" type="checkbox"> 預估工時: 2-8 小時（1 個工作天內）</li>
<li><input disabled="" type="checkbox"> 如果超過標準，規劃拆分策略</li>
</ul>
<h5 id="code-smell-預防檢查">Code Smell 預防檢查</h5>
<ul>
<li><input disabled="" type="checkbox"> 檢查是否有 Shotgun Surgery 風險（層級跨度 &gt; 1）</li>
<li><input disabled="" type="checkbox"> 檢查是否有 God Ticket 風險（檔案數 &gt; 5）</li>
<li><input disabled="" type="checkbox"> 檢查是否有 Ambiguous Responsibility 風險（職責不明確）</li>
</ul>
<h6 id="依賴關係檢查">依賴關係檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 依賴的內層介面已存在（或同時設計）</li>
<li><input disabled="" type="checkbox"> 依賴方向正確（外層→內層）</li>
<li><input disabled="" type="checkbox"> 不存在循環依賴</li>
</ul>
<hr>
<h3 id="52-phase-2-測試設計階段檢查清單">5.2 Phase 2 測試設計階段檢查清單</h3>
<p><strong>目標</strong>: 確保測試範圍限定在單一層級</p>
<p><strong>檢查項目</strong>:</p>
<p><strong>測試範圍檢查</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 6.4 節）</p>
<ul>
<li><input disabled="" type="checkbox"> 測試只驗證該層級的職責</li>
<li><input disabled="" type="checkbox"> 不需要啟動其他層級（使用 Mock）</li>
<li><input disabled="" type="checkbox"> 測試檔案路徑對應層級結構</li>
</ul>
<h4 id="測試獨立性檢查">測試獨立性檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> 測試不依賴外部資源（資料庫、網路）</li>
<li><input disabled="" type="checkbox"> 測試可以獨立執行（不依賴其他測試）</li>
<li><input disabled="" type="checkbox"> 使用 Mock/Stub 隔離依賴</li>
</ul>
<h5 id="測試完整性檢查">測試完整性檢查</h5>
<ul>
<li><input disabled="" type="checkbox"> 正常流程測試（Happy Path）</li>
<li><input disabled="" type="checkbox"> 異常流程測試（Error Cases）</li>
<li><input disabled="" type="checkbox"> 邊界條件測試（Boundary Conditions）</li>
</ul>
<h6 id="code-smell-檢查">Code Smell 檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 檢查是否有 Incomplete Ticket 風險（缺少測試）</li>
<li><input disabled="" type="checkbox"> 測試覆蓋率目標設定（100%）</li>
</ul>
<hr>
<h3 id="53-phase-3-實作階段檢查清單">5.3 Phase 3 實作階段檢查清單</h3>
<p><strong>目標</strong>: 確保實作符合層級隔離原則，不產生 Code Smell</p>
<p><strong>檢查項目</strong>:</p>
<h4 id="程式碼品質檢查">程式碼品質檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> 方法行數 &lt; 50 行（避免 Long Method）</li>
<li><input disabled="" type="checkbox"> 類別行數 &lt; 300 行（避免 Large Class）</li>
<li><input disabled="" type="checkbox"> 巢狀層級 &lt; 3 層</li>
<li><input disabled="" type="checkbox"> 使用 package 導入格式（避免相對路徑）</li>
</ul>
<h5 id="層級隔離檢查">層級隔離檢查</h5>
<ul>
<li><input disabled="" type="checkbox"> import 語句只引用內層或同層</li>
<li><input disabled="" type="checkbox"> 不存在內層依賴外層的情況</li>
<li><input disabled="" type="checkbox"> 使用介面依賴，不依賴具體實作</li>
</ul>
<h6 id="code-smell-檢查-1">Code Smell 檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 檢查是否有 Feature Envy（UI 直接存取 Domain）</li>
<li><input disabled="" type="checkbox"> 檢查是否有 Inappropriate Intimacy（依賴方向錯誤）</li>
<li><input disabled="" type="checkbox"> 檢查是否有 Leaky Abstraction（介面洩漏實作）</li>
<li><input disabled="" type="checkbox"> 檢查是否有 Divergent Change（方法可分組）</li>
</ul>
<h6 id="測試執行檢查">測試執行檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 所有測試 100% 通過</li>
<li><input disabled="" type="checkbox"> dart analyze 無錯誤和警告</li>
<li><input disabled="" type="checkbox"> 程式碼覆蓋率達到 100%</li>
</ul>
<hr>
<h3 id="54-phase-4-重構階段檢查清單">5.4 Phase 4 重構階段檢查清單</h3>
<p><strong>目標</strong>: 識別需要重構的 Code Smell</p>
<p><strong>檢查項目</strong>:</p>
<h4 id="code-smell-掃描">Code Smell 掃描</h4>
<ul>
<li><input disabled="" type="checkbox"> 使用 dart analyze 檢測 unused 警告（Dead Code）</li>
<li><input disabled="" type="checkbox"> 檢查方法行數和類別行數（Long Method, Large Class）</li>
<li><input disabled="" type="checkbox"> 檢查方法分組（Divergent Change）</li>
<li><input disabled="" type="checkbox"> 檢查依賴方向（Inappropriate Intimacy）</li>
</ul>
<h5 id="重構優先級評估">重構優先級評估</h5>
<ul>
<li><input disabled="" type="checkbox"> 計算影響範圍（1-5）</li>
<li><input disabled="" type="checkbox"> 評估業務風險（1-5）</li>
<li><input disabled="" type="checkbox"> 評估累積速度（1-5）</li>
<li><input disabled="" type="checkbox"> 計算優先級分數</li>
</ul>
<h6 id="重構執行檢查">重構執行檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 重構前測試覆蓋率基準</li>
<li><input disabled="" type="checkbox"> 漸進式重構（每次一個 Code Smell）</li>
<li><input disabled="" type="checkbox"> 重構後測試覆蓋率不降低</li>
<li><input disabled="" type="checkbox"> Code Review 確認重構正確性</li>
</ul>
<h6 id="重構完成檢查">重構完成檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> Code Smell 已修正</li>
<li><input disabled="" type="checkbox"> 所有測試通過</li>
<li><input disabled="" type="checkbox"> 工作日誌記錄重構決策</li>
</ul>
<hr>
<h6 id="第一批次撰寫完成第一章到第五章">第一批次撰寫完成（第一章到第五章）</h6>
<hr>
<h2 id="第六章code-review-檢查清單">第六章：Code Review 檢查清單</h2>
<h3 id="61-快速檢查5-分鐘">6.1 快速檢查（5 分鐘）</h3>
<p><strong>目標</strong>: 快速識別 PR 中的明顯 Code Smell</p>
<p><strong>檢查項目</strong>:</p>
<p><strong>層級隔離快速檢查</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 6.2 節）</p>
<ul>
<li>
<p><input disabled="" type="checkbox"> <strong>檔案路徑檢查</strong>: 所有修改檔案都屬於同一層級？</p>
<ul>
<li>使用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 2.4 節決策樹快速判斷</li>
<li>如果跨多層 → 檢查是否有 Shotgun Surgery</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>import 語句檢查</strong>: 依賴方向正確？</p>
<ul>
<li>檢查是否有內層依賴外層（Inappropriate Intimacy）</li>
<li>檢查是否有 UI 直接 import Domain Entity（Feature Envy）</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>測試檔案檢查</strong>: 測試路徑對應層級結構？</p>
<ul>
<li>test/ 目錄結構是否對應 lib/ 結構</li>
<li>測試檔案數量是否與程式碼檔案數量相當</li>
</ul>
</li>
</ul>
<h4 id="ticket-粒度快速檢查">Ticket 粒度快速檢查</h4>
<ul>
<li>
<p><input disabled="" type="checkbox"> <strong>檔案數量 &lt; 5 個？</strong></p>
<ul>
<li>
<blockquote>
<p>5 個檔案 → 可能是 God Ticket</p></blockquote>
</li>
<li>
<blockquote>
<p>10 個檔案 → 強烈建議拆分</p></blockquote>
</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>程式碼變更行數合理（&lt; 500 行）？</strong></p>
<ul>
<li>變更行數過多可能暗示 Ticket 範圍過大</li>
</ul>
</li>
</ul>
<h5 id="明顯-code-smell-檢查">明顯 Code Smell 檢查</h5>
<ul>
<li>
<p><input disabled="" type="checkbox"> <strong>UI 層是否包含業務邏輯？</strong></p>
<ul>
<li>檢查 Widget 中是否有業務規則判斷</li>
<li>檢查是否有業務計算邏輯</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>Domain 層是否依賴外層？</strong></p>
<ul>
<li>檢查 Domain Entity 的 import 語句</li>
<li>確認沒有依賴 UseCase 或 Controller</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>是否有註解掉的程式碼？</strong></p>
<ul>
<li>註解掉的程式碼應該刪除，不應保留</li>
</ul>
</li>
</ul>
<hr>
<h3 id="62-深度檢查15-分鐘">6.2 深度檢查（15 分鐘）</h3>
<p><strong>目標</strong>: 全面檢查所有類別的 Code Smell</p>
<h4 id="a-類-code-smell-檢查跨層級">A 類 Code Smell 檢查（跨層級）</h4>
<h5 id="shotgun-surgery-檢查">Shotgun Surgery 檢查</h5>
<ul>
<li><input disabled="" type="checkbox"> 統計 PR 修改的檔案數和層級跨度</li>
<li><input disabled="" type="checkbox"> 檢查是否有單一變更需要修改多個層級</li>
<li><input disabled="" type="checkbox"> 評估是否應該引入 Facade 隔離變更</li>
</ul>
<h6 id="feature-envy-檢查">Feature Envy 檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> 檢查 UI 是否直接依賴 Entity</p>
<ul>
<li>搜尋 <code>import .*/domains/.*/entities/</code></li>
<li>確認 UI 使用 ViewModel</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 統計外層存取內層內部欄位次數</p>
<ul>
<li>超過 3 次 → Feature Envy</li>
<li>建議引入 Presenter 轉換</li>
</ul>
</li>
</ul>
<h6 id="inappropriate-intimacy-檢查">Inappropriate Intimacy 檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> 檢查依賴方向是否正確</p>
<ul>
<li>Domain 不應依賴外層</li>
<li>UseCase 應依賴介面，不依賴具體實作</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查是否有循環依賴</p>
<ul>
<li>執行 <code>dart analyze</code> 確認</li>
</ul>
</li>
</ul>
<h6 id="leaky-abstraction-檢查">Leaky Abstraction 檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> 檢查 Repository 介面是否洩漏實作細節</p>
<ul>
<li>方法名稱不應包含 SQL、HTTP、Cache 等關鍵字</li>
<li>參數類型應該是 Domain 概念</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查 Event 定義是否包含 UI 特定資料</p>
<ul>
<li>不應包含 BuildContext 等 UI 類型</li>
</ul>
</li>
</ul>
<h6 id="b-類-code-smell-檢查單層級">B 類 Code Smell 檢查（單層級）</h6>
<h6 id="divergent-change-檢查">Divergent Change 檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 分析類別方法是否可以分組
<ul>
<li>
<blockquote>
<p>2 個群組 → Divergent Change</p></blockquote>
</li>
<li>建議拆分為多個單一職責類別</li>
</ul>
</li>
</ul>
<h6 id="large-class-檢查">Large Class 檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> 檢查類別行數是否超過 300 行</p>
<ul>
<li>使用 <code>wc -l {file}</code> 檢查</li>
<li>超過標準 → 建議拆分</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查 public 方法數量是否超過 15 個</p>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查屬性數量是否超過 12 個</p>
</li>
</ul>
<h6 id="long-method-檢查">Long Method 檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> 檢查方法行數是否超過 50 行</p>
<ul>
<li>超過標準 → 建議 Extract Method</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查巢狀層級是否超過 3 層</p>
<ul>
<li>過深巢狀 → 難以理解和測試</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> 檢查方法名稱是否包含「And」</p>
<ul>
<li>如 <code>validateAndSave</code> → 應拆分</li>
</ul>
</li>
</ul>
<h6 id="dead-code-檢查">Dead Code 檢查</h6>
<ul>
<li><input disabled="" type="checkbox"> 執行 <code>dart analyze | grep &quot;unused&quot;</code>
<ul>
<li>檢查是否有 unused 警告</li>
<li>確認所有警告都已處理</li>
</ul>
</li>
</ul>
<h6 id="測試完整性檢查-1">測試完整性檢查</h6>
<ul>
<li>
<p><input disabled="" type="checkbox"> <strong>測試覆蓋率是否達到 100%？</strong></p>
<ul>
<li>執行 <code>flutter test --coverage</code></li>
<li>檢查 coverage 報告</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>測試是否包含異常流程？</strong></p>
<ul>
<li>確認有 Error Cases 測試</li>
</ul>
</li>
<li>
<p><input disabled="" type="checkbox"> <strong>測試是否獨立（不依賴外部資源）？</strong></p>
<ul>
<li>確認使用 Mock/Stub 隔離依賴</li>
</ul>
</li>
</ul>
<hr>
<h3 id="63-違規模式識別引用層級隔離派工方法論-第-65-節">6.3 違規模式識別（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 6.5 節）</h3>
<p><strong>常見違規模式</strong>:</p>
<h4 id="違規模式-1-ui-層包含業務邏輯">違規模式 1: UI 層包含業務邏輯</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：違規
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1">// 反例：業務規則不應在 UI 層
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">publicationDate</span><span class="p">.</span><span class="n">year</span> <span class="o">&gt;=</span> <span class="m">2024</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="k">return</span> <span class="n">Text</span><span class="p">(</span><span class="s1">&#39;新書&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">// 反例：業務計算不應在 UI 層
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">discountedPrice</span> <span class="o">=</span> <span class="n">book</span><span class="p">.</span><span class="n">price</span> <span class="o">*</span> <span class="m">0.9</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">Text</span><span class="p">(</span><span class="s1">&#39;優惠價: </span><span class="si">$</span><span class="n">discountedPrice</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// 正例：業務邏輯在 Domain 層
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="kt">bool</span> <span class="n">isNewRelease</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">publicationDate</span><span class="p">.</span><span class="n">year</span> <span class="o">&gt;=</span> <span class="m">2024</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="kt">double</span> <span class="n">getDiscountedPrice</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="n">price</span> <span class="o">*</span> <span class="m">0.9</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1">// 正例：UI 使用 ViewModel
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">isNew</span><span class="p">)</span> <span class="n">Text</span><span class="p">(</span><span class="s1">&#39;新書&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="s1">&#39;優惠價: </span><span class="si">${</span><span class="n">viewModel</span><span class="p">.</span><span class="n">discountedPrice</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h4 id="違規模式-2-controller-包含業務規則">違規模式 2: Controller 包含業務規則</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：違規
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kt">void</span> <span class="n">validateBook</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1">// 反例：業務規則應在 Domain 層
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;ISBN 必須是 13 碼&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// 正例：業務規則在 Domain 層
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">ISBN</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">value</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="n">ISBN</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">value</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">      <span class="k">throw</span> <span class="n">ValidationException</span><span class="p">(</span><span class="s1">&#39;ISBN 必須是 13 碼&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h4 id="違規模式-3-usecase-包含-ui-邏輯">違規模式 3: UseCase 包含 UI 邏輯</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：違規
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">GetBookDetailUseCase</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">String</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">repository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1">// 反例：UI 格式化不應在 UseCase
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="s1">&#39;書名: </span><span class="si">${</span><span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 正例：UseCase 回傳 Domain 類型
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">GetBookDetailUseCase</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="n">Book</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="kd">await</span> <span class="n">repository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// 正例：Presenter 負責轉換
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookPresenter</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kd">static</span> <span class="n">BookViewModel</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">BookViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">      <span class="nl">displayText:</span> <span class="s1">&#39;書名: </span><span class="si">${</span><span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h3 id="64-code-review-報告模板">6.4 Code Review 報告模板</h3>
<p><strong>Code Smell 檢測報告格式</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># Code Smell 檢測報告
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**檢測時間**</span>: 2025-10-11 14:30
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**檢測範圍**</span>: PR <span class="ni">#123</span> - [Layer 2] 實作書籍詳情頁面事件處理
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**Reviewer**</span>: <span class="ni">@reviewer</span>-name
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">## 檢測總結
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> **高優先級問題**: 1 個
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> **中優先級問題**: 1 個
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> **低優先級問題**: 0 個
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> **總體評估**: 需要修正後再合併
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">---
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">## 高優先級問題
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### Shotgun Surgery 檢測結果
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span>**檔案清單**:
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> lib/presentation/widgets/book_detail_widget.dart (Layer 1)
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> lib/presentation/controllers/book_detail_controller.dart (Layer 2)
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">-</span> lib/application/use_cases/get_book_detail_use_case.dart (Layer 3)
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">-</span> lib/domain/entities/book.dart (Layer 5)
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gs">**分析**</span>:
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> 檔案數: 4 個
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 層級跨度: 4 層（Layer 1, 2, 3, 5）
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 判斷: Shotgun Surgery
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gs">**建議**</span>:
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> 拆分為 4 個獨立 Ticket
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 每個 Ticket 只修改單一層級
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">-</span> 引入 Facade 隔離變更
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gs">**影響評估**</span>:
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 影響範圍: 5 分（跨 4 層）
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 業務風險: 4 分（重要功能）
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">-</span> 累積速度: 2 分（偶爾新增）
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">-</span> 優先級分數: 25（高優先級）
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">---
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="gu">## 中優先級問題
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="gu">### 警告 Large Class 檢測結果
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="gs">**檔案**</span>: <span class="sb">`lib/presentation/controllers/book_controller.dart`</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="gs">**分析**</span>:
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">-</span> 總行數: 320 行（超過 300 行標準）
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="k">-</span> public 方法: 18 個（超過 15 個標準）
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="k">-</span> 方法分組: 3 組（列表、詳情、搜尋）
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="gs">**建議**</span>:
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="k">-</span> Extract Class 重構
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="k">-</span> 拆分為 BookListController、BookDetailController、BookSearchController
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="gs">**影響評估**</span>:
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="k">-</span> 影響範圍: 2 分（2-3 個檔案）
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="k">-</span> 業務風險: 3 分（常用功能）
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="k">-</span> 累積速度: 4 分（頻繁新增）
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="k">-</span> 優先級分數: 16（中優先級）
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">---
</span></span><span class="line"><span class="ln">64</span><span class="cl">
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="gu">## 無檢測到的 Code Smell
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="gu"></span><span class="k">-</span> Long Method
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="k">-</span> Dead Code
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="k">-</span> Feature Envy
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="k">-</span> Inappropriate Intimacy
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="k">-</span> Leaky Abstraction
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl">---
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="gu">## 測試覆蓋率
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="gu"></span><span class="k">-</span> **覆蓋率**: 98%
</span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="k">-</span> **未覆蓋檔案**: <span class="sb">`book_controller.dart`</span> line 285-290
</span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="k">-</span> **建議**: 補充測試覆蓋未測試部分
</span></span><span class="line"><span class="ln">78</span><span class="cl">
</span></span><span class="line"><span class="ln">79</span><span class="cl">---
</span></span><span class="line"><span class="ln">80</span><span class="cl">
</span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="gu">## 總體建議
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="gs">**立即處理**</span>: Shotgun Surgery（高優先級）
</span></span><span class="line"><span class="ln">83</span><span class="cl">   <span class="k">-</span> 拆分 PR 為 4 個獨立 Ticket
</span></span><span class="line"><span class="ln">84</span><span class="cl">   <span class="k">-</span> 每個 Ticket 遵循單層修改原則
</span></span><span class="line"><span class="ln">85</span><span class="cl">
</span></span><span class="line"><span class="ln">86</span><span class="cl"><span class="k">2.</span> <span class="gs">**下個版本處理**</span>: Large Class（中優先級）
</span></span><span class="line"><span class="ln">87</span><span class="cl">   <span class="k">-</span> 建立 Refactoring Ticket
</span></span><span class="line"><span class="ln">88</span><span class="cl">   <span class="k">-</span> 執行 Extract Class 重構
</span></span><span class="line"><span class="ln">89</span><span class="cl">
</span></span><span class="line"><span class="ln">90</span><span class="cl"><span class="k">3.</span> <span class="gs">**補充測試**</span>: 測試覆蓋率不足部分
</span></span><span class="line"><span class="ln">91</span><span class="cl">   <span class="k">-</span> 補充 line 285-290 測試
</span></span><span class="line"><span class="ln">92</span><span class="cl">
</span></span><span class="line"><span class="ln">93</span><span class="cl">---
</span></span><span class="line"><span class="ln">94</span><span class="cl">
</span></span><span class="line"><span class="ln">95</span><span class="cl"><span class="gs">**審查結論**</span>: 建議重構後再合併 PR
</span></span><span class="line"><span class="ln">96</span><span class="cl"><span class="gs">**預估修正時間**</span>: 4 小時</span></span></code></pre></div><hr>
<h2 id="第七章自動化檢測整合">第七章：自動化檢測整合</h2>
<h3 id="71-hook-系統整合點">7.1 Hook 系統整合點</h3>
<p><strong>目標</strong>: 將 Code Smell 檢測整合到 Hook 系統，實現自動化品質檢查</p>
<h4 id="711-phase-1-設計階段-hook">7.1.1 Phase 1 設計階段 Hook</h4>
<p><strong>Hook 名稱</strong>: Pre-Design Dependency Check Hook</p>
<p><strong>觸發時機</strong>: Ticket 設計完成時（Phase 1 完成）</p>
<p><strong>檢測項目</strong>:</p>
<ol>
<li>
<p>Ticket 粒度檢查</p>
<ul>
<li>計算預估修改檔案數</li>
<li>判斷層級跨度</li>
<li>評估預估工時</li>
</ul>
</li>
<li>
<p>God Ticket 檢測</p>
<ul>
<li>檔案數 &gt; 10 → 警告並建議拆分</li>
<li>層級跨度 &gt; 2 → 強制拆分</li>
</ul>
</li>
<li>
<p>Ambiguous Responsibility 檢測</p>
<ul>
<li>檢查 Ticket 標題是否包含 [Layer X]</li>
<li>檢查職責描述是否明確</li>
</ul>
</li>
</ol>
<p><strong>Hook 行為</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 檢測通過 → 允許進入 Phase 2</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 檢測失敗 → 提示修正並阻止進入下一階段</span></span></span></code></pre></div><h4 id="712-phase-3-實作階段-hook">7.1.2 Phase 3 實作階段 Hook</h4>
<p><strong>Hook 名稱</strong>: Code Smell Detection Hook</p>
<p><strong>觸發時機</strong>: 程式碼修改後（PostEdit Hook）</p>
<p><strong>檢測項目</strong>:</p>
<ol>
<li>
<p>dart analyze 執行</p>
<ul>
<li>檢測 unused 警告（Dead Code）</li>
<li>檢測語法錯誤</li>
</ul>
</li>
<li>
<p>檔案行數檢查</p>
<ul>
<li>類別行數 &gt; 300 → 警告 Large Class</li>
<li>方法行數 &gt; 50 → 警告 Long Method</li>
</ul>
</li>
<li>
<p>import 語句分析</p>
<ul>
<li>檢測 UI 是否 import Domain Entity（Feature Envy）</li>
<li>檢測依賴方向是否正確（Inappropriate Intimacy）</li>
</ul>
</li>
</ol>
<p><strong>Hook 行為</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 偵測到 Code Smell → 記錄到問題追蹤清單</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 啟動 agents 處理問題（不阻止開發）</span></span></span></code></pre></div><h4 id="713-code-review-階段-hook">7.1.3 Code Review 階段 Hook</h4>
<p><strong>Hook 名稱</strong>: PR Validation Hook</p>
<p><strong>觸發時機</strong>: 提交 PR 時</p>
<p><strong>檢測項目</strong>:</p>
<ol>
<li>
<p>層級隔離檢查</p>
<ul>
<li>執行完整的 A 類 Code Smell 檢測</li>
<li>檢查所有修改檔案的層級定位</li>
</ul>
</li>
<li>
<p>測試覆蓋率檢查</p>
<ul>
<li>執行 <code>flutter test --coverage</code></li>
<li>確保覆蓋率 ≥ 95%</li>
</ul>
</li>
<li>
<p>Code Smell 掃描</p>
<ul>
<li>執行所有 11 種 Code Smell 檢測</li>
<li>生成 Code Smell 檢測報告</li>
</ul>
</li>
</ol>
<p><strong>Hook 行為</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 生成檢測報告</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 高優先級問題 → 阻止合併</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 中/低優先級問題 → 警告但允許合併</span></span></span></code></pre></div><hr>
<h3 id="72-檢測工具推薦">7.2 檢測工具推薦</h3>
<h4 id="721-靜態分析工具">7.2.1 靜態分析工具</h4>
<p><strong>analysis_options.yaml 配置</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .claude/analysis_options.yaml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">analyzer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">errors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="c"># Dead Code 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">unused_element</span><span class="p">:</span><span class="w"> </span><span class="l">error</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">unused_import</span><span class="p">:</span><span class="w"> </span><span class="l">error</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">unused_local_variable</span><span class="p">:</span><span class="w"> </span><span class="l">error</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="c"># 依賴方向檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">implementation_imports</span><span class="p">:</span><span class="w"> </span><span class="l">error</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">exclude</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span>- <span class="l">build/**</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span>- <span class="l">lib/generated/**</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w"></span><span class="nt">linter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="c"># 程式碼品質</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">    </span>- <span class="l">avoid_classes_with_only_static_members</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">    </span>- <span class="l">prefer_single_quotes</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">    </span>- <span class="l">lines_longer_than_80_chars</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="c"># Code Smell 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">    </span>- <span class="l">avoid_returning_null_for_void</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">    </span>- <span class="l">prefer_final_fields</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">    </span>- <span class="l">unnecessary_getters_setters</span></span></span></code></pre></div><h4 id="722-程式碼複雜度工具">7.2.2 程式碼複雜度工具</h4>
<p><strong>安裝和配置</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 安裝 dart_code_metrics</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">dart pub global activate dart_code_metrics
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 執行複雜度分析</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">metrics analyze lib/
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 設定複雜度閾值</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">metrics check-unused-files lib/
</span></span><span class="line"><span class="ln">9</span><span class="cl">metrics check-unused-code lib/</span></span></code></pre></div><p><strong>analysis_options.yaml 整合</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">dart_code_metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">anti-patterns</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span>- <span class="l">long-method</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span>- <span class="l">long-parameter-list</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">  </span><span class="nt">metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">cyclomatic-complexity</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">number-of-parameters</span><span class="p">:</span><span class="w"> </span><span class="m">4</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">maximum-nesting-level</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span>- <span class="l">avoid-unused-parameters</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span>- <span class="l">avoid-nested-conditional-expressions</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span>- <span class="l">prefer-trailing-comma</span></span></span></code></pre></div><h4 id="723-測試覆蓋率工具">7.2.3 測試覆蓋率工具</h4>
<p><strong>執行測試和生成報告</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 執行測試並生成覆蓋率報告</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 生成 HTML 報告</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">genhtml coverage/lcov.info -o coverage/html
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 開啟報告</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">open coverage/html/index.html
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 檢查覆蓋率百分比</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">lcov --summary coverage/lcov.info</span></span></code></pre></div><hr>
<h3 id="73-報告格式設計">7.3 報告格式設計</h3>
<h4 id="731-code-smell-檢測報告-json-格式">7.3.1 Code Smell 檢測報告 JSON 格式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nt">&#34;检测时间&#34;</span><span class="p">:</span> <span class="s2">&#34;2025-10-11T14:30:00Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;检测范围&#34;</span><span class="p">:</span> <span class="s2">&#34;PR #123 - [Layer 2] 實作書籍詳情頁面&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nt">&#34;总体评估&#34;</span><span class="p">:</span> <span class="s2">&#34;需要修正後再合併&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nt">&#34;优先级统计&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nt">&#34;高优先级&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nt">&#34;中优先级&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nt">&#34;低优先级&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nt">&#34;检测结果&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nt">&#34;A类_跨层级&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nt">&#34;类型&#34;</span><span class="p">:</span> <span class="s2">&#34;Shotgun Surgery&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="nt">&#34;严重程度&#34;</span><span class="p">:</span> <span class="s2">&#34;高&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nt">&#34;文件数&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nt">&#34;层级跨度&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="nt">&#34;影响范围&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="nt">&#34;业务风险&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="nt">&#34;累积速度&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="nt">&#34;优先级分数&#34;</span><span class="p">:</span> <span class="mi">25</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="nt">&#34;建议&#34;</span><span class="p">:</span> <span class="s2">&#34;拆分为 4 个独立 Ticket&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="nt">&#34;B类_单层级&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="nt">&#34;类型&#34;</span><span class="p">:</span> <span class="s2">&#34;Large Class&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="nt">&#34;严重程度&#34;</span><span class="p">:</span> <span class="s2">&#34;中&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="nt">&#34;文件&#34;</span><span class="p">:</span> <span class="s2">&#34;lib/presentation/controllers/book_controller.dart&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="nt">&#34;总行数&#34;</span><span class="p">:</span> <span class="mi">320</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nt">&#34;public方法数&#34;</span><span class="p">:</span> <span class="mi">18</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="nt">&#34;优先级分数&#34;</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="nt">&#34;建议&#34;</span><span class="p">:</span> <span class="s2">&#34;Extract Class 重構&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nt">&#34;C类_Ticket粒度&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="nt">&#34;测试覆盖率&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nt">&#34;总覆盖率&#34;</span><span class="p">:</span> <span class="mi">98</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nt">&#34;未覆盖文件&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="nt">&#34;文件&#34;</span><span class="p">:</span> <span class="s2">&#34;book_controller.dart&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="nt">&#34;行范围&#34;</span><span class="p">:</span> <span class="s2">&#34;285-290&#34;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h3 id="74-cicd-整合指引">7.4 CI/CD 整合指引</h3>
<h4 id="741-github-actions-整合">7.4.1 GitHub Actions 整合</h4>
<p><strong>工作流程配置</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .github/workflows/code-smell-check.yml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Code Smell 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">main, develop ]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">code-smell-check</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v3</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">設定 Flutter</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">subosito/flutter-action@v2</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">          </span><span class="nt">flutter-version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.16.0&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">安裝依賴</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">flutter pub get</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Dart Analyze</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">dart analyze</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">檢測 Code Smell</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="sd">          # A 類檢測：檔案路徑分析
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="sd">          python .claude/scripts/check_shotgun_surgery.py
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="sd">          # B 類檢測：程式碼複雜度
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="sd">          metrics analyze lib/
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="sd">          # 測試覆蓋率
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="sd">          flutter test --coverage
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="sd">          lcov --summary coverage/lcov.info</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">生成報告</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="sd">          python .claude/scripts/generate_code_smell_report.py \
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="sd">            --output code-smell-report.json</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">上傳報告</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-artifact@v3</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">code-smell-report</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">code-smell-report.json</span><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">檢查優先級</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="sd">          # 如果有高優先級問題，阻止合併
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="sd">          python .claude/scripts/check_priority.py \
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="sd">            --input code-smell-report.json \
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="sd">            --fail-on-high</span></span></span></code></pre></div><hr>
<h2 id="第八章實踐案例">第八章：實踐案例</h2>
<h3 id="81-案例-1-修正-shotgun-surgery">8.1 案例 1: 修正 Shotgun Surgery</h3>
<p><strong>問題描述</strong>:</p>
<p>Ticket: 新增「書籍評分」功能</p>
<p><strong>初始設計</strong>:</p>
<ul>
<li>需要修改 4 個層級（Layer 1, 2, 3, 5）</li>
<li>修改 6 個檔案</li>
<li>預估工時: 16 小時</li>
</ul>
<p><strong>檢測過程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 列出涉及的檔案
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">1. lib/presentation/widgets/book_detail_widget.dart (Layer 1)
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">2. lib/presentation/controllers/book_detail_controller.dart (Layer 2)
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">3. lib/application/use_cases/rate_book_use_case.dart (Layer 3)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">4. lib/application/use_cases/get_book_rating_use_case.dart (Layer 3)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">5. lib/domain/entities/book.dart (Layer 5)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">6. lib/domain/value_objects/rating_value.dart (Layer 5)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">步驟 2: 統計層級跨度
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 層級: Layer 1, 2, 3, 5（4 層）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 判斷: Shotgun Surgery
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">步驟 3: 計算優先級分數
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 影響範圍: 4 分（6 個檔案，跨 2+ 層）
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 業務風險: 3 分（常用功能）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 累積速度: 2 分（偶爾新增）
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 優先級分數 = (4 × 3) + (3 × 2) + (2 × 1) = 20
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 判斷: 高優先級（立即修正）</span></span></code></pre></div><p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 拆分 Ticket（引用[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 5.4 節）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Ticket 1 [Layer 5]: Rating Value Object 和 Book Entity 擴充
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - 新增 Rating Value Object
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  - Book Entity 新增 rating 屬性
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - 預估工時: 2 小時
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Ticket 2 [Layer 3]: RateBookUseCase 實作
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 實作評分業務邏輯
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 整合 BookRepository
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 預估工時: 3 小時
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Ticket 3 [Layer 3]: GetBookRatingUseCase 實作
</span></span><span class="line"><span class="ln">14</span><span class="cl">  - 實作取得評分邏輯
</span></span><span class="line"><span class="ln">15</span><span class="cl">  - 整合 RatingRepository
</span></span><span class="line"><span class="ln">16</span><span class="cl">  - 預估工時: 2 小時
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">Ticket 4 [Layer 2]: Controller 整合 UseCase
</span></span><span class="line"><span class="ln">19</span><span class="cl">  - BookDetailController 新增評分事件處理
</span></span><span class="line"><span class="ln">20</span><span class="cl">  - Presenter 轉換評分資料
</span></span><span class="line"><span class="ln">21</span><span class="cl">  - 預估工時: 3 小時
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">Ticket 5 [Layer 1]: UI 新增評分元件
</span></span><span class="line"><span class="ln">24</span><span class="cl">  - 新增 RatingWidget
</span></span><span class="line"><span class="ln">25</span><span class="cl">  - 整合 BookDetailWidget
</span></span><span class="line"><span class="ln">26</span><span class="cl">  - 預估工時: 4 小時
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">步驟 2: 執行漸進式實作
</span></span><span class="line"><span class="ln">29</span><span class="cl">  - 每個 Ticket 獨立開發和測試
</span></span><span class="line"><span class="ln">30</span><span class="cl">  - 每個 Ticket 完成 TDD 四階段
</span></span><span class="line"><span class="ln">31</span><span class="cl">  - 按順序合併（Layer 5 → 3 → 2 → 1）</span></span></code></pre></div><p><strong>效果驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 檔案數: 6 個
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 層級跨度: 4 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 預估工時: 16 小時（單一 Ticket）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 風險: 高（一次性修改多層）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">重構後:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- Ticket 數: 5 個
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 每個 Ticket 檔案數: 1-2 個
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 每個 Ticket 層級跨度: 1 層
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 總預估工時: 14 小時（分散到 5 個 Ticket）
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 風險: 低（單層修改，逐步整合）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">改善效果:
</span></span><span class="line"><span class="ln">15</span><span class="cl">正例 符合單層修改原則
</span></span><span class="line"><span class="ln">16</span><span class="cl">正例 風險可控
</span></span><span class="line"><span class="ln">17</span><span class="cl">正例 可並行開發（Layer 5 和 Layer 1 可同時開發）
</span></span><span class="line"><span class="ln">18</span><span class="cl">正例 易於測試和驗證</span></span></code></pre></div><hr>
<h3 id="82-案例-2-修正-feature-envy">8.2 案例 2: 修正 Feature Envy</h3>
<p><strong>問題描述</strong>:</p>
<p>在 Code Review 中發現 UI 層直接存取 Domain Entity 內部欄位。</p>
<p><strong>檢測過程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：發現的問題程式碼
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// lib/presentation/widgets/book_detail_widget.dart
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 反例：UI 不應 import Domain Entity
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kd">final</span> <span class="n">Book</span> <span class="n">book</span><span class="p">;</span> <span class="c1">// 反例：直接依賴 Entity
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">),</span>        <span class="c1">// 存取 1
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">value</span><span class="p">),</span>         <span class="c1">// 存取 2
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">author</span><span class="p">.</span><span class="n">name</span><span class="p">),</span>        <span class="c1">// 存取 3
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">publisher</span><span class="p">),</span>          <span class="c1">// 存取 4
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="n">publicationDate</span><span class="p">.</span><span class="n">toString</span><span class="p">()),</span> <span class="c1">// 存取 5
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">// 檢測結果:
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="o">-</span> <span class="err">直接依賴</span> <span class="n">Domain</span> <span class="n">Entity</span><span class="o">//</span> <span class="o">-</span> <span class="err">存取內部欄位</span> <span class="m">5</span> <span class="err">次（</span><span class="o">&gt;</span> <span class="m">3</span> <span class="err">次標準）</span><span class="o">//</span> <span class="o">-</span> <span class="err">判斷</span><span class="o">:</span> <span class="n">Feature</span> <span class="n">Envy</span></span></span></code></pre></div><p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 步驟 1: 建立 ViewModel（Layer 2）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailViewModel</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">isbn</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">author</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">publisher</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">publicationDate</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="n">BookDetailViewModel</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">title</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">isbn</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">author</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">publisher</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="kd">required</span> <span class="k">this</span><span class="p">.</span><span class="n">publicationDate</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">// 步驟 2: 建立 Presenter 轉換（Layer 2）
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailPresenter</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="kd">static</span> <span class="n">BookDetailViewModel</span> <span class="n">toViewModel</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">BookDetailViewModel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">      <span class="nl">title:</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">      <span class="nl">isbn:</span> <span class="n">book</span><span class="p">.</span><span class="n">isbn</span><span class="p">.</span><span class="n">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">      <span class="nl">author:</span> <span class="n">book</span><span class="p">.</span><span class="n">author</span><span class="p">.</span><span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">      <span class="nl">publisher:</span> <span class="n">book</span><span class="p">.</span><span class="n">publisher</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">      <span class="nl">publicationDate:</span> <span class="n">book</span><span class="p">.</span><span class="n">publicationDate</span><span class="p">.</span><span class="n">toString</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1">// 步驟 3: 重構 UI（Layer 1）
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="kd">final</span> <span class="n">BookDetailViewModel</span> <span class="n">viewModel</span><span class="p">;</span> <span class="c1">// 正例：依賴 ViewModel
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">return</span> <span class="n">Column</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">      <span class="nl">children:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">title</span><span class="p">),</span>           <span class="c1">// 正例：使用轉換後的資料
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span>        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">isbn</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">author</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">publisher</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="n">Text</span><span class="p">(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">publicationDate</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1">// 步驟 4: 更新 Controller（Layer 2）
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">  <span class="kd">final</span> <span class="n">GetBookDetailUseCase</span> <span class="n">getBookDetailUseCase</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="n">BookDetailViewModel</span><span class="o">?</span> <span class="n">viewModel</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">getBookDetailUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">viewModel</span> <span class="o">=</span> <span class="n">BookDetailPresenter</span><span class="p">.</span><span class="n">toViewModel</span><span class="p">(</span><span class="n">book</span><span class="p">);</span> <span class="c1">// 正例：轉換
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="c1"></span>    <span class="n">notifyListeners</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>效果驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- UI 直接依賴 Domain Entity
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 存取內部欄位 5 次
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 緊耦合，Domain 修改影響 UI
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">重構後:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- UI 依賴 ViewModel
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- Presenter 集中處理轉換
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- Domain 修改不影響 UI
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 測試更容易（Mock ViewModel）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">測試改善:
</span></span><span class="line"><span class="ln">13</span><span class="cl">// 重構前：需要 Mock 整個 Domain Entity
</span></span><span class="line"><span class="ln">14</span><span class="cl">test(&#39;should display book details&#39;, () {
</span></span><span class="line"><span class="ln">15</span><span class="cl">  // 需要建立完整的 Book Entity（複雜）
</span></span><span class="line"><span class="ln">16</span><span class="cl">  final book = Book(...); // 需要所有 Value Objects
</span></span><span class="line"><span class="ln">17</span><span class="cl">  ...
</span></span><span class="line"><span class="ln">18</span><span class="cl">});
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">// 重構後：只需 Mock ViewModel
</span></span><span class="line"><span class="ln">21</span><span class="cl">test(&#39;should display book details&#39;, () {
</span></span><span class="line"><span class="ln">22</span><span class="cl">  final viewModel = BookDetailViewModel(
</span></span><span class="line"><span class="ln">23</span><span class="cl">    title: &#39;Test Book&#39;,
</span></span><span class="line"><span class="ln">24</span><span class="cl">    isbn: &#39;1234567890123&#39;,
</span></span><span class="line"><span class="ln">25</span><span class="cl">    ...
</span></span><span class="line"><span class="ln">26</span><span class="cl">  );
</span></span><span class="line"><span class="ln">27</span><span class="cl">  // 測試更簡單
</span></span><span class="line"><span class="ln">28</span><span class="cl">});</span></span></code></pre></div><hr>
<h3 id="83-案例-3-拆分-god-ticket">8.3 案例 3: 拆分 God Ticket</h3>
<p><strong>問題描述</strong>:</p>
<p>Ticket: 實作完整的「我的書架」功能</p>
<p><strong>初始 Ticket 設計</strong>:</p>
<ul>
<li>修改 15 個檔案</li>
<li>跨 4 個層級</li>
<li>預估工時: 32 小時</li>
<li>包含：列表顯示、新增書籍、刪除書籍、搜尋、排序</li>
</ul>
<p><strong>檢測過程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檔案清單分析
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Layer 1 (UI):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">1. lib/presentation/widgets/bookshelf_screen.dart
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">2. lib/presentation/widgets/book_list_widget.dart
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">3. lib/presentation/widgets/book_item_widget.dart
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">4. lib/presentation/widgets/add_book_dialog.dart
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Layer 2 (Behavior):
</span></span><span class="line"><span class="ln">10</span><span class="cl">5. lib/presentation/controllers/bookshelf_controller.dart
</span></span><span class="line"><span class="ln">11</span><span class="cl">6. lib/presentation/presenters/book_presenter.dart
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Layer 3 (UseCase):
</span></span><span class="line"><span class="ln">14</span><span class="cl">7. lib/application/use_cases/get_bookshelf_books_use_case.dart
</span></span><span class="line"><span class="ln">15</span><span class="cl">8. lib/application/use_cases/add_book_to_shelf_use_case.dart
</span></span><span class="line"><span class="ln">16</span><span class="cl">9. lib/application/use_cases/remove_book_from_shelf_use_case.dart
</span></span><span class="line"><span class="ln">17</span><span class="cl">10. lib/application/use_cases/search_bookshelf_use_case.dart
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">Layer 5 (Domain + Infrastructure):
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">11. lib/domain/entities/bookshelf.dart
</span></span><span class="line"><span class="ln">22</span><span class="cl">12. lib/domain/value_objects/shelf_name.dart
</span></span><span class="line"><span class="ln">23</span><span class="cl">13. lib/infrastructure/repositories/bookshelf_repository_impl.dart
</span></span><span class="line"><span class="ln">24</span><span class="cl">14. lib/infrastructure/database/bookshelf_table.dart
</span></span><span class="line"><span class="ln">25</span><span class="cl">15. lib/infrastructure/database/bookshelf_book_table.dart
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">步驟 2: God Ticket 判斷
</span></span><span class="line"><span class="ln">28</span><span class="cl">- 檔案數: 15 個（&gt; 10 個標準）
</span></span><span class="line"><span class="ln">29</span><span class="cl">- 層級跨度: 4 層
</span></span><span class="line"><span class="ln">30</span><span class="cl">- 預估工時: 32 小時（&gt; 16 小時標準）
</span></span><span class="line"><span class="ln">31</span><span class="cl">- 判斷: God Ticket
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">步驟 3: 計算優先級分數
</span></span><span class="line"><span class="ln">34</span><span class="cl">- 影響範圍: 5 分（&gt; 10 個檔案，跨 3+ 層）
</span></span><span class="line"><span class="ln">35</span><span class="cl">- 業務風險: 4 分（重要功能）
</span></span><span class="line"><span class="ln">36</span><span class="cl">- 累積速度: 3 分（定期新增）
</span></span><span class="line"><span class="ln">37</span><span class="cl">- 優先級分數 = (5 × 3) + (4 × 2) + (3 × 1) = 26
</span></span><span class="line"><span class="ln">38</span><span class="cl">- 判斷: 高優先級（強制拆分）</span></span></code></pre></div><p><strong>拆分策略</strong>（引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.4 節）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">策略 1: 按層級拆分（從內而外）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Ticket 1 [Layer 5]: Bookshelf Domain 設計
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - Bookshelf Entity
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  - ShelfName Value Object
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - 檔案數: 2 個，預估: 4 小時
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Ticket 2 [Layer 5]: Bookshelf Repository 實作
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - BookshelfRepositoryImpl
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 資料庫表格設計
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 檔案數: 3 個，預估: 6 小時
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Ticket 3 [Layer 3]: 書架查詢 UseCase
</span></span><span class="line"><span class="ln">14</span><span class="cl">  - GetBookshelfBooksUseCase
</span></span><span class="line"><span class="ln">15</span><span class="cl">  - SearchBookshelfUseCase
</span></span><span class="line"><span class="ln">16</span><span class="cl">  - 檔案數: 2 個，預估: 4 小時
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">Ticket 4 [Layer 3]: 書架操作 UseCase
</span></span><span class="line"><span class="ln">19</span><span class="cl">  - AddBookToShelfUseCase
</span></span><span class="line"><span class="ln">20</span><span class="cl">  - RemoveBookFromShelfUseCase
</span></span><span class="line"><span class="ln">21</span><span class="cl">  - 檔案數: 2 個，預估: 4 小時
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">Ticket 5 [Layer 2]: Controller 和 Presenter
</span></span><span class="line"><span class="ln">24</span><span class="cl">  - BookshelfController
</span></span><span class="line"><span class="ln">25</span><span class="cl">  - BookPresenter
</span></span><span class="line"><span class="ln">26</span><span class="cl">  - 檔案數: 2 個，預估: 5 小時
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">Ticket 6 [Layer 1]: 書架列表 UI
</span></span><span class="line"><span class="ln">29</span><span class="cl">  - BookshelfScreen
</span></span><span class="line"><span class="ln">30</span><span class="cl">  - BookListWidget
</span></span><span class="line"><span class="ln">31</span><span class="cl">  - BookItemWidget
</span></span><span class="line"><span class="ln">32</span><span class="cl">  - 檔案數: 3 個，預估: 6 小時
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">Ticket 7 [Layer 1]: 新增書籍 UI
</span></span><span class="line"><span class="ln">35</span><span class="cl">  - AddBookDialog
</span></span><span class="line"><span class="ln">36</span><span class="cl">  - 整合 Controller
</span></span><span class="line"><span class="ln">37</span><span class="cl">  - 檔案數: 1 個，預估: 3 小時
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">策略 2: 按功能拆分（MVP 優先）
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">Ticket 1: 書架基礎功能（MVP）
</span></span><span class="line"><span class="ln">42</span><span class="cl">  - 只實作「顯示書架列表」功能
</span></span><span class="line"><span class="ln">43</span><span class="cl">  - Layer 5 + 3 + 2 + 1（最小實作）
</span></span><span class="line"><span class="ln">44</span><span class="cl">  - 檔案數: 7 個，預估: 12 小時
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">Ticket 2: 新增書籍功能
</span></span><span class="line"><span class="ln">47</span><span class="cl">  - Layer 3 + 2 + 1
</span></span><span class="line"><span class="ln">48</span><span class="cl">  - 檔案數: 4 個，預估: 8 小時
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">Ticket 3: 刪除書籍功能
</span></span><span class="line"><span class="ln">51</span><span class="cl">  - Layer 3 + 2 + 1
</span></span><span class="line"><span class="ln">52</span><span class="cl">  - 檔案數: 3 個，預估: 6 小時
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">Ticket 4: 搜尋和排序功能
</span></span><span class="line"><span class="ln">55</span><span class="cl">  - Layer 3 + 2 + 1
</span></span><span class="line"><span class="ln">56</span><span class="cl">  - 檔案數: 4 個，預估: 6 小時
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">選擇策略 1（按層級拆分）的理由:
</span></span><span class="line"><span class="ln">59</span><span class="cl">正例 符合從內而外實作順序（[層級隔離派工方法論](/record/layered-ticket-methodology/) 第 4.1 節）
</span></span><span class="line"><span class="ln">60</span><span class="cl">正例 每個 Ticket 單層修改
</span></span><span class="line"><span class="ln">61</span><span class="cl">正例 可並行開發（Layer 5 和 Layer 1 可同時開發）
</span></span><span class="line"><span class="ln">62</span><span class="cl">正例 依賴關係清晰</span></span></code></pre></div><p><strong>效果驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前（God Ticket）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 檔案數: 15 個
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 層級跨度: 4 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 預估工時: 32 小時（單一巨大 Ticket）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 風險: 極高
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- 測試困難度: 極高
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 無法並行開發
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">重構後（7 個 Ticket）:
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 每個 Ticket 檔案數: 1-3 個
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 每個 Ticket 層級跨度: 1 層
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 總預估工時: 32 小時（分散到 7 個 Ticket）
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 風險: 低（單層修改）
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 測試困難度: 低（每個 Ticket 獨立測試）
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 可並行開發（Ticket 1-2, Ticket 6-7 可並行）
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">實際改善效果:
</span></span><span class="line"><span class="ln">18</span><span class="cl">正例 開發時間縮短 20%（並行開發）
</span></span><span class="line"><span class="ln">19</span><span class="cl">正例 Bug 數量減少 60%（單層修改，易於測試）
</span></span><span class="line"><span class="ln">20</span><span class="cl">正例 Code Review 時間縮短 40%（每個 PR 更小）
</span></span><span class="line"><span class="ln">21</span><span class="cl">正例 團隊協作效率提升（可分配給不同開發人員）</span></span></code></pre></div><hr>
<h3 id="84-案例-4-重構-large-class">8.4 案例 4: 重構 Large Class</h3>
<p><strong>問題描述</strong>:</p>
<p>在 Phase 4 重構階段發現 <code>BookController</code> 類別過大。</p>
<p><strong>檢測過程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 檢測類別行數</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">wc -l lib/presentation/controllers/book_controller.dart
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 輸出: 450 lib/presentation/controllers/book_controller.dart</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 統計 public 方法數量</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">grep -c <span class="s2">&#34;void\|Future&#34;</span> lib/presentation/controllers/book_controller.dart
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 輸出: 25</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 分析結果:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># - 總行數: 450 行（&gt; 300 行標準）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># - public 方法: 25 個（&gt; 15 個標準）</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># - 判斷: Large Class</span></span></span></code></pre></div><p><strong>方法分組分析</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 分析 BookController 的方法
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="c1">// 群組 A：書架列表相關（8 個方法）
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">bookList</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kt">void</span> <span class="n">refreshBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kt">void</span> <span class="n">sortBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">sortBy</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kt">void</span> <span class="n">filterBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">filter</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="kt">void</span> <span class="n">loadMoreBooks</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kt">void</span> <span class="n">clearBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="kt">void</span> <span class="n">updateBookListView</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kt">void</span> <span class="n">onBookListError</span><span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="c1">// 群組 B：書籍詳情相關（7 個方法）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span>  <span class="n">BookViewModel</span><span class="o">?</span> <span class="n">bookDetail</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="kt">void</span> <span class="n">updateBookDetail</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kt">void</span> <span class="n">shareBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="kt">void</span> <span class="n">favoriteBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="kt">void</span> <span class="n">unfavoriteBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="kt">void</span> <span class="n">onBookDetailError</span><span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="c1">// 群組 C：搜尋相關（6 個方法）
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span>  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">searchResults</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="kt">void</span> <span class="n">searchBooks</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="kt">void</span> <span class="n">clearSearchResults</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kt">void</span> <span class="n">updateSearchQuery</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="kt">void</span> <span class="n">loadSearchHistory</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="kt">void</span> <span class="n">saveSearchHistory</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteSearchHistory</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">  <span class="c1">// 群組 D：評分相關（4 個方法）
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">rateBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">,</span> <span class="kt">int</span> <span class="n">rating</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookRating</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="kt">void</span> <span class="n">updateRating</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteRating</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">// 分析結果:
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1">// - 4 個方法群組
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1">// - 4 種變更原因（列表、詳情、搜尋、評分）
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="o">-</span> <span class="err">判斷</span><span class="o">:</span> <span class="n">Divergent</span> <span class="n">Change</span> <span class="o">+</span> <span class="n">Large</span> <span class="n">Class</span></span></span></code></pre></div><p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 步驟 1: Extract Class 重構
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Controller 1：只負責書架列表
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">bookList</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kt">void</span> <span class="n">refreshBookList</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="kt">void</span> <span class="n">sortBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">sortBy</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kt">void</span> <span class="n">filterBookList</span><span class="p">(</span><span class="kt">String</span> <span class="n">filter</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="kt">void</span> <span class="n">loadMoreBooks</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// Controller 2：只負責書籍詳情
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="n">BookViewModel</span><span class="o">?</span> <span class="n">bookDetail</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookDetail</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kt">void</span> <span class="n">updateBookDetail</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="kt">void</span> <span class="n">shareBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="kt">void</span> <span class="n">toggleFavorite</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">// Controller 3：只負責搜尋
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookSearchController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="n">List</span><span class="o">&lt;</span><span class="n">BookViewModel</span><span class="o">&gt;</span> <span class="n">searchResults</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="kt">void</span> <span class="n">searchBooks</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="kt">void</span> <span class="n">clearSearchResults</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="kt">void</span> <span class="n">updateSearchQuery</span><span class="p">(</span><span class="kt">String</span> <span class="n">query</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="kt">void</span> <span class="n">manageSearchHistory</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1">// Controller 4：只負責評分
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookRatingController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="kt">void</span> <span class="n">rateBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">,</span> <span class="kt">int</span> <span class="n">rating</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="kt">void</span> <span class="n">loadBookRating</span><span class="p">(</span><span class="kt">String</span> <span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">  <span class="kt">void</span> <span class="n">updateRating</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">  <span class="kt">void</span> <span class="n">deleteRating</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">// 步驟 2: 更新 Widget 依賴
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1">// Before: 單一巨大 Controller
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookshelfScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">  <span class="kd">final</span> <span class="n">BookController</span> <span class="n">controller</span><span class="p">;</span> <span class="c1">// 依賴巨大 Controller
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1">// After: 使用對應的小 Controller
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookListScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">  <span class="kd">final</span> <span class="n">BookListController</span> <span class="n">controller</span><span class="p">;</span> <span class="c1">// 只依賴需要的 Controller
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">  <span class="kd">final</span> <span class="n">BookDetailController</span> <span class="n">controller</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="kd">class</span> <span class="nc">BookSearchScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">  <span class="kd">final</span> <span class="n">BookSearchController</span> <span class="n">controller</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>效果驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前（Large Class）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- BookController: 450 行，25 個方法
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 職責: 列表 + 詳情 + 搜尋 + 評分（4 種）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 變更原因: 4 個
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 測試困難度: 高（需要 Mock 所有依賴）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- 單一測試檔案: 800+ 行
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">重構後（4 個小 Controller）:
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- BookListController: 120 行，5 個方法
</span></span><span class="line"><span class="ln">10</span><span class="cl">- BookDetailController: 110 行，5 個方法
</span></span><span class="line"><span class="ln">11</span><span class="cl">- BookSearchController: 100 行，4 個方法
</span></span><span class="line"><span class="ln">12</span><span class="cl">- BookRatingController: 80 行，4 個方法
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 每個 Controller 單一職責
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 每個 Controller 單一變更原因
</span></span><span class="line"><span class="ln">15</span><span class="cl">- 測試困難度: 低（每個 Controller 獨立測試）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 測試檔案: 每個 150-200 行
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">測試改善:
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 測試執行時間: 從 8 秒 → 2 秒（每個 Controller 獨立測試）
</span></span><span class="line"><span class="ln">20</span><span class="cl">- Mock 複雜度: 降低 70%
</span></span><span class="line"><span class="ln">21</span><span class="cl">- 測試可讀性: 提升（每個測試檔案更專注）
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">維護改善:
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 修改列表功能: 只需要修改 BookListController
</span></span><span class="line"><span class="ln">25</span><span class="cl">- Bug 定位時間: 縮短 50%（範圍更明確）
</span></span><span class="line"><span class="ln">26</span><span class="cl">- Code Review 時間: 縮短 40%（每個類別更小）</span></span></code></pre></div><hr>
<h3 id="85-案例-5-消除-inappropriate-intimacy">8.5 案例 5: 消除 Inappropriate Intimacy</h3>
<p><strong>問題描述</strong>:</p>
<p>在 Code Review 中發現 Domain 層依賴 UseCase 層。</p>
<p><strong>檢測過程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 反例：發現的問題程式碼
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// lib/domain/entities/book.dart
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/application/use_cases/add_book_to_favorite_use_case.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 反例：Domain 不應 import UseCase
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="kd">final</span> <span class="n">Title</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kd">final</span> <span class="n">AddBookToFavoriteUseCase</span> <span class="n">favoriteUseCase</span><span class="p">;</span> <span class="c1">// 反例：Domain 依賴 UseCase
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kt">void</span> <span class="n">addToFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">favoriteUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">id</span><span class="p">);</span> <span class="c1">// 反例：呼叫 UseCase
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="kt">void</span> <span class="n">removeFromFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1">// 類似的錯誤模式
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">// 檢測結果:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="o">-</span> <span class="n">Domain</span> <span class="err">依賴外層（</span><span class="n">UseCase</span><span class="err">）</span><span class="o">//</span> <span class="o">-</span> <span class="err">違反依賴方向規則</span><span class="o">//</span> <span class="o">-</span> <span class="n">Domain</span> <span class="err">失去獨立性</span><span class="o">//</span> <span class="o">-</span> <span class="err">判斷</span><span class="o">:</span> <span class="n">Inappropriate</span> <span class="n">Intimacy</span></span></span></code></pre></div><p><strong>重構步驟</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 步驟 1: 重新設計 Domain（移除外層依賴）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// 正例： Domain 設計
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Book</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="n">Title</span> <span class="n">title</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kt">bool</span> <span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="c1">// 正例：只記錄狀態
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// 正例：Domain 只處理業務邏輯
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">markAsFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">isFavorited</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="k">throw</span> <span class="n">AlreadyFavoritedException</span><span class="p">(</span><span class="s1">&#39;書籍已在我的最愛&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="kt">void</span> <span class="n">unmarkFromFavorite</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isFavorited</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">      <span class="k">throw</span> <span class="n">NotFavoritedException</span><span class="p">(</span><span class="s1">&#39;書籍不在我的最愛&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">isFavorited</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="c1">// 正例：Domain 方法完全獨立，無外層依賴
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1">// 步驟 2: UseCase 協調業務流程
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1">// 正例：UseCase 負責協調
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">AddBookToFavoriteUseCase</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="kd">final</span> <span class="n">IBookRepository</span> <span class="n">bookRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="kd">final</span> <span class="n">IFavoriteRepository</span> <span class="n">favoriteRepository</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="n">Future</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">execute</span><span class="p">(</span><span class="kt">String</span> <span class="n">bookId</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1">// 1. 取得書籍
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"></span>    <span class="kd">final</span> <span class="n">book</span> <span class="o">=</span> <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">findById</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1">// 2. 執行 Domain 方法
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"></span>    <span class="n">book</span><span class="p">.</span><span class="n">markAsFavorite</span><span class="p">();</span> <span class="c1">// 正例：UseCase 呼叫 Domain
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1">// 3. 儲存狀態
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span>    <span class="kd">await</span> <span class="n">bookRepository</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="kd">await</span> <span class="n">favoriteRepository</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="c1">// 4. 發送事件
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"></span>    <span class="n">eventBus</span><span class="p">.</span><span class="n">fire</span><span class="p">(</span><span class="n">BookAddedToFavoriteEvent</span><span class="p">(</span><span class="n">bookId</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1">// 步驟 3: Controller 觸發 UseCase
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDetailController</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">  <span class="kd">final</span> <span class="n">AddBookToFavoriteUseCase</span> <span class="n">addToFavoriteUseCase</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">  <span class="kt">void</span> <span class="n">onFavoriteButtonPressed</span><span class="p">(</span><span class="kt">String</span> <span class="n">bookId</span><span class="p">)</span> <span class="kd">async</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">      <span class="kd">await</span> <span class="n">addToFavoriteUseCase</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="n">bookId</span><span class="p">);</span> <span class="c1">// 正例：呼叫方向
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="c1"></span>      <span class="c1">// 更新 UI 狀態
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"></span>    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">      <span class="c1">// 錯誤處理
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>依賴方向驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前（錯誤的依賴方向）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Layer 5 (Domain) → Layer 3 (UseCase)
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Book Entity 依賴 AddBookToFavoriteUseCase
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 違反依賴倒置原則
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- Domain 失去獨立性和可重用性
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">重構後（正確的依賴方向）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Layer 2 → Layer 3 → Layer 5
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- Controller → UseCase → Domain
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 符合依賴倒置原則
</span></span><span class="line"><span class="ln">11</span><span class="cl">- Domain 獨立且純淨
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">依賴關係圖:
</span></span><span class="line"><span class="ln">14</span><span class="cl">重構前:
</span></span><span class="line"><span class="ln">15</span><span class="cl">Book (Layer 5) ──┐
</span></span><span class="line"><span class="ln">16</span><span class="cl">                 ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">AddBookToFavoriteUseCase (Layer 3) 內層依賴外層
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">重構後:
</span></span><span class="line"><span class="ln">20</span><span class="cl">BookDetailController (Layer 2)
</span></span><span class="line"><span class="ln">21</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">22</span><span class="cl">AddBookToFavoriteUseCase (Layer 3)
</span></span><span class="line"><span class="ln">23</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">24</span><span class="cl">Book (Layer 5) 正確的依賴方向</span></span></code></pre></div><p><strong>效果驗證</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">重構前:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- Domain 依賴 UseCase
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- Domain 無法獨立測試
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- Domain 無法重用
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 違反 Clean Architecture
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">重構後:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- Domain 完全獨立
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- Domain 可獨立測試
</span></span><span class="line"><span class="ln">10</span><span class="cl">- Domain 可重用（可在不同 UseCase 中使用）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 符合 Clean Architecture
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">測試改善:
</span></span><span class="line"><span class="ln">14</span><span class="cl">// 重構前：Domain 測試需要 Mock UseCase
</span></span><span class="line"><span class="ln">15</span><span class="cl">test(&#39;should add book to favorite&#39;, () {
</span></span><span class="line"><span class="ln">16</span><span class="cl">  final mockUseCase = MockAddBookToFavoriteUseCase();
</span></span><span class="line"><span class="ln">17</span><span class="cl">  final book = Book(favoriteUseCase: mockUseCase); // 需要注入
</span></span><span class="line"><span class="ln">18</span><span class="cl">  book.addToFavorite();
</span></span><span class="line"><span class="ln">19</span><span class="cl">  verify(mockUseCase.execute(book.id)).called(1);
</span></span><span class="line"><span class="ln">20</span><span class="cl">});
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">// 重構後：Domain 測試完全獨立
</span></span><span class="line"><span class="ln">23</span><span class="cl">test(&#39;should mark book as favorite&#39;, () {
</span></span><span class="line"><span class="ln">24</span><span class="cl">  final book = Book(...); // 無需任何 Mock
</span></span><span class="line"><span class="ln">25</span><span class="cl">  book.markAsFavorite();
</span></span><span class="line"><span class="ln">26</span><span class="cl">  expect(book.isFavorited, true); // 純粹的單元測試
</span></span><span class="line"><span class="ln">27</span><span class="cl">});
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">架構改善:
</span></span><span class="line"><span class="ln">30</span><span class="cl">正例 Domain 層純淨（無外層依賴）
</span></span><span class="line"><span class="ln">31</span><span class="cl">正例 依賴方向正確（外層→內層）
</span></span><span class="line"><span class="ln">32</span><span class="cl">正例 可在不同 UseCase 中重用 Domain 邏輯
</span></span><span class="line"><span class="ln">33</span><span class="cl">正例 易於測試和維護</span></span></code></pre></div><hr>
<h2 id="第九章常見問題-faq">第九章：常見問題 FAQ</h2>
<h3 id="91-理論問題">9.1 理論問題</h3>
<h4 id="q1-code-smell-和-bug-有什麼區別">Q1: Code Smell 和 Bug 有什麼區別？</h4>
<p><strong>答</strong>:</p>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>Bug（程式錯誤）</th>
          <th>Code Smell（程式異味）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>影響</strong></td>
          <td>導致功能失敗或程式崩潰</td>
          <td>程式功能正常運作</td>
      </tr>
      <tr>
          <td><strong>檢測方式</strong></td>
          <td>透過測試失敗發現</td>
          <td>透過程式碼檢視或靜態分析發現</td>
      </tr>
      <tr>
          <td><strong>修正優先級</strong></td>
          <td>必須立即修正</td>
          <td>可規劃重構時機</td>
      </tr>
      <tr>
          <td><strong>修正方法</strong></td>
          <td>修正邏輯錯誤</td>
          <td>透過重構改善設計</td>
      </tr>
      <tr>
          <td><strong>長期影響</strong></td>
          <td>直接影響用戶體驗</td>
          <td>影響程式碼可維護性和擴展性</td>
      </tr>
  </tbody>
</table>
<p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Bug（程式錯誤）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">calculateTotal</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Item</span><span class="o">&gt;</span> <span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kt">double</span> <span class="n">total</span> <span class="o">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="n">item</span> <span class="k">in</span> <span class="n">items</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">total</span> <span class="o">+=</span> <span class="n">item</span><span class="p">.</span><span class="n">price</span><span class="p">;</span> <span class="c1">// 反例：Bug: 沒有考慮數量
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">return</span> <span class="n">total</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// Code Smell（Long Method）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">processOrder</span><span class="p">(</span><span class="n">Order</span> <span class="n">order</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="c1">// 80 行的方法
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  <span class="c1">// 功能正常，但難以理解和維護
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>  <span class="c1">// 這是 Code Smell，不是 Bug
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><hr>
<h4 id="q2-為什麼要從-ticket-粒度檢測-code-smell">Q2: 為什麼要從 Ticket 粒度檢測 Code Smell？</h4>
<p><strong>答</strong>:</p>
<p><strong>Ticket 粒度檢測的優勢</strong>:</p>
<ol>
<li>
<p><strong>及早發現問題</strong>（設計階段 vs 實作階段）</p>
<ul>
<li>設計階段發現 → 修正成本低（只需調整設計）</li>
<li>實作階段發現 → 修正成本中（需要重寫程式碼）</li>
<li>維護階段發現 → 修正成本高（需要大規模重構）</li>
</ul>
</li>
<li>
<p>預防勝於治療</p>
<ul>
<li>Ticket 設計時檢測到 God Ticket → 拆分為多個 Ticket</li>
<li>避免實作後才發現範圍過大</li>
</ul>
</li>
<li>
<p>與 TDD 四階段整合</p>
<ul>
<li>Phase 1 設計：檢測 Ticket 粒度（C1, C3, A1）</li>
<li>Phase 2 測試：檢測測試範圍（C2）</li>
<li>Phase 3 實作：檢測程式碼品質（A2, A3, A4, B2, B3）</li>
<li>Phase 4 重構：識別重構需求（B1, B2, B3, B4）</li>
</ul>
</li>
</ol>
<p><strong>成本對比</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket 粒度檢測（Phase 1）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 修正成本: 1 小時（調整設計）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 影響範圍: 無（尚未實作）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 風險: 低
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">實作完成後檢測（Phase 3-4）:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 修正成本: 8 小時（重寫程式碼）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 影響範圍: 中（需要修改多個檔案）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 風險: 中（需要回歸測試）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">上線後檢測（維護階段）:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 修正成本: 24 小時（大規模重構）
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 影響範圍: 大（可能影響多個模組）
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 風險: 高（可能引入新 Bug）</span></span></code></pre></div><hr>
<h4 id="q3-所有-code-smell-都必須立即修正嗎">Q3: 所有 Code Smell 都必須立即修正嗎？</h4>
<p><strong>答</strong>: 不是。應該根據<strong>優先級評估公式</strong>決定修正時機。</p>
<p><strong>優先級分類</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">優先級分數 = (影響範圍 × 3) + (業務風險 × 2) + (累積速度 × 1)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">高優先級（分數 &gt; 20）→ 立即修正
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- Inappropriate Intimacy（依賴方向錯誤）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- Shotgun Surgery（影響範圍大）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">- God Ticket（風險高）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">中優先級（分數 10-20）→ 排入下個版本
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- Feature Envy（耦合度高但不影響功能）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- Divergent Change（技術債務累積）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- Large Class（複雜度高）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">低優先級（分數 &lt; 10）→ 重構階段處理
</span></span><span class="line"><span class="ln">14</span><span class="cl">- Long Method（可讀性問題）
</span></span><span class="line"><span class="ln">15</span><span class="cl">- Dead Code（無功能影響）
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Incomplete Ticket（補測試即可）</span></span></code></pre></div><p><strong>決策建議</strong>:</p>
<ul>
<li>高優先級：<strong>立即修正</strong>（影響架構或核心功能）</li>
<li>中優先級：<strong>規劃重構</strong>（技術債務累積但不緊急）</li>
<li>低優先級：<strong>opportunistic 重構</strong>（修改相關程式碼時順便重構）</li>
</ul>
<hr>
<h4 id="q4-code-smell-檢測會不會過度限制創意">Q4: Code Smell 檢測會不會過度限制創意？</h4>
<p><strong>答</strong>: 不會。Code Smell 檢查清單是<strong>品質標準</strong>，不是<strong>創意限制</strong>。</p>
<p><strong>澄清誤解</strong>:</p>
<table>
  <thead>
      <tr>
          <th>誤解</th>
          <th>實際情況</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「檢查清單限制了我的設計」</td>
          <td>檢查清單是<strong>最低標準</strong>，不限制創新設計</td>
      </tr>
      <tr>
          <td>「量化指標太死板」</td>
          <td>量化指標是<strong>參考標準</strong>，特殊情況可調整</td>
      </tr>
      <tr>
          <td>「所有 Code Smell 都要消除」</td>
          <td>根據<strong>優先級評估</strong>決定修正時機</td>
      </tr>
      <tr>
          <td>「重構會降低開發速度」</td>
          <td>及早重構<strong>降低長期維護成本</strong></td>
      </tr>
  </tbody>
</table>
<p><strong>正確理解</strong>:</p>
<ol>
<li>
<p>量化指標是參考，不是絕對</p>
<ul>
<li>方法行數 &gt; 50 行 → 「建議」拆分，不是「強制」</li>
<li>特殊情況（如配置檔載入）可以例外</li>
</ul>
</li>
<li>
<p>檢查清單是輔助，不是束縛</p>
<ul>
<li>幫助發現潛在問題</li>
<li>提供重構方向</li>
<li>不限制創新設計</li>
</ul>
</li>
<li>
<p>重構是投資，不是成本</p>
<ul>
<li>短期投入時間重構</li>
<li>長期降低維護成本</li>
<li>提升團隊生產力</li>
</ul>
</li>
</ol>
<hr>
<h4 id="q5-本檢查清單和層級隔離派工方法論-的關係是什麼">Q5: 本檢查清單和<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的關係是什麼？</h4>
<p><strong>答</strong>: <strong>互補關係</strong> - <a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 定義「應該怎麼做」，本檢查清單定義「不應該怎麼做」。</p>
<p><strong>關係說明</strong>:</p>
<table>
  <thead>
      <tr>
          <th>方法論</th>
          <th><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a></th>
          <th>本 Code Smell 檢查清單</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>角色</strong></td>
          <td>正面原則（應該怎麼做）</td>
          <td>負面模式（不應該怎麼做）</td>
      </tr>
      <tr>
          <td><strong>內容</strong></td>
          <td>五層架構定義、單層修改原則、Ticket 粒度標準</td>
          <td>Code Smell 檢測、違規模式識別、重構策略</td>
      </tr>
      <tr>
          <td><strong>使用時機</strong></td>
          <td>設計和規劃階段</td>
          <td>檢測和驗證階段</td>
      </tr>
      <tr>
          <td><strong>產出</strong></td>
          <td>架構設計、Ticket 規劃</td>
          <td>品質檢測報告、重構建議</td>
      </tr>
  </tbody>
</table>
<p><strong>引用關係</strong>:</p>
<ul>
<li>本檢查清單<strong>引用</strong><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 的定義，不重複定義層級架構</li>
<li>例如：Shotgun Surgery 的判斷標準引用<a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 第 5.2 節 Ticket 粒度標準</li>
</ul>
<p><strong>協作流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 1 設計:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">1. 使用[層級隔離派工方法論](/record/layered-ticket-methodology/) 設計 Ticket（定義層級、規劃粒度）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">2. 使用本檢查清單檢測 Ticket（檢查是否有 God Ticket、Ambiguous Responsibility）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Phase 3 實作:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">1. 使用[層級隔離派工方法論](/record/layered-ticket-methodology/) 指導實作（遵循單層修改原則）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">2. 使用本檢查清單檢測實作（檢查是否產生 Code Smell）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Phase 4 重構:
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">1. 使用本檢查清單識別 Code Smell
</span></span><span class="line"><span class="ln">14</span><span class="cl">2. 使用本檢查清單的重構策略修正
</span></span><span class="line"><span class="ln">15</span><span class="cl">3. 使用[層級隔離派工方法論](/record/layered-ticket-methodology/) 驗證重構後是否符合層級隔離原則</span></span></code></pre></div><hr>
<h3 id="92-實務問題">9.2 實務問題</h3>
<h4 id="q6-如何處理必要的shotgun-surgery">Q6: 如何處理「必要的」Shotgun Surgery？</h4>
<p><strong>答</strong>: 區分<strong>真正的 Shotgun Surgery</strong> 和<strong>合理的跨層修改</strong>。</p>
<p><strong>特殊場景（可能需要跨層修改）</strong>:</p>
<ol>
<li>
<p><strong>架構遷移</strong>（一次性重構）</p>
<ul>
<li>情境：從舊架構遷移到 Clean Architecture</li>
<li>允許：臨時性的大規模修改</li>
<li>要求：完整的測試覆蓋率、詳細的遷移計畫</li>
</ul>
</li>
<li>
<p>Hotfix（緊急修復）</p>
<ul>
<li>情境：生產環境緊急 Bug 修復</li>
<li>允許：臨時性跨層修改</li>
<li>要求：事後必須重構、補充測試</li>
</ul>
</li>
<li>
<p><strong>新增核心欄位</strong>（影響多層的基礎資料）</p>
<ul>
<li>情境：新增影響整個系統的核心欄位</li>
<li>建議：使用 Facade 模式隔離變更</li>
<li>要求：遵循「從內而外」實作順序</li>
</ul>
</li>
</ol>
<p><strong>處理策略</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 評估是否為真正的「必要」跨層修改
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ 是否為架構遷移？ → 允許（一次性）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ├─ 是否為 Hotfix？ → 允許（事後重構）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  └─ 是否可引入 Facade 隔離？ → 建議重新設計
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">步驟 2: 如果確認「必要」，執行風險控制
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ├─ 確保測試覆蓋率 100%
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ 建立詳細的修改計畫
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 逐層修改並測試
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 記錄技術債務（Hotfix 情況）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">步驟 3: 事後處理
</span></span><span class="line"><span class="ln">13</span><span class="cl">  └─ Hotfix → 規劃重構 Ticket 消除技術債務
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ 架構遷移 → 完成後驗證架構一致性</span></span></code></pre></div><p><strong>範例說明</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 情境：新增「書籍語言」核心欄位
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// 反例：直接跨層修改
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// - Layer 5: Book Entity 新增 language
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// - Layer 3: GetBookDetailUseCase 處理 language
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// - Layer 2: Controller 新增 language 屬性
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// - Layer 1: UI 顯示 language
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// 正例：使用 Facade 隔離變更
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 步驟 1 [Layer 5]: Book Entity 新增 language
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// 步驟 2 [Layer 3]: BookDetailFacade 更新（統一處理）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">//   - Facade 內部整合新欄位
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">//   - 對外介面不變或最小變更
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// 步驟 3 [Layer 2]: Presenter 更新 ViewModel（只在這裡處理轉換）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// 步驟 4 [Layer 1]: UI 使用 ViewModel（透明變更）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// 效果：
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1">// - 未來新增欄位只需修改 Facade 和 Presenter
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="o">//</span> <span class="o">-</span> <span class="n">Layer</span> <span class="m">1</span> <span class="err">和</span> <span class="n">Layer</span> <span class="m">5</span> <span class="err">的修改影響已隔離</span></span></span></code></pre></div><hr>
<h4 id="q7-large-class-的-300-行標準是否太嚴格">Q7: Large Class 的 300 行標準是否太嚴格？</h4>
<p><strong>答</strong>: 300 行是<strong>建議標準</strong>，不是<strong>絕對限制</strong>。應根據<strong>類別職責</strong>判斷。</p>
<p><strong>彈性標準</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">良好大小類別:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- &lt; 200 行 → 優秀
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 200-300 行 → 良好（可接受）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 300-400 行 → 需要注意（考慮拆分）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- &gt; 400 行 → 需要拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">例外情況（可以超過 300 行）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">1. 配置類別（如 analysis_options.yaml 定義類別）
</span></span><span class="line"><span class="ln">10</span><span class="cl">2. 自動生成的程式碼（如 *.g.dart）
</span></span><span class="line"><span class="ln">11</span><span class="cl">3. 大型 enum 定義（如包含 50+ 個值）
</span></span><span class="line"><span class="ln">12</span><span class="cl">4. 完整的狀態機實作（如包含所有狀態轉換）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">判斷原則：
</span></span><span class="line"><span class="ln">15</span><span class="cl">「類別職責是否可以用一句話清楚描述？」
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ├─ 可以 → 即使超過 300 行也可接受
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ 不行 → 即使未超過 300 行也應拆分</span></span></code></pre></div><p><strong>實務建議</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 範例 1: 可接受的 Large Class
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// AppConfig.dart (350 行)
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">AppConfig</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="c1">// 統一管理所有應用程式配置
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>  <span class="c1">// 職責單一且明確：「應用程式配置管理」
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="c1">// 雖然超過 300 行，但職責清晰 → 可接受
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span>  <span class="kd">final</span> <span class="kt">String</span> <span class="n">appName</span> <span class="o">=</span> <span class="s1">&#39;書籍管理&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kd">final</span> <span class="kt">String</span> <span class="n">apiBaseUrl</span> <span class="o">=</span> <span class="s1">&#39;https://api.example.com&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// ... 100+ 個配置項
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// 範例 2: 需要拆分的類別
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// BookService.dart (280 行)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">BookService</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="c1">// 職責：書籍管理 + 查詢 + 統計 + 報表
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span>  <span class="c1">// 雖然未超過 300 行，但職責不單一 → 應該拆分
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span>  <span class="kt">void</span> <span class="n">addBook</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="kt">void</span> <span class="n">searchBooks</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kt">void</span> <span class="n">getStatistics</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="kt">void</span> <span class="n">exportReport</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="o">//</span> <span class="err">重點：判斷依據是「職責是否單一」，不只是「行數」</span></span></span></code></pre></div><hr>
<h4 id="q8-如何在敏捷開發中平衡速度和程式碼品質">Q8: 如何在敏捷開發中平衡速度和程式碼品質？</h4>
<p><strong>答</strong>: 使用<strong>分階段品質策略</strong> - Phase 1-3 優先速度，Phase 4 確保品質。</p>
<p><strong>分階段策略</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 1 設計（重點：Ticket 粒度）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 檢測: God Ticket、Ambiguous Responsibility
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 目標: 確保 Ticket 範圍合理
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 時間投入: 10 分鐘/Ticket
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Phase 2 測試設計（重點：測試完整性）:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">- 檢測: Incomplete Ticket
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 目標: 確保有測試設計
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 時間投入: 30 分鐘/Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">Phase 3 實作（重點：快速交付）:
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 檢測: 嚴重的 Code Smell（Inappropriate Intimacy、Leaky Abstraction）
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 目標: 快速實作功能，避免嚴重架構問題
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 時間投入: 根據 Ticket 預估工時
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">Phase 4 重構（重點：持續改進）:
</span></span><span class="line"><span class="ln">17</span><span class="cl">- 檢測: 所有 Code Smell
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 目標: 識別技術債務，規劃重構
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 時間投入: 20% 時間用於重構
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">平衡原則：
</span></span><span class="line"><span class="ln">22</span><span class="cl">「先快速交付功能（Phase 3），再持續改進品質（Phase 4）」</span></span></code></pre></div><p><strong>實務做法</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Sprint 規劃:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 80% 時間: 功能開發（Phase 1-3）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 20% 時間: 技術債務償還（Phase 4 重構）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">每個 Sprint:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">1. 功能開發（快速交付）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - 確保基本品質（無嚴重 Code Smell）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 允許存在低優先級 Code Smell
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">2. 技術債務償還（持續改進）
</span></span><span class="line"><span class="ln">12</span><span class="cl">   - 根據優先級評估公式選擇重構項目
</span></span><span class="line"><span class="ln">13</span><span class="cl">   - 優先處理高優先級 Code Smell
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">3. 平衡指標
</span></span><span class="line"><span class="ln">16</span><span class="cl">   - 新功能交付速度
</span></span><span class="line"><span class="ln">17</span><span class="cl">   - 技術債務控制在可接受範圍
</span></span><span class="line"><span class="ln">18</span><span class="cl">   - 測試覆蓋率維持 95%+</span></span></code></pre></div><hr>
<h4 id="q9-code-smell-檢測是否會增加-code-review-時間">Q9: Code Smell 檢測是否會增加 Code Review 時間？</h4>
<p><strong>答</strong>: 短期增加 5-10 分鐘，長期<strong>縮短</strong> Code Review 時間 30-40%。</p>
<p><strong>時間分析</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">傳統 Code Review（無系統化檢測）:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 審查時間: 30-45 分鐘/PR
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 問題發現率: 60%（依賴 Reviewer 經驗）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 往返次數: 平均 2-3 次
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 總時間成本: 60-120 分鐘
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">使用 Code Smell 檢查清單:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- 快速檢查: 5 分鐘（使用 6.1 快速檢查清單）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 深度檢查: 15 分鐘（使用 6.2 深度檢查清單）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 問題發現率: 90%（系統化檢測）
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 往返次數: 平均 1 次（問題更早發現）
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 總時間成本: 20-30 分鐘
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">時間節省: 40-90 分鐘/PR（66-75% 改善）</span></span></code></pre></div><p><strong>改善原因</strong>:</p>
<ol>
<li><strong>系統化檢測更快</strong>（不依賴回憶）</li>
<li><strong>問題更早發現</strong>（減少往返次數）</li>
<li><strong>檢測標準統一</strong>（減少討論時間）</li>
</ol>
<p><strong>實測數據</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">專案A (10 人團隊，100 個 PR/月):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">- 導入前: 平均 Code Review 時間 45 分鐘/PR
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- 導入後: 平均 Code Review 時間 18 分鐘/PR
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- 改善: 60% 時間節省
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 每月節省: 27 * 100 = 2700 分鐘（45 小時）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">品質改善:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- Bug 數量: 減少 40%
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- 重構需求: 減少 50%（問題更早發現）
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 團隊滿意度: 提升（減少返工）</span></span></code></pre></div><hr>
<h4 id="q10-團隊成員對-code-smell-標準有不同理解怎麼辦">Q10: 團隊成員對 Code Smell 標準有不同理解怎麼辦？</h4>
<p><strong>答</strong>: 建立<strong>共識機制</strong> - 團隊 Code Smell 討論會 + 案例庫。</p>
<p><strong>共識建立流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 初始化階段（第 1-2 週）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  - 全體成員閱讀本 Code Smell 檢查清單
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  - 舉辦 Code Smell 培訓工作坊（2 小時）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - 討論量化標準是否適用於團隊
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">步驟 2: 調整階段（第 3-4 週）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  - 每週 Code Smell 討論會（30 分鐘）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - 討論爭議案例
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 調整團隊特定標準（如果需要）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 3: 穩定階段（第 5 週後）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - 建立團隊 Code Smell 案例庫
</span></span><span class="line"><span class="ln">13</span><span class="cl">  - 持續更新檢查清單
</span></span><span class="line"><span class="ln">14</span><span class="cl">  - 每月回顧和優化標準</span></span></code></pre></div><p><strong>爭議處理機制</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">情境：團隊成員對「Large Class」標準有不同意見
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">成員 A: 「300 行太嚴格，我們的配置類別都超過 300 行」
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">成員 B: 「300 行是合理標準，配置類別應該拆分」
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">處理流程:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">1. 討論會議（30 分鐘）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 展示具體案例
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 分析職責是否單一
</span></span><span class="line"><span class="ln">11</span><span class="cl">   - 評估拆分成本和收益
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">2. 團隊共識
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 投票決定團隊標準
</span></span><span class="line"><span class="ln">15</span><span class="cl">   - 記錄決策理由
</span></span><span class="line"><span class="ln">16</span><span class="cl">   - 更新團隊檢查清單
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">3. 案例記錄
</span></span><span class="line"><span class="ln">19</span><span class="cl">   - 將決策加入團隊案例庫
</span></span><span class="line"><span class="ln">20</span><span class="cl">   - 未來遇到類似情況參考此案例</span></span></code></pre></div><p><strong>團隊案例庫範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># 團隊 Code Smell 案例庫
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 案例 #1: AppConfig 類別（350 行）
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**爭議**</span>: 是否屬於 Large Class？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gs">**團隊決議**</span>: 可接受
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gs">**理由**</span>: 職責單一（應用程式配置管理），雖超過 300 行但不拆分
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**標準**</span>: 配置類別可以超過 300 行，但職責必須單一
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">---
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">## 案例 #2: BookController（280 行，4 種職責）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gs">**爭議**</span>: 未超過 300 行，是否需要拆分？
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gs">**團隊決議**</span>: 需要拆分
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gs">**理由**</span>: 雖未超過 300 行，但有 4 種職責（Divergent Change）
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gs">**標準**</span>: 判斷依據是「職責是否單一」，不只是「行數」</span></span></code></pre></div><hr>
<h3 id="93-工具問題">9.3 工具問題</h3>
<h4 id="q11-如何自動化檢測-code-smell">Q11: 如何自動化檢測 Code Smell？</h4>
<p><strong>答</strong>: 整合靜態分析工具 + Hook 系統 + CI/CD pipeline。</p>
<p><strong>自動化檢測架構</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Level 1: 本地開發（即時反饋）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ PostEdit Hook → 程式碼修改後立即檢測
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">     ├─ dart analyze（Dead Code、unused 警告）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">     ├─ 檔案行數檢查（Large Class、Long Method）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">     └─ import 語句分析（Feature Envy、Inappropriate Intimacy）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Level 2: 提交前（全面檢測）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ Pre-Commit Hook → git commit 前檢測
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">     ├─ 執行所有 Level 1 檢測
</span></span><span class="line"><span class="ln">10</span><span class="cl">     ├─ 測試覆蓋率檢查（Incomplete Ticket）
</span></span><span class="line"><span class="ln">11</span><span class="cl">     └─ Code Smell 優先級評估
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Level 3: PR 階段（完整報告）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ GitHub Actions → PR 提交時檢測
</span></span><span class="line"><span class="ln">15</span><span class="cl">     ├─ 執行所有 Level 1-2 檢測
</span></span><span class="line"><span class="ln">16</span><span class="cl">     ├─ 生成 Code Smell 檢測報告
</span></span><span class="line"><span class="ln">17</span><span class="cl">     └─ 高優先級問題阻止合併</span></span></code></pre></div><p><strong>工具整合範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/hooks/post-edit.sh</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Level 1: 即時檢測</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;執行 Code Smell 即時檢測...&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 1. dart analyze</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">dart analyze --fatal-infos 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep <span class="s2">&#34;unused&#34;</span> <span class="o">&amp;&amp;</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;⚠️ 檢測到 Dead Code (unused 警告)&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 2. 檔案行數檢查</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">for</span> file in <span class="k">$(</span>git diff --name-only --staged<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$file</span> <span class="o">==</span> *.dart <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nv">lines</span><span class="o">=</span><span class="k">$(</span>wc -l &lt; <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$lines</span><span class="s2">&#34;</span> -gt <span class="m">300</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️ Large Class: </span><span class="nv">$file</span><span class="s2"> (</span><span class="nv">$lines</span><span class="s2"> 行)&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 3. import 語句分析</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">for</span> file in <span class="k">$(</span>git diff --name-only --staged<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$file</span> <span class="o">==</span> lib/presentation/* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">if</span> grep -q <span class="s2">&#34;import.*domains/.*/entities&#34;</span> <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️ Feature Envy: UI 直接 import Domain Entity&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">done</span></span></span></code></pre></div><p><strong>CI/CD 整合範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .github/workflows/code-smell.yml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Code Smell 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">pull_request]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span><span class="nt">code-smell</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v3</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Code Smell 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sd">          # 執行完整檢測
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="sd">          bash .claude/scripts/code-smell-check.sh
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="sd">          # 生成報告
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="sd">          python .claude/scripts/generate-report.py</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">檢查優先級</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="sd">          # 高優先級問題 → 阻止合併
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="sd">          python .claude/scripts/check-priority.py --fail-on-high</span></span></span></code></pre></div><hr>
<h4 id="q12-dart_code_metrics-和本檢查清單的關係">Q12: dart_code_metrics 和本檢查清單的關係？</h4>
<p><strong>答</strong>: <strong>互補關係</strong> - dart_code_metrics 提供量化指標，本檢查清單提供架構檢測。</p>
<p><strong>工具定位</strong>:</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>dart_code_metrics</th>
          <th>Code Smell 檢查清單（本文件）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>檢測範圍</strong></td>
          <td>程式碼複雜度、重複度</td>
          <td>架構設計、層級隔離</td>
      </tr>
      <tr>
          <td><strong>檢測對象</strong></td>
          <td>單一檔案、方法</td>
          <td>跨檔案、跨層級</td>
      </tr>
      <tr>
          <td><strong>量化指標</strong></td>
          <td>循環複雜度、認知複雜度</td>
          <td>檔案數、層級跨度</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>Phase 3 實作、Phase 4 重構</td>
          <td>Phase 1 設計、Code Review</td>
      </tr>
  </tbody>
</table>
<p><strong>整合使用</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># analysis_options.yaml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">dart_code_metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">metrics</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="c"># B3. Long Method 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">cyclomatic-complexity</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">lines-of-code</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">maximum-nesting-level</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="c"># B2. Large Class 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">number-of-methods</span><span class="p">:</span><span class="w"> </span><span class="m">15</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="nt">weight-of-class</span><span class="p">:</span><span class="w"> </span><span class="m">0.33</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="c"># B4. Dead Code 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span>- <span class="l">avoid-unused-parameters</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="c"># B1. Divergent Change 檢測</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span>- <span class="l">prefer-single-widget-per-file</span></span></span></code></pre></div><p><strong>協作流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">步驟 1: dart_code_metrics 檢測程式碼複雜度
</span></span><span class="line"><span class="ln">2</span><span class="cl">  └─ 輸出: 方法行數、循環複雜度、認知複雜度
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">步驟 2: 本檢查清單檢測架構問題
</span></span><span class="line"><span class="ln">5</span><span class="cl">  └─ 輸出: 層級跨度、依賴方向、Ticket 粒度
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">步驟 3: 整合報告
</span></span><span class="line"><span class="ln">8</span><span class="cl">  └─ 結合兩者結果，提供完整的 Code Smell 檢測報告</span></span></code></pre></div><hr>
<h4 id="q13-如何處理自動生成的程式碼如-gdart">Q13: 如何處理自動生成的程式碼（如 *.g.dart）？</h4>
<p><strong>答</strong>: 在檢測配置中<strong>排除</strong>自動生成的程式碼。</p>
<p><strong>排除配置</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># analysis_options.yaml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">analyzer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">exclude</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="c"># 排除自動生成的程式碼</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span>- <span class="s2">&#34;**/*.g.dart&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span>- <span class="s2">&#34;**/*.freezed.dart&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span>- <span class="s2">&#34;**/generated/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span>- <span class="s2">&#34;build/**&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="c"># 排除第三方程式碼</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span>- <span class="s2">&#34;lib/generated_plugin_registrant.dart&#34;</span></span></span></code></pre></div><p><strong>Hook 系統排除</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/hooks/code-smell-check.sh</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 排除自動生成的檔案</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">for</span> file in <span class="k">$(</span>git diff --name-only --staged<span class="k">)</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="c1"># 跳過 *.g.dart</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$file</span> <span class="o">==</span> *.g.dart <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="c1"># 跳過 *.freezed.dart</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$file</span> <span class="o">==</span> *.freezed.dart <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="c1"># 執行檢測</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  check_code_smell <span class="s2">&#34;</span><span class="nv">$file</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">done</span></span></span></code></pre></div><p><strong>原則</strong>:</p>
<ul>
<li>檢測：手寫程式碼</li>
<li>不檢測：自動生成的程式碼（*.g.dart, *.freezed.dart）</li>
<li>不檢測：第三方程式碼（dependencies）</li>
<li>不檢測：測試 Mock 程式碼（*.mocks.dart）</li>
</ul>
<hr>
<h4 id="q14-如何在-vs-code-中整合-code-smell-檢測">Q14: 如何在 VS Code 中整合 Code Smell 檢測？</h4>
<p><strong>答</strong>: 使用 <strong>VS Code 擴充功能</strong> + <strong>Tasks</strong> + <strong>Problem Matchers</strong>。</p>
<p><strong>設定檔配置</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// .vscode/tasks.json
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;2.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nt">&#34;tasks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span> <span class="s2">&#34;Code Smell 檢測&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;shell&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;bash .claude/scripts/code-smell-check.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">      <span class="nt">&#34;problemMatcher&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nt">&#34;owner&#34;</span><span class="p">:</span> <span class="s2">&#34;code-smell&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nt">&#34;fileLocation&#34;</span><span class="p">:</span> <span class="s2">&#34;relative&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nt">&#34;pattern&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">          <span class="nt">&#34;regexp&#34;</span><span class="p">:</span> <span class="s2">&#34;^(⚠️|❌)\\s+(\\w+):\\s+(.+)\\s+\\((.+):(\\d+)\\)$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">          <span class="nt">&#34;severity&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">          <span class="nt">&#34;code&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">          <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">          <span class="nt">&#34;file&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">          <span class="nt">&#34;line&#34;</span><span class="p">:</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">      <span class="nt">&#34;group&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="nt">&#34;kind&#34;</span><span class="p">:</span> <span class="s2">&#34;test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="nt">&#34;isDefault&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>快捷鍵設定</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// .vscode/keybindings.json
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="p">[</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+shift+s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.tasks.runTask&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="s2">&#34;Code Smell 檢測&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><p><strong>使用方式</strong>:</p>
<ol>
<li>按 <code>Ctrl+Shift+S</code> 執行 Code Smell 檢測</li>
<li>問題面板顯示檢測結果</li>
<li>點擊問題項目跳轉到對應程式碼</li>
</ol>
<hr>
<h4 id="q15-測試覆蓋率工具與-code-smell-檢測的關係">Q15: 測試覆蓋率工具與 Code Smell 檢測的關係？</h4>
<p><strong>答</strong>: 測試覆蓋率工具檢測<strong>測試完整性</strong>，輔助識別 <strong>Dead Code</strong> 和 <strong>Incomplete Ticket</strong>。</p>
<p><strong>工具整合</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 執行測試並生成覆蓋率報告</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 2. 分析覆蓋率報告</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># a. 0% 覆蓋率 → 可能是 Dead Code</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">lcov --summary coverage/lcov.info <span class="p">|</span> grep <span class="s2">&#34;0.0%&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># b. 新增程式碼無測試 → Incomplete Ticket</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">git diff main --name-only <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> file<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="nv">$file</span> <span class="o">==</span> lib/*.dart <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nv">test_file</span><span class="o">=</span><span class="s2">&#34;test/</span><span class="si">${</span><span class="nv">file</span><span class="p">#lib/</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nv">test_file</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">test_file</span><span class="p">%.dart</span><span class="si">}</span><span class="s2">_test.dart&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="o">[</span> ! -f <span class="s2">&#34;</span><span class="nv">$test_file</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️ Incomplete Ticket: </span><span class="nv">$file</span><span class="s2"> 缺少測試檔案&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 3. 生成 HTML 報告</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">genhtml coverage/lcov.info -o coverage/html</span></span></code></pre></div><p><strong>Dead Code 檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 執行測試覆蓋率分析
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ flutter test --coverage
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 識別 0% 覆蓋率程式碼
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 可能是 Dead Code 或缺少測試
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 交叉驗證
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ dart analyze 有 unused 警告？ → Dead Code
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ dart analyze 無警告？ → 缺少測試
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 4: 採取行動
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ Dead Code → 刪除
</span></span><span class="line"><span class="ln">13</span><span class="cl">  └─ 缺少測試 → 補充測試</span></span></code></pre></div><p><strong>Incomplete Ticket 檢測流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查程式碼檔案是否有對應測試
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  └─ lib/foo.dart → test/foo_test.dart 是否存在？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 檢查測試覆蓋率
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  └─ 新增程式碼覆蓋率是否達到 100%？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 判斷
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ 無測試檔案 → Incomplete Ticket
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 覆蓋率 &lt; 100% → Incomplete Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ 覆蓋率 = 100% → 完整 Ticket</span></span></code></pre></div><hr>
<h2 id="第十章參考資料">第十章：參考資料</h2>
<h3 id="101-引用的方法論">10.1 引用的方法論</h3>
<p>本 Code Smell 檢查清單基於以下方法論建立：</p>
<h4 id="層級隔離派工方法論"><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a></h4>
<p><strong>檔案位置</strong>: <code>.claude/methodologies/layered-ticket-methodology.md</code></p>
<p><strong>引用章節</strong>:</p>
<ul>
<li>
<p><strong>2.2 節</strong>: Clean Architecture 五層完整定義</p>
<ul>
<li>Layer 1 (UI): 視覺呈現職責</li>
<li>Layer 2 (Behavior): 事件處理和資料轉換職責</li>
<li>Layer 3 (UseCase): 業務流程協調職責</li>
<li>Layer 4 (Domain Interface): 介面契約職責</li>
<li>Layer 5 (Domain): 業務規則和不可變邏輯職責</li>
</ul>
</li>
<li>
<p><strong>2.3 節</strong>: 依賴方向規則</p>
<ul>
<li>正確依賴方向：Layer 1 → Layer 2 → Layer 3 → Layer 4 ← Layer 5</li>
</ul>
</li>
<li>
<p><strong>2.4 節</strong>: 層級定位決策樹</p>
<ul>
<li>檔案路徑分析法判斷層級歸屬</li>
</ul>
</li>
<li>
<p><strong>3.1 節</strong>: 單層修改原則定義</p>
<ul>
<li>單一 Ticket 應該只修改單一架構層級</li>
</ul>
</li>
<li>
<p><strong>3.2 節</strong>: SRP 理論依據</p>
<ul>
<li>Single Responsibility Principle 應用</li>
</ul>
</li>
<li>
<p><strong>5.2 節</strong>: Ticket 粒度量化指標</p>
<ul>
<li>良好 Ticket：1-5 個檔案，1 層，2-8 小時</li>
<li>God Ticket：&gt; 10 個檔案，&gt; 2 層，&gt; 16 小時</li>
</ul>
</li>
<li>
<p><strong>5.4 節</strong>: Ticket 拆分指引</p>
<ul>
<li>按層級拆分、按 Domain 拆分、按功能拆分</li>
</ul>
</li>
<li>
<p><strong>6.2 節</strong>: 檔案路徑分析法</p>
<ul>
<li>從檔案路徑判斷層級歸屬</li>
</ul>
</li>
<li>
<p><strong>6.4 節</strong>: 測試層級對應原則</p>
<ul>
<li>測試檔案路徑對應層級結構</li>
</ul>
</li>
<li>
<p><strong>6.5 節</strong>: 違規模式識別</p>
<ul>
<li>常見的層級違規模式</li>
</ul>
</li>
</ul>
<p><strong>關係說明</strong>:</p>
<ul>
<li><a href="/blog/record/%E5%B1%A4%E7%B4%9A%E9%9A%94%E9%9B%A2%E8%AE%93%E6%AF%8F%E5%BC%B5-ticket-%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E5%B1%A4%E7%B4%9A%E7%9A%84%E4%BA%8B/" data-link-title="層級隔離：讓每張 Ticket 只做一件層級的事" data-link-desc="我們在實際開發中整理出一套方法論，讓 Clean Architecture 五層架構與 Ticket 拆分真正結合——每張 Ticket 只修改一個架構層，不多也不少。">層級隔離派工方法論</a> 定義「應該怎麼做」（正面原則）</li>
<li>本檢查清單定義「不應該怎麼做」（負面模式識別）</li>
<li>兩者互補，共同建構完整的品質標準體系</li>
</ul>
<hr>
<h3 id="102-code-smell-理論文獻">10.2 Code Smell 理論文獻</h3>
<h4 id="martin-fowler---refactoring-improving-the-design-of-existing-code">Martin Fowler - Refactoring: Improving the Design of Existing Code</h4>
<p><strong>重要概念</strong>:</p>
<ul>
<li>Code Smell 定義和分類</li>
<li>重構模式目錄</li>
<li>Extract Method、Extract Class、Move Method 等重構技巧</li>
</ul>
<p><strong>本檢查清單應用</strong>:</p>
<ul>
<li>第四章重構模式對應表引用 Fowler 的重構模式</li>
<li>重構步驟設計參考 Fowler 的重構技巧</li>
</ul>
<p><strong>延伸閱讀</strong>: <a href="https://refactoring.com/">refactoring.com</a></p>
<hr>
<h4 id="robert-c-martin---clean-code">Robert C. Martin - Clean Code</h4>
<p><strong>重要概念</strong>:</p>
<ul>
<li>有意義的命名</li>
<li>函式應該短小</li>
<li>單一職責原則（SRP）</li>
<li>依賴倒置原則（DIP）</li>
</ul>
<p><strong>本檢查清單應用</strong>:</p>
<ul>
<li>Long Method 判斷標準（&lt; 50 行）</li>
<li>Divergent Change 檢測（SRP 違反）</li>
<li>Inappropriate Intimacy 檢測（DIP 違反）</li>
</ul>
<hr>
<h4 id="robert-c-martin---clean-architecture">Robert C. Martin - Clean Architecture</h4>
<p><strong>重要概念</strong>:</p>
<ul>
<li>分層架構設計</li>
<li>依賴規則（Dependency Rule）</li>
<li>介面隔離原則</li>
</ul>
<p><strong>本檢查清單應用</strong>:</p>
<ul>
<li>A 類 Code Smell 分類（跨層級問題）</li>
<li>Inappropriate Intimacy 檢測（依賴方向錯誤）</li>
<li>Leaky Abstraction 檢測（介面設計問題）</li>
</ul>
<hr>
<h3 id="103-重構模式參考">10.3 重構模式參考</h3>
<h4 id="extract-interface提取介面">Extract Interface（提取介面）</h4>
<p><strong>用途</strong>: 修正 Leaky Abstraction</p>
<p><strong>重構步驟</strong>:</p>
<ol>
<li>分析具體類別的公開方法</li>
<li>建立介面定義</li>
<li>提取抽象方法簽名</li>
<li>讓具體類別實作介面</li>
<li>更新依賴為使用介面</li>
</ol>
<p><strong>參考</strong>: Fowler, Refactoring (1999), p.341</p>
<hr>
<h4 id="extract-method提取方法">Extract Method（提取方法）</h4>
<p><strong>用途</strong>: 修正 Long Method</p>
<p><strong>重構步驟</strong>:</p>
<ol>
<li>識別邏輯區塊</li>
<li>為區塊建立新方法</li>
<li>傳遞必要參數</li>
<li>回傳必要值</li>
<li>替換原區塊為方法呼叫</li>
</ol>
<p><strong>參考</strong>: Fowler, Refactoring (1999), p.110</p>
<hr>
<h4 id="extract-class提取類別">Extract Class（提取類別）</h4>
<p><strong>用途</strong>: 修正 Large Class、Divergent Change</p>
<p><strong>重構步驟</strong>:</p>
<ol>
<li>分析方法分組</li>
<li>建立新類別</li>
<li>移動相關欄位和方法</li>
<li>建立委派方法（如需要）</li>
<li>更新依賴關係</li>
</ol>
<p><strong>參考</strong>: Fowler, Refactoring (1999), p.149</p>
<hr>
<h4 id="move-method移動方法">Move Method（移動方法）</h4>
<p><strong>用途</strong>: 修正 Feature Envy</p>
<p><strong>重構步驟</strong>:</p>
<ol>
<li>識別方法應該屬於哪個類別</li>
<li>在目標類別建立方法</li>
<li>調整參數和回傳值</li>
<li>移除原方法或建立委派</li>
<li>更新呼叫端</li>
</ol>
<p><strong>參考</strong>: Fowler, Refactoring (1999), p.142</p>
<hr>
<h4 id="introduce-facade引入外觀">Introduce Facade（引入外觀）</h4>
<p><strong>用途</strong>: 修正 Shotgun Surgery</p>
<p><strong>重構步驟</strong>:</p>
<ol>
<li>分析跨層操作的共同點</li>
<li>建立 Facade 介面</li>
<li>實作 Facade 封裝跨層操作</li>
<li>更新呼叫端使用 Facade</li>
<li>驗證未來變更只需修改 Facade</li>
</ol>
<p><strong>參考</strong>: Gang of Four, Design Patterns (1994), p.185</p>
]]></content:encoded></item><item><title>Ticket 設計派工方法論</title><link>https://tarrragon.github.io/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/ticket-%E8%A8%AD%E8%A8%88%E6%B4%BE%E5%B7%A5%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="方法論概述">方法論概述&lt;/h2>
&lt;p>這裡嘗試以 Ticket 設計和派工機制，解決大型開發任務的協作效率問題，特別是工作日誌臃腫和實作偏差風險。&lt;/p>
&lt;p>但是這篇方法論太長了，之後會分拆成多個方法論方便閱讀&lt;/p>
&lt;p>&lt;strong>適用場景&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>多人協作開發&lt;/li>
&lt;li>大型功能模組開發&lt;/li>
&lt;li>需要精細進度追蹤的專案&lt;/li>
&lt;li>需要即時 review 機制的團隊&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>核心目標&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>建立量化的 Ticket 拆分標準&lt;/li>
&lt;li>定義完整的 Ticket 生命週期&lt;/li>
&lt;li>建立即時 Review 機制&lt;/li>
&lt;li>避免工作日誌臃腫&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>方法論版本&lt;/strong>：v1.0.0&lt;/p>
&lt;hr>
&lt;h2 id="第一章ticket-機制核心原則">第一章：Ticket 機制核心原則&lt;/h2>
&lt;h3 id="11-ticket-vs-工作日誌的定位差異">1.1 Ticket vs 工作日誌的定位差異&lt;/h3>
&lt;p>&lt;strong>Ticket 定義&lt;/strong>：&lt;/p>
&lt;p>Ticket 是「最小可交付單元」（Minimal Deliverable Unit），代表一個可以獨立完成、驗收和追蹤的最小任務單位。&lt;/p>
&lt;p>&lt;strong>核心特徵&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>獨立性&lt;/strong>：可以獨立執行和驗收&lt;/li>
&lt;li>&lt;strong>原子性&lt;/strong>：不可再分割為更小的可交付單元&lt;/li>
&lt;li>&lt;strong>可驗證性&lt;/strong>：有明確的完成標準&lt;/li>
&lt;li>&lt;strong>複雜度限制&lt;/strong>：基於職責、檔案、測試、行數的量化標準&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>工作日誌定義&lt;/strong>：&lt;/p>
&lt;p>工作日誌是記錄整個開發過程的完整文檔，包含設計決策、實作細節、問題分析和解決方案。&lt;/p>
&lt;p>&lt;strong>核心特徵&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>完整性&lt;/strong>：記錄所有決策和過程&lt;/li>
&lt;li>&lt;strong>追溯性&lt;/strong>：提供歷史記錄和演進軌跡&lt;/li>
&lt;li>&lt;strong>知識傳承&lt;/strong>：為後續開發者提供上下文&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>定位差異總結&lt;/strong>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Ticket&lt;/th>
 &lt;th>工作日誌&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>範圍&lt;/strong>&lt;/td>
 &lt;td>單一具體任務&lt;/td>
 &lt;td>整個功能模組或版本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>粒度&lt;/strong>&lt;/td>
 &lt;td>最小可交付單元（單一職責或少數相關職責）&lt;/td>
 &lt;td>完整開發週期（數天到數週）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>目的&lt;/strong>&lt;/td>
 &lt;td>任務執行和驗收&lt;/td>
 &lt;td>知識記錄和傳承&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>更新頻率&lt;/strong>&lt;/td>
 &lt;td>執行中持續更新&lt;/td>
 &lt;td>階段性更新&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>文件大小&lt;/strong>&lt;/td>
 &lt;td>100-200 行&lt;/td>
 &lt;td>500-6000 行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>適用場景&lt;/strong>&lt;/td>
 &lt;td>協作開發、進度追蹤&lt;/td>
 &lt;td>決策記錄、技術傳承&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>互補關係&lt;/strong>：&lt;/p>
&lt;p>Ticket 和工作日誌是互補關係：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ticket&lt;/strong> 負責「執行層面」：將大任務拆分為可管理的小單元&lt;/li>
&lt;li>&lt;strong>工作日誌&lt;/strong> 負責「記錄層面」：記錄決策過程和演進軌跡&lt;/li>
&lt;li>&lt;strong>主版本日誌&lt;/strong> 負責「總覽層面」：提供任務總覽和 Ticket 索引&lt;/li>
&lt;/ul>
&lt;h3 id="12-ticket-機制的三大目標">1.2 Ticket 機制的三大目標&lt;/h3>
&lt;h4 id="目標-1可追溯性traceability">目標 1：可追溯性（Traceability）&lt;/h4>
&lt;p>&lt;strong>定義&lt;/strong>：每個 Ticket 都有明確的來源和目標，可以追溯到需求、設計文件或問題報告。&lt;/p>
&lt;p>&lt;strong>實現方式&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Ticket 必須包含「參考文件」欄位，連結到需求規格或設計文件&lt;/li>
&lt;li>Ticket 必須包含「背景」欄位，說明為什麼需要這個 Ticket&lt;/li>
&lt;li>主版本日誌維護完整的 Ticket 索引&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>範例&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">### 參考文件
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>&lt;span class="k">-&lt;/span> v0.12.7-design-decisions.md &lt;span class="ni">#決策1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">-&lt;/span> docs/app-requirements-spec.md &lt;span class="ni">#UC&lt;/span>-01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="gu">### 背景
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>根據 UC-01 需求，需要建立書籍資訊豐富化服務的介面契約&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效益&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>開發者清楚知道「為什麼」要做這個任務&lt;/li>
&lt;li>PM 可以追蹤每個 Ticket 的需求來源&lt;/li>
&lt;li>後續維護者可以理解設計意圖&lt;/li>
&lt;/ul>
&lt;h4 id="目標-2可驗收性verifiability">目標 2：可驗收性（Verifiability）&lt;/h4>
&lt;p>&lt;strong>定義&lt;/strong>：每個 Ticket 都有明確、可驗證的完成標準，避免主觀判斷。&lt;/p>
&lt;p>&lt;strong>實現方式&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Ticket 必須包含「驗收條件」欄位，列出所有可驗證的條件&lt;/li>
&lt;li>驗收條件必須是客觀可檢查的（檔案存在、測試通過、功能運作）&lt;/li>
&lt;li>Review 時逐項檢查驗收條件&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>範例&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">### 驗收條件
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>&lt;span class="k">- [ ]&lt;/span> 介面檔案建立在 &lt;span class="sb">`lib/domains/import/services/`&lt;/span> 目錄
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">- [ ]&lt;/span> &lt;span class="sb">`enrichBook`&lt;/span> 方法簽名完整且明確
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">- [ ]&lt;/span> 輸入輸出類型定義清楚
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="k">- [ ]&lt;/span> 包含完整的文檔註解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">- [ ] dart analyze 0 錯誤&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效益&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>避免「看起來完成了」但實際未達標準&lt;/li>
&lt;li>提供明確的驗收依據&lt;/li>
&lt;li>減少 review 時的主觀爭議&lt;/li>
&lt;/ul>
&lt;h4 id="目標-3可協作性collaborability">目標 3：可協作性（Collaborability）&lt;/h4>
&lt;p>&lt;strong>定義&lt;/strong>：多個開發者可以並行執行不同的 Ticket，互不阻塞。&lt;/p>
&lt;p>&lt;strong>實現方式&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Ticket 拆分時考慮依賴關係，最小化依賴&lt;/li>
&lt;li>明確標註 Ticket 間的依賴關係（必須先完成 / 可並行）&lt;/li>
&lt;li>使用 Interface-Driven 開發，內層未完成也可開發外層&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>範例&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">### 依賴 Ticket
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>&lt;span class="k">-&lt;/span> Ticket &lt;span class="ni">#1:&lt;/span> 定義 IBookInfoEnrichmentService 介面（必須先完成）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">- Ticket &lt;span class="ni">#3:&lt;/span> 撰寫 EnrichmentProgress 測試（可並行）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效益&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>多人可同時開發不同模組&lt;/li>
&lt;li>減少等待時間&lt;/li>
&lt;li>提升開發效率&lt;/li>
&lt;/ul>
&lt;h3 id="13-基於-clean-architecture-的-ticket-設計哲學">1.3 基於 Clean Architecture 的 Ticket 設計哲學&lt;/h3>
&lt;h4 id="clean-architecture-核心原則回顧">Clean Architecture 核心原則回顧&lt;/h4>
&lt;p>&lt;strong>依賴方向規則&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<h2 id="方法論概述">方法論概述</h2>
<p>這裡嘗試以 Ticket 設計和派工機制，解決大型開發任務的協作效率問題，特別是工作日誌臃腫和實作偏差風險。</p>
<p>但是這篇方法論太長了，之後會分拆成多個方法論方便閱讀</p>
<p><strong>適用場景</strong>：</p>
<ul>
<li>多人協作開發</li>
<li>大型功能模組開發</li>
<li>需要精細進度追蹤的專案</li>
<li>需要即時 review 機制的團隊</li>
</ul>
<p><strong>核心目標</strong>：</p>
<ul>
<li>建立量化的 Ticket 拆分標準</li>
<li>定義完整的 Ticket 生命週期</li>
<li>建立即時 Review 機制</li>
<li>避免工作日誌臃腫</li>
</ul>
<p><strong>方法論版本</strong>：v1.0.0</p>
<hr>
<h2 id="第一章ticket-機制核心原則">第一章：Ticket 機制核心原則</h2>
<h3 id="11-ticket-vs-工作日誌的定位差異">1.1 Ticket vs 工作日誌的定位差異</h3>
<p><strong>Ticket 定義</strong>：</p>
<p>Ticket 是「最小可交付單元」（Minimal Deliverable Unit），代表一個可以獨立完成、驗收和追蹤的最小任務單位。</p>
<p><strong>核心特徵</strong>：</p>
<ul>
<li><strong>獨立性</strong>：可以獨立執行和驗收</li>
<li><strong>原子性</strong>：不可再分割為更小的可交付單元</li>
<li><strong>可驗證性</strong>：有明確的完成標準</li>
<li><strong>複雜度限制</strong>：基於職責、檔案、測試、行數的量化標準</li>
</ul>
<p><strong>工作日誌定義</strong>：</p>
<p>工作日誌是記錄整個開發過程的完整文檔，包含設計決策、實作細節、問題分析和解決方案。</p>
<p><strong>核心特徵</strong>：</p>
<ul>
<li><strong>完整性</strong>：記錄所有決策和過程</li>
<li><strong>追溯性</strong>：提供歷史記錄和演進軌跡</li>
<li><strong>知識傳承</strong>：為後續開發者提供上下文</li>
</ul>
<p><strong>定位差異總結</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Ticket</th>
          <th>工作日誌</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>範圍</strong></td>
          <td>單一具體任務</td>
          <td>整個功能模組或版本</td>
      </tr>
      <tr>
          <td><strong>粒度</strong></td>
          <td>最小可交付單元（單一職責或少數相關職責）</td>
          <td>完整開發週期（數天到數週）</td>
      </tr>
      <tr>
          <td><strong>目的</strong></td>
          <td>任務執行和驗收</td>
          <td>知識記錄和傳承</td>
      </tr>
      <tr>
          <td><strong>更新頻率</strong></td>
          <td>執行中持續更新</td>
          <td>階段性更新</td>
      </tr>
      <tr>
          <td><strong>文件大小</strong></td>
          <td>100-200 行</td>
          <td>500-6000 行</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>協作開發、進度追蹤</td>
          <td>決策記錄、技術傳承</td>
      </tr>
  </tbody>
</table>
<p><strong>互補關係</strong>：</p>
<p>Ticket 和工作日誌是互補關係：</p>
<ul>
<li><strong>Ticket</strong> 負責「執行層面」：將大任務拆分為可管理的小單元</li>
<li><strong>工作日誌</strong> 負責「記錄層面」：記錄決策過程和演進軌跡</li>
<li><strong>主版本日誌</strong> 負責「總覽層面」：提供任務總覽和 Ticket 索引</li>
</ul>
<h3 id="12-ticket-機制的三大目標">1.2 Ticket 機制的三大目標</h3>
<h4 id="目標-1可追溯性traceability">目標 1：可追溯性（Traceability）</h4>
<p><strong>定義</strong>：每個 Ticket 都有明確的來源和目標，可以追溯到需求、設計文件或問題報告。</p>
<p><strong>實現方式</strong>：</p>
<ul>
<li>Ticket 必須包含「參考文件」欄位，連結到需求規格或設計文件</li>
<li>Ticket 必須包含「背景」欄位，說明為什麼需要這個 Ticket</li>
<li>主版本日誌維護完整的 Ticket 索引</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> v0.12.7-design-decisions.md <span class="ni">#決策1</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> docs/app-requirements-spec.md <span class="ni">#UC</span>-01
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="gu"></span>根據 UC-01 需求，需要建立書籍資訊豐富化服務的介面契約</span></span></code></pre></div><p><strong>效益</strong>：</p>
<ul>
<li>開發者清楚知道「為什麼」要做這個任務</li>
<li>PM 可以追蹤每個 Ticket 的需求來源</li>
<li>後續維護者可以理解設計意圖</li>
</ul>
<h4 id="目標-2可驗收性verifiability">目標 2：可驗收性（Verifiability）</h4>
<p><strong>定義</strong>：每個 Ticket 都有明確、可驗證的完成標準，避免主觀判斷。</p>
<p><strong>實現方式</strong>：</p>
<ul>
<li>Ticket 必須包含「驗收條件」欄位，列出所有可驗證的條件</li>
<li>驗收條件必須是客觀可檢查的（檔案存在、測試通過、功能運作）</li>
<li>Review 時逐項檢查驗收條件</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 介面檔案建立在 <span class="sb">`lib/domains/import/services/`</span> 目錄
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">- [ ]</span> <span class="sb">`enrichBook`</span> 方法簽名完整且明確
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">- [ ]</span> 輸入輸出類型定義清楚
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">- [ ]</span> 包含完整的文檔註解
</span></span><span class="line"><span class="ln">6</span><span class="cl">- [ ] dart analyze 0 錯誤</span></span></code></pre></div><p><strong>效益</strong>：</p>
<ul>
<li>避免「看起來完成了」但實際未達標準</li>
<li>提供明確的驗收依據</li>
<li>減少 review 時的主觀爭議</li>
</ul>
<h4 id="目標-3可協作性collaborability">目標 3：可協作性（Collaborability）</h4>
<p><strong>定義</strong>：多個開發者可以並行執行不同的 Ticket，互不阻塞。</p>
<p><strong>實現方式</strong>：</p>
<ul>
<li>Ticket 拆分時考慮依賴關係，最小化依賴</li>
<li>明確標註 Ticket 間的依賴關係（必須先完成 / 可並行）</li>
<li>使用 Interface-Driven 開發，內層未完成也可開發外層</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookInfoEnrichmentService 介面（必須先完成）
</span></span><span class="line"><span class="ln">3</span><span class="cl">- Ticket <span class="ni">#3:</span> 撰寫 EnrichmentProgress 測試（可並行）</span></span></code></pre></div><p><strong>效益</strong>：</p>
<ul>
<li>多人可同時開發不同模組</li>
<li>減少等待時間</li>
<li>提升開發效率</li>
</ul>
<h3 id="13-基於-clean-architecture-的-ticket-設計哲學">1.3 基於 Clean Architecture 的 Ticket 設計哲學</h3>
<h4 id="clean-architecture-核心原則回顧">Clean Architecture 核心原則回顧</h4>
<p><strong>依賴方向規則</strong>：</p>
<ul>
<li>外層依賴內層，內層不依賴外層</li>
<li>所有依賴都指向內層（Domain）</li>
</ul>
<p><strong>分層定義</strong>：</p>
<ul>
<li><strong>Entities（Domain）</strong>：核心業務邏輯</li>
<li><strong>Use Cases（Application）</strong>：應用業務規則</li>
<li><strong>Interface Adapters（Presentation/Infrastructure）</strong>：轉接層</li>
<li><strong>Frameworks &amp; Drivers（External）</strong>：外部實作</li>
</ul>
<h4 id="ticket-設計對應-clean-architecture">Ticket 設計對應 Clean Architecture</h4>
<h5 id="原則-1interface-定義優先於具體實作">原則 1：Interface 定義優先於具體實作</h5>
<p>每個功能模組的開發順序：</p>
<ol>
<li><strong>先定義 Interface</strong>（Ticket 類型：Interface 定義）</li>
<li><strong>再實作具體邏輯</strong>（Ticket 類型：具體實作）</li>
</ol>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Ticket #1: 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">Ticket #2: 實作 SQLiteBookRepository（依賴 #1）</span></span></code></pre></div><p><strong>好處</strong>：</p>
<ul>
<li>外層可依賴 Interface 先行開發（使用 Mock）</li>
<li>內層實作延後，不阻塞外層開發</li>
<li>符合 Dependency Inversion Principle</li>
</ul>
<h6 id="原則-2測試驅動-ticket-拆分">原則 2：測試驅動 Ticket 拆分</h6>
<p>每個實作 Ticket 都應該有對應的測試 Ticket：</p>
<ol>
<li><strong>Interface 定義 Ticket</strong> → 定義契約</li>
<li><strong>測試撰寫 Ticket</strong> → 驗證契約</li>
<li><strong>具體實作 Ticket</strong> → 實現契約</li>
<li><strong>整合驗證 Ticket</strong> → 確認整合</li>
</ol>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Ticket #1: 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">Ticket #2: 撰寫 BookRepository 測試
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">Ticket #3: 實作 SQLiteBookRepository
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">7</span><span class="cl">Ticket #4: 整合測試驗證</span></span></code></pre></div><p><strong>好處</strong>：</p>
<ul>
<li>測試先行，確保需求清晰</li>
<li>實作以測試為目標，不過度設計</li>
<li>整合驗證確保模組間正確協作</li>
</ul>
<h6 id="原則-3分層拆分-ticket">原則 3：分層拆分 Ticket</h6>
<p>基於 Clean Architecture 分層，將任務拆分為不同層次的 Ticket：</p>
<p><strong>Domain 層 Ticket</strong>：</p>
<ul>
<li>定義 Entity</li>
<li>定義 Value Object</li>
<li>定義 Domain Event</li>
<li>定義 Repository Interface</li>
</ul>
<p><strong>Application 層 Ticket</strong>：</p>
<ul>
<li>定義 Use Case Interface</li>
<li>實作 Use Case Interactor</li>
<li>定義 Input/Output Port</li>
</ul>
<p><strong>Infrastructure 層 Ticket</strong>：</p>
<ul>
<li>實作 Repository</li>
<li>實作 External Service</li>
<li>實作 Database Mapper</li>
</ul>
<p><strong>Presentation 層 Ticket</strong>：</p>
<ul>
<li>實作 Controller/Presenter</li>
<li>實作 ViewModel</li>
<li>實作 UI Component</li>
</ul>
<p><strong>好處</strong>：</p>
<ul>
<li>職責清晰，不跨層混合</li>
<li>符合 Clean Architecture 分層原則</li>
<li>易於並行開發</li>
</ul>
<h3 id="14-ticket-機制與-tdd-階段的關係">1.4 Ticket 機制與 TDD 階段的關係</h3>
<h4 id="tdd-階段回顧">TDD 階段回顧</h4>
<ul>
<li><strong>Phase 1</strong>：功能設計（設計 Interface、規劃架構）</li>
<li><strong>Phase 2</strong>：測試設計（撰寫測試案例）</li>
<li><strong>Phase 3</strong>：實作執行（實作功能、通過測試）</li>
<li><strong>Phase 4</strong>：重構優化（改善品質、消除技術債務）</li>
</ul>
<h4 id="ticket-與-tdd-階段對應">Ticket 與 TDD 階段對應</h4>
<p><strong>Phase 1 產出的 Ticket</strong>：</p>
<p>Phase 1 設計階段產出「Interface 定義 Ticket」：</p>
<ul>
<li>定義 Domain Interface</li>
<li>定義 Use Case Interface</li>
<li>定義 Input/Output Port</li>
</ul>
<p><strong>特徵</strong>：</p>
<ul>
<li>無具體實作，只有介面簽名</li>
<li>明確定義輸入輸出</li>
<li>建立契約規範</li>
</ul>
<p><strong>Phase 2 產出的 Ticket</strong>：</p>
<p>Phase 2 測試設計階段產出「測試撰寫 Ticket」：</p>
<ul>
<li>撰寫 Entity 測試</li>
<li>撰寫 Use Case 測試</li>
<li>撰寫 Repository 測試</li>
</ul>
<p><strong>特徵</strong>：</p>
<ul>
<li>測試先行，定義預期行為</li>
<li>覆蓋所有 Interface 方法</li>
<li>包含正常流程和異常處理</li>
</ul>
<p><strong>Phase 3 產出的 Ticket</strong>：</p>
<p>Phase 3 實作階段產出「具體實作 Ticket」：</p>
<ul>
<li>實作 Entity 邏輯</li>
<li>實作 Use Case Interactor</li>
<li>實作 Repository</li>
</ul>
<p><strong>特徵</strong>：</p>
<ul>
<li>以測試通過為目標</li>
<li>最小可行實作</li>
<li>100% 測試通過</li>
</ul>
<p><strong>Phase 4 產出的 Ticket</strong>：</p>
<p>Phase 4 重構階段產出「品質改善 Ticket」：</p>
<ul>
<li>重構複雜邏輯</li>
<li>消除程式異味</li>
<li>改善錯誤處理</li>
</ul>
<p><strong>特徵</strong>：</p>
<ul>
<li>保持測試通過</li>
<li>改善程式品質</li>
<li>不新增功能</li>
</ul>
<h4 id="ticket-機制支援-tdd-流程">Ticket 機制支援 TDD 流程</h4>
<h5 id="支援-1明確的階段產出">支援 1：明確的階段產出</h5>
<p>每個 TDD Phase 都產出對應的 Ticket 類型，職責清晰。</p>
<h6 id="支援-2可並行執行">支援 2：可並行執行</h6>
<p>Phase 1 完成後，多個開發者可並行執行 Phase 2-3 的不同 Ticket。</p>
<h6 id="支援-3即時-review">支援 3：即時 Review</h6>
<p>每完成一個 Ticket 觸發 review，確保品質。</p>
<hr>
<h2 id="第二章ticket-拆分標準">第二章：Ticket 拆分標準</h2>
<h3 id="21-量化指標定義">2.1 量化指標定義</h3>
<p>為了確保 Ticket 拆分的一致性和可操作性，定義以下 <strong>4 個量化指標</strong>。</p>
<h4 id="核心原則基於客觀的工作內容評估">核心原則**：**基於客觀的工作內容評估</h4>
<ul>
<li><strong>職責數量</strong>：最客觀，不受個人能力影響- <strong>程式碼行數</strong>：可量化，可驗證- <strong>檔案數量</strong>：架構層級指標- <strong>測試數量</strong>：品質保證指標</li>
</ul>
<hr>
<h4 id="指標-1職責數量responsibilities最優先">指標 1：職責數量（Responsibilities）（最優先）</h4>
<p><strong>定義</strong>：Ticket 需要完成的獨立職責數量。</p>
<p><strong>標準</strong>：</p>
<ul>
<li><strong>簡單 Ticket</strong>：1 個明確職責</li>
<li><strong>中等 Ticket</strong>：2-3 個相關職責</li>
<li><strong>複雜 Ticket</strong>：3-5 個相關職責</li>
<li><strong>超過 5 個職責</strong>：必須拆分</li>
</ul>
<p><strong>識別方式</strong>：</p>
<ul>
<li>每個「需要實作的功能點」算一個職責</li>
<li>每個「需要驗證的邊界條件」算一個職責</li>
<li>每個「需要處理的錯誤情境」算一個職責</li>
</ul>
<p><strong>範例</strong>：</p>
<ul>
<li><strong>簡單（1 職責）</strong>：定義 SelectionManager 介面方法簽名</li>
<li><strong>中等（2-3 職責）</strong>：
<ul>
<li>職責 1: 實作 toggleSelection 方法</li>
<li>職責 2: 實作 clearSelection 方法</li>
<li>職責 3: 通知狀態變更（ChangeNotifier）</li>
</ul>
</li>
<li><strong>複雜（3-5 職責）</strong>：
<ul>
<li>職責 1: Repository CRUD 實作</li>
<li>職責 2: Data Mapper 轉換</li>
<li>職責 3: 錯誤處理</li>
<li>職責 4: Cache 管理</li>
<li>職責 5: 單元測試</li>
</ul>
</li>
</ul>
<p><strong>為什麼職責是第一指標</strong>：</p>
<ul>
<li><strong>最客觀</strong>：不受個人能力影響- <strong>最穩定</strong>：不受環境和參考資料影響- <strong>最易溝通</strong>：PM 和工程師都能理解</li>
</ul>
<h4 id="指標-2程式碼行數lines-of-code">指標 2：程式碼行數（Lines of Code）</h4>
<p><strong>定義</strong>：Ticket 涉及的程式碼修改行數（新增 + 修改 + 刪除）。</p>
<p><strong>標準</strong>：</p>
<ul>
<li><strong>簡單 Ticket</strong>：&lt; 30 行</li>
<li><strong>中等 Ticket</strong>：30-50 行</li>
<li><strong>複雜 Ticket</strong>：50-100 行</li>
<li><strong>超過 100 行</strong>：必須拆分</li>
</ul>
<p><strong>測量方式</strong>：</p>
<ul>
<li>使用 <code>git diff --stat</code> 統計</li>
<li>包含新增、修改、刪除的行數總和</li>
<li>不包含空行和註解</li>
</ul>
<p><strong>範例</strong>：</p>
<ul>
<li>簡單：定義 Interface（~20 行）</li>
<li>中等：實作 Value Object（~40 行）</li>
<li>複雜：實作 Repository（~80 行）</li>
</ul>
<h4 id="指標-3涉及檔案數files">指標 3：涉及檔案數（Files）</h4>
<p><strong>定義</strong>：Ticket 需要修改的檔案數量。</p>
<p><strong>標準</strong>：</p>
<ul>
<li><strong>簡單 Ticket</strong>：1 個檔案</li>
<li><strong>中等 Ticket</strong>：2-3 個檔案</li>
<li><strong>複雜 Ticket</strong>：3-5 個檔案</li>
<li><strong>超過 5 個檔案</strong>：必須拆分</li>
</ul>
<p><strong>測量方式</strong>：</p>
<ul>
<li>計算 <code>git diff --name-only</code> 的檔案數</li>
<li>包含新建檔案</li>
<li>不包含測試檔案（測試檔案另計）</li>
</ul>
<p><strong>範例</strong>：</p>
<ul>
<li>簡單：新建一個 Interface 檔案（1 個）</li>
<li>中等：修改 Entity 和 Repository Interface（2 個）</li>
<li>複雜：實作 Use Case，修改 Interactor、Input Port、Output Port（3 個）</li>
</ul>
<h4 id="指標-4測試用例數tests">指標 4：測試用例數（Tests）</h4>
<p><strong>定義</strong>：Ticket 對應的測試用例數量。</p>
<p><strong>標準</strong>：</p>
<ul>
<li><strong>簡單 Ticket</strong>：1-3 個測試</li>
<li><strong>中等 Ticket</strong>：3-5 個測試</li>
<li><strong>複雜 Ticket</strong>：5-10 個測試</li>
<li><strong>超過 10 個測試</strong>：必須拆分</li>
</ul>
<p><strong>測量方式</strong>：</p>
<ul>
<li>計算 test 檔案中的測試方法數</li>
<li>包含單元測試和整合測試</li>
<li>每個 <code>test('...', () {...})</code> 算一個</li>
</ul>
<p><strong>範例</strong>：</p>
<ul>
<li>簡單：測試 Value Object 的建立和驗證（2 個測試）</li>
<li>中等：測試 Repository 的 CRUD 操作（4 個測試）</li>
<li>複雜：測試 Use Case 的正常流程和異常處理（8 個測試）</li>
</ul>
<hr>
<h3 id="22-任務複雜度評估方法">2.2 任務複雜度評估方法</h3>
<p>基於 4 個量化指標，定義任務複雜度評估方法：</p>
<p><strong>複雜度等級定義</strong>：</p>
<table>
  <thead>
      <tr>
          <th>等級</th>
          <th>職責</th>
          <th>檔案</th>
          <th>測試</th>
          <th>行數</th>
          <th>描述</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>簡單</strong></td>
          <td>1 個</td>
          <td>1 個</td>
          <td>1-3 個</td>
          <td>&lt; 30 行</td>
          <td>單一職責，單一檔案</td>
      </tr>
      <tr>
          <td><strong>中等</strong></td>
          <td>2-3 個</td>
          <td>2-3 個</td>
          <td>3-6 個</td>
          <td>30-70 行</td>
          <td>少數職責，少數檔案</td>
      </tr>
      <tr>
          <td><strong>複雜</strong></td>
          <td>3-5 個</td>
          <td>3-5 個</td>
          <td>6-10 個</td>
          <td>70-100 行</td>
          <td>多職責，多檔案</td>
      </tr>
      <tr>
          <td><strong>需拆分</strong></td>
          <td>&gt; 5 個</td>
          <td>&gt; 5 個</td>
          <td>&gt; 10 個</td>
          <td>&gt; 100 行</td>
          <td>任一指標超標必須拆分</td>
      </tr>
  </tbody>
</table>
<p><strong>評估方法</strong>：</p>
<h4 id="步驟-1初步評估">步驟 1：初步評估</h4>
<ul>
<li>根據任務描述，估算 4 個指標</li>
<li>取最高的複雜度等級作為初步評估結果</li>
</ul>
<h5 id="步驟-2複雜度確認">步驟 2：複雜度確認</h5>
<ul>
<li>如果初步評估為「需拆分」，必須拆分</li>
<li>如果初步評估為「複雜」，檢查是否可拆分為「簡單」或「中等」</li>
<li>如果初步評估為「中等」或「簡單」，可直接建立 Ticket</li>
</ul>
<h6 id="步驟-3拆分決策">步驟 3：拆分決策</h6>
<ul>
<li>優先拆分為「簡單」Ticket（單一職責、單一檔案）</li>
<li>無法拆分為「簡單」時，拆分為「中等」Ticket（2-3 職責）</li>
<li>避免建立「複雜」Ticket（3-5 職責），除非無法再拆分</li>
</ul>
<h3 id="23-基於-clean-architecture-分層的拆分策略">2.3 基於 Clean Architecture 分層的拆分策略</h3>
<p>基於 Clean Architecture 的分層原則，定義 4 種標準拆分策略：</p>
<h4 id="策略-1interface-定義-ticket">策略 1：Interface 定義 Ticket</h4>
<p><strong>目的</strong>：定義一個介面及其輸入輸出契約。</p>
<p><strong>範圍</strong>：</p>
<ul>
<li>定義 Interface 簽名</li>
<li>定義輸入參數類型</li>
<li>定義回傳類型</li>
<li>撰寫文檔註解</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #1: 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IBookRepository`</span> 介面，定義書籍資料存取的契約
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domains/library/repositories/`</span> 建立 <span class="sb">`i_book_repository.dart`</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`getBookByIsbn`</span> 方法簽名
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`saveBook`</span> 方法簽名
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">4.</span> 定義 <span class="sb">`deleteBook`</span> 方法簽名
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">5.</span> 撰寫文檔註解
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Interface 檔案建立在正確位置
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">- [ ]</span> 3 個方法簽名完整且明確
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 輸入輸出類型定義清楚
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 包含完整的文檔註解
</span></span><span class="line"><span class="ln">18</span><span class="cl">- [ ] dart analyze 0 錯誤</span></span></code></pre></div><h4 id="策略-2具體實作-ticket">策略 2：具體實作 Ticket</h4>
<p><strong>目的</strong>：實作一個類別的核心邏輯。</p>
<p><strong>範圍</strong>：</p>
<ul>
<li>實作類別邏輯</li>
<li>實現介面方法</li>
<li>處理異常</li>
<li>確保測試通過</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #2: 實作 SQLiteBookRepository
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`SQLiteBookRepository`</span>，提供書籍資料的 SQLite 儲存
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`SQLiteBookRepository`</span> 類別
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 實作 <span class="sb">`getBookByIsbn`</span> 方法
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`saveBook`</span> 方法
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">4.</span> 實作 <span class="sb">`deleteBook`</span> 方法
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">5.</span> 處理 SQLite 異常
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">6.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 實作所有 IBookRepository 方法
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整（Database Exception）
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu"></span>- Ticket <span class="ni">#1:</span> 定義 IBookRepository 介面（必須先完成）</span></span></code></pre></div><h4 id="策略-3測試驗證-ticket">策略 3：測試驗證 Ticket</h4>
<p><strong>目的</strong>：撰寫一組相關的測試用例。</p>
<p><strong>範圍</strong>：</p>
<ul>
<li>撰寫單元測試</li>
<li>覆蓋正常流程</li>
<li>覆蓋異常處理</li>
<li>確保測試通過</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #3: 撰寫 BookRepository 測試
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>撰寫 <span class="sb">`BookRepository`</span> 的完整測試用例
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立測試檔案 <span class="sb">`book_repository_test.dart`</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 撰寫 <span class="sb">`getBookByIsbn`</span> 測試（正常流程 + 不存在情況）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 撰寫 <span class="sb">`saveBook`</span> 測試（新增 + 更新）
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">4.</span> 撰寫 <span class="sb">`deleteBook`</span> 測試（存在 + 不存在）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 測試檔案建立在 <span class="sb">`test/unit/domains/library/repositories/`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">- [ ]</span> 至少 6 個測試用例
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 覆蓋正常流程和異常處理
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 所有測試 100% 通過
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span>- Ticket <span class="ni">#1:</span> 定義 IBookRepository 介面（必須先完成）</span></span></code></pre></div><h4 id="策略-4整合連接-ticket">策略 4：整合連接 Ticket</h4>
<p><strong>目的</strong>：連接兩個模組並驗證整合。</p>
<p><strong>範圍</strong>：</p>
<ul>
<li>連接 Use Case 和 Repository</li>
<li>實作依賴注入</li>
<li>撰寫整合測試</li>
<li>驗證端到端流程</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #4: 整合 BookRepository 到 GetBookUseCase
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>將 <span class="sb">`BookRepository`</span> 整合到 <span class="sb">`GetBookUseCase`</span>，實現完整流程
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`GetBookInteractor`</span> 注入 <span class="sb">`IBookRepository`</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 在 <span class="sb">`execute`</span> 方法中呼叫 <span class="sb">`repository.getBookByIsbn`</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 處理 Repository 異常
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">4.</span> 撰寫整合測試驗證端到端流程
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> <span class="sb">`GetBookInteractor`</span> 正確注入 <span class="sb">`IBookRepository`</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">- [ ]</span> 端到端流程正常運作
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 整合測試 100% 通過
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span>- Ticket <span class="ni">#2:</span> 實作 SQLiteBookRepository（必須先完成）</span></span></code></pre></div><h3 id="24-code-smell-品質閘門檢測">2.4 Code Smell 品質閘門檢測</h3>
<p>本節整合 v0.12.G.2「Code Smell 檢查清單」到 Ticket 設計流程，實現「設計階段就能發現 Code Smell」的品質管理策略。</p>
<p><strong>整合目標</strong>:</p>
<ul>
<li>在 Ticket 設計階段執行 Code Smell 檢測</li>
<li>降低修正成本（設計階段 vs 實作後修正）</li>
<li>提供明確的 Ticket 設計品質標準</li>
<li>實現預防勝於治療的品質管理</li>
</ul>
<p><strong>引用方法論</strong>:</p>
<ul>
<li>引用 v0.12.G.1「層級隔離派工方法論」的五層架構和單層修改原則</li>
<li>引用 v0.12.G.2「Code Smell 檢查清單」的 C 類 Code Smell 檢測方法</li>
</ul>
<hr>
<h4 id="241-品質閘門機制概述">2.4.1 品質閘門機制概述</h4>
<p><strong>品質閘門定義</strong>:</p>
<p>品質閘門（Quality Gate）是 Ticket 設計階段的強制檢查點，確保 Ticket 符合 Code Smell 品質標準後才能進入執行階段。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket 設計流程整合品質閘門：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Phase 1: 功能設計（lavender-interface-designer）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  1. 撰寫 Ticket 清單
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  2. 定義驗收條件
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  3. 規劃步驟和檔案
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">【品質閘門檢測】← 新增檢測點
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ C1. God Ticket 檢測
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├─ C2. Incomplete Ticket 檢測
</span></span><span class="line"><span class="ln">12</span><span class="cl">  └─ C3. Ambiguous Responsibility 檢測
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">14</span><span class="cl">  通過 → Phase 2: 測試驗證
</span></span><span class="line"><span class="ln">15</span><span class="cl">  未通過 → 修正 Ticket → 重新檢測</span></span></code></pre></div><p><strong>檢測時機</strong>:</p>
<table>
  <thead>
      <tr>
          <th>檢測時機</th>
          <th>說明</th>
          <th>執行者</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Ticket 撰寫完成後</strong></td>
          <td>所有 Ticket 的內容（背景、目標、步驟、驗收條件）都已撰寫完成</td>
          <td>lavender-interface-designer</td>
      </tr>
      <tr>
          <td><strong>分派執行前</strong></td>
          <td>在將 Ticket 分派給開發者執行前，必須先通過品質閘門</td>
          <td>lavender-interface-designer</td>
      </tr>
      <tr>
          <td><strong>PM 審查前</strong></td>
          <td>PM 審查時，品質閘門檢測報告是重要參考依據</td>
          <td>lavender-interface-designer</td>
      </tr>
  </tbody>
</table>
<p><strong>檢測執行者</strong>:</p>
<p><strong>責任歸屬</strong>: lavender-interface-designer（TDD Phase 1 功能設計專家）</p>
<p><strong>檢測職責</strong>:</p>
<ol>
<li>對每個 Ticket 執行 C1/C2/C3 檢測</li>
<li>記錄檢測結果到工作日誌</li>
<li>檢測失敗時執行修正</li>
<li>提供品質閘門報告給 PM</li>
</ol>
<p><strong>阻斷機制</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">檢測結果處理：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">所有 Ticket 都通過 C1/C2/C3？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ Yes → 提交給 PM 審查
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │           └─ PM 批准 → 分派給開發者執行
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  └─ No → 阻止進入 Phase 2
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">              ├─ 記錄檢測失敗原因
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">              ├─ 執行修正（拆分/補充/重新定義）
</span></span><span class="line"><span class="ln">10</span><span class="cl">              └─ 重新檢測（直到通過）</span></span></code></pre></div><p><strong>阻斷原則</strong>:</p>
<ul>
<li><strong>強制阻斷</strong>: 任何 Ticket 未通過 C1/C2/C3 檢測，禁止進入 Phase 2</li>
<li><strong>修正優先</strong>: 發現問題後立即修正，不延後處理</li>
<li><strong>完整記錄</strong>: 所有檢測過程和修正過程都記錄到工作日誌</li>
</ul>
<p><strong>品質閘門價值</strong>:</p>
<table>
  <thead>
      <tr>
          <th>價值維度</th>
          <th>說明</th>
          <th>效益</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>及早發現</strong></td>
          <td>設計階段就能發現 Code Smell</td>
          <td>修正成本降低 80%</td>
      </tr>
      <tr>
          <td><strong>標準化</strong></td>
          <td>提供統一的檢測標準</td>
          <td>避免主觀判斷</td>
      </tr>
      <tr>
          <td><strong>可追溯</strong></td>
          <td>完整的檢測記錄</td>
          <td>提升品質可見性</td>
      </tr>
      <tr>
          <td><strong>預防性</strong></td>
          <td>預防問題進入實作階段</td>
          <td>減少返工時間</td>
      </tr>
  </tbody>
</table>
<hr>
<h4 id="242-c1-god-ticket-檢測">2.4.2 C1. God Ticket 檢測</h4>
<p><strong>定義</strong>（引用 v0.12.G.2 第 2.3.1 節 God Ticket 定義）:</p>
<p>God Ticket 是指單一 Ticket 修改過多檔案和層級，範圍失控，違反「單層修改原則」（引用 v0.12.G.1 第 3.1 節）。</p>
<p><strong>量化檢測指標</strong>:</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>良好設計</th>
          <th>需要檢查</th>
          <th>God Ticket（必須拆分）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>檔案數量</strong></td>
          <td>1-3 個（合格）</td>
          <td>4-6 個（需檢查）</td>
          <td><strong>&gt; 10 個</strong>（超標）</td>
      </tr>
      <tr>
          <td><strong>層級跨度</strong></td>
          <td>1 層（合格）</td>
          <td>2 層（需檢查）</td>
          <td><strong>&gt; 2 層</strong>（超標）</td>
      </tr>
      <tr>
          <td><strong>預估工時</strong></td>
          <td>2-4 小時（合格）</td>
          <td>4-8 小時（需檢查）</td>
          <td><strong>&gt; 16 小時</strong>（超標）</td>
      </tr>
  </tbody>
</table>
<p><strong>組合邏輯</strong>（Phase 2 補充說明）:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">判斷標準：任一項目超標 = God Ticket（需要拆分）
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">- 檔案數 = 8 個（未超標）、層級跨度 = 3 層（超標） → God Ticket
</span></span><span class="line"><span class="ln">6</span><span class="cl">- 檔案數 = 12 個（超標）、層級跨度 = 1 層（未超標） → God Ticket
</span></span><span class="line"><span class="ln">7</span><span class="cl">- 檔案數 = 5 個（未超標）、層級跨度 = 2 層（未超標）、工時 = 20 小時（超標） → God Ticket
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">只要有任何一個指標超標，就判定為 God Ticket，必須拆分。</span></span></code></pre></div><p><strong>檢測方法（Ticket 設計階段）</strong>:</p>
<h5 id="步驟-1-列出-ticket-涉及的檔案清單">步驟 1: 列出 Ticket 涉及的檔案清單</h5>
<p>從 Ticket 的「步驟」章節中提取所有檔案路徑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">範例 Ticket 步驟：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">1.</span> 建立 Rating Value Object（<span class="sb">`lib/domain/value_objects/rating_value.dart`</span>）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">2.</span> 建立 Rating Entity（<span class="sb">`lib/domain/entities/rating.dart`</span>）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">3.</span> 更新 Book Entity（<span class="sb">`lib/domain/entities/book.dart`</span>）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">4.</span> 定義 IRatingRepository（<span class="sb">`lib/domain/repositories/i_rating_repository.dart`</span>）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">...
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">提取檔案清單：
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> lib/domain/value_objects/rating_value.dart
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> lib/domain/entities/rating.dart
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> lib/domain/entities/book.dart
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> lib/domain/repositories/i_rating_repository.dart
</span></span><span class="line"><span class="ln">15</span><span class="cl">...</span></span></code></pre></div><h6 id="步驟-2-計算檔案數量">步驟 2: 計算檔案數量</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">統計檔案數量（不包含測試檔案）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├─ 1-3 個 → 良好設計
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ├─ 4-6 個 → 需要檢查（評估是否可拆分）
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ├─ 7-10 個 → 建議拆分
</span></span><span class="line"><span class="ln">5</span><span class="cl">  └─ &gt; 10 個 → God Ticket（強制拆分）</span></span></code></pre></div><h6 id="步驟-3-判斷層級跨度">步驟 3: 判斷層級跨度</h6>
<p>使用 v0.12.G.1 第 6.2 節「檔案路徑分析法」判斷每個檔案所屬層級：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">檔案路徑 → 層級對應規則：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Layer 1（UI）:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- lib/presentation/widgets/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- lib/presentation/pages/
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Layer 2（Behavior）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">- lib/presentation/controllers/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">- lib/presentation/providers/
</span></span><span class="line"><span class="ln">10</span><span class="cl">- lib/presentation/view_models/
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">Layer 3（UseCase）:
</span></span><span class="line"><span class="ln">13</span><span class="cl">- lib/application/use_cases/
</span></span><span class="line"><span class="ln">14</span><span class="cl">- lib/application/services/
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">Layer 4（Domain Events/Interfaces）:
</span></span><span class="line"><span class="ln">17</span><span class="cl">- lib/domain/events/
</span></span><span class="line"><span class="ln">18</span><span class="cl">- lib/domain/repositories/ (介面)
</span></span><span class="line"><span class="ln">19</span><span class="cl">- lib/domain/services/ (介面)
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">Layer 5（Domain Implementation）:
</span></span><span class="line"><span class="ln">22</span><span class="cl">- lib/domain/entities/
</span></span><span class="line"><span class="ln">23</span><span class="cl">- lib/domain/value_objects/
</span></span><span class="line"><span class="ln">24</span><span class="cl">- lib/infrastructure/repositories/ (實作)
</span></span><span class="line"><span class="ln">25</span><span class="cl">- lib/infrastructure/services/ (實作)</span></span></code></pre></div><p>計算層級跨度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">2</span><span class="cl">檔案清單：
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">- lib/presentation/widgets/rating_widget.dart → Layer 1
</span></span><span class="line"><span class="ln">5</span><span class="cl">- lib/presentation/controllers/rating_controller.dart → Layer 2
</span></span><span class="line"><span class="ln">6</span><span class="cl">- lib/application/use_cases/rate_book_use_case.dart → Layer 3
</span></span><span class="line"><span class="ln">7</span><span class="cl">- lib/domain/entities/rating.dart → Layer 5
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">層級跨度 = max(5) - min(1) = 4 層（超標）→ God Ticket</span></span></code></pre></div><p>判斷標準：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">  ├─ 1 層（單層修改）→ 良好設計
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├─ 2 層（Facade 整合）→ 需要檢查（可能可接受）
</span></span><span class="line"><span class="ln">3</span><span class="cl">  └─ &gt; 2 層（跨多層修改）→ God Ticket（強制拆分）</span></span></code></pre></div><h6 id="步驟-4-評估預估工時">步驟 4: 評估預估工時</h6>
<p>根據 Ticket 的「步驟」章節複雜度評估：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">評估依據：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">1. 步驟數量：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ├─ &lt; 10 項 → 簡單任務（2-4 小時）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ├─ 10-20 項 → 中等任務（4-8 小時）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   └─ &gt; 20 項 → 複雜任務（&gt; 8 小時）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">2. 步驟複雜度：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ├─ 單純檔案建立或方法定義 → 簡單（x1.0 係數）
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ├─ 包含邏輯實作和測試 → 中等（x1.5 係數）
</span></span><span class="line"><span class="ln">11</span><span class="cl">   └─ 包含多層整合和異常處理 → 複雜（x2.0 係數）
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">3. 計算公式：
</span></span><span class="line"><span class="ln">14</span><span class="cl">   預估工時 = 步驟數量 × 平均每步驟時間 × 複雜度係數
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">範例：
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 15 個步驟 × 30 分鐘 × 1.5 係數 = 11.25 小時（需檢查）
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 25 個步驟 × 30 分鐘 × 2.0 係數 = 25 小時（超標）→ God Ticket</span></span></code></pre></div><p>判斷標準：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">  ├─ 2-4 小時 → 良好設計
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├─ 4-8 小時 → 需要檢查
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ├─ 8-16 小時 → 建議拆分
</span></span><span class="line"><span class="ln">4</span><span class="cl">  └─ &gt; 16 小時 → God Ticket（強制拆分）</span></span></code></pre></div><p><strong>檢測失敗處理</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">God Ticket 檢測失敗 → 執行拆分策略
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1: 選擇拆分策略
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  優先策略: 按層級拆分（引用 v0.12.G.1 第 3.1 節單層修改原則）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  次要策略: 按職責拆分
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  最終策略: 按功能模組拆分
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">步驟 2: 執行拆分（引用 v0.12.G.1 第 5.4 節 Ticket 拆分指引）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  範例：14 個檔案、4 層 → 拆分為 5 個 Ticket（每層級 1 個）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 3: 重新檢測
</span></span><span class="line"><span class="ln">12</span><span class="cl">  對拆分後的每個 Ticket 重新執行 C1 檢測
</span></span><span class="line"><span class="ln">13</span><span class="cl">  確保所有 Ticket 都通過標準</span></span></code></pre></div><p><strong>拆分策略決策樹</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">God Ticket 拆分決策：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檔案數量 &gt; 10 或 層級跨度 &gt; 2？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  └─ Yes → 按層級拆分（優先）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            ├─ Layer 5: Domain 層 Ticket
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            ├─ Layer 4: Domain 介面層 Ticket
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            ├─ Layer 3: UseCase 層 Ticket
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            ├─ Layer 2: Behavior 層 Ticket
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            └─ Layer 1: UI 層 Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">預估工時 &gt; 16 小時？
</span></span><span class="line"><span class="ln">12</span><span class="cl">  └─ Yes → 按職責拆分（次要）
</span></span><span class="line"><span class="ln">13</span><span class="cl">            ├─ 職責 1: 資料建模 Ticket
</span></span><span class="line"><span class="ln">14</span><span class="cl">            ├─ 職責 2: 業務邏輯 Ticket
</span></span><span class="line"><span class="ln">15</span><span class="cl">            ├─ 職責 3: 介面整合 Ticket
</span></span><span class="line"><span class="ln">16</span><span class="cl">            └─ 職責 4: UI 呈現 Ticket
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">職責數量 &gt; 5？
</span></span><span class="line"><span class="ln">19</span><span class="cl">  └─ Yes → 按功能模組拆分（最終）
</span></span><span class="line"><span class="ln">20</span><span class="cl">            ├─ 模組 A: 核心功能 Ticket
</span></span><span class="line"><span class="ln">21</span><span class="cl">            ├─ 模組 B: 輔助功能 Ticket
</span></span><span class="line"><span class="ln">22</span><span class="cl">            └─ 模組 C: 整合驗證 Ticket</span></span></code></pre></div><p><strong>檢測範例（完整流程）</strong>:</p>
<p><strong>原始 Ticket（違反 C1 標準）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket: 新增「書籍評分」完整功能
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>根據 UC-03 需求，需要新增書籍評分功能。
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>實作書籍評分的完整功能，包含 UI、邏輯、資料儲存。
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 Rating Value Object（<span class="sb">`lib/domain/value_objects/rating_value.dart`</span>）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 建立 Rating Entity（<span class="sb">`lib/domain/entities/rating.dart`</span>）
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 更新 Book Entity（<span class="sb">`lib/domain/entities/book.dart`</span>）
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 定義 IRatingRepository（<span class="sb">`lib/domain/repositories/i_rating_repository.dart`</span>）
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">5.</span> 實作 RatingRepositoryImpl（<span class="sb">`lib/infrastructure/repositories/rating_repository_impl.dart`</span>）
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">6.</span> 建立 Rating 資料表（<span class="sb">`lib/infrastructure/database/rating_table.dart`</span>）
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">7.</span> 定義 RateBookUseCase 介面（<span class="sb">`lib/application/use_cases/i_rate_book_use_case.dart`</span>）
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">8.</span> 實作 RateBookUseCase（<span class="sb">`lib/application/use_cases/rate_book_use_case.dart`</span>）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">9.</span> 定義 GetBookRatingUseCase 介面（<span class="sb">`lib/application/use_cases/i_get_book_rating_use_case.dart`</span>）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">10.</span> 實作 GetBookRatingUseCase（<span class="sb">`lib/application/use_cases/get_book_rating_use_case.dart`</span>）
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">11.</span> 更新 BookDetailController（<span class="sb">`lib/presentation/controllers/book_detail_controller.dart`</span>）
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">12.</span> 建立 RatingController（<span class="sb">`lib/presentation/controllers/rating_controller.dart`</span>）
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">13.</span> 建立 RatingWidget（<span class="sb">`lib/presentation/widgets/rating_widget.dart`</span>）
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">14.</span> 更新 BookDetailWidget（<span class="sb">`lib/presentation/widgets/book_detail_widget.dart`</span>）
</span></span><span class="line"><span class="ln">24</span><span class="cl">15. 撰寫測試</span></span></code></pre></div><p><strong>C1 檢測執行</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 列出檔案清單
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  檔案數量 = 14 個
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">步驟 2: 計算檔案數量
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  14 個 &gt; 10 個 → God Ticket
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 3: 判斷層級跨度
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  檔案層級分布：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - Layer 5: rating_value.dart, rating.dart, book.dart (3 個)
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - Layer 5: rating_repository_impl.dart, rating_table.dart (2 個)
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - Layer 4: i_rating_repository.dart (1 個)
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - Layer 3: i_rate_book_use_case.dart, rate_book_use_case.dart (2 個)
</span></span><span class="line"><span class="ln">13</span><span class="cl">  - Layer 3: i_get_book_rating_use_case.dart, get_book_rating_use_case.dart (2 個)
</span></span><span class="line"><span class="ln">14</span><span class="cl">  - Layer 2: book_detail_controller.dart, rating_controller.dart (2 個)
</span></span><span class="line"><span class="ln">15</span><span class="cl">  - Layer 1: rating_widget.dart, book_detail_widget.dart (2 個)
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">  層級跨度 = 5 - 1 = 4 層（超標）→ God Ticket
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">步驟 4: 評估預估工時
</span></span><span class="line"><span class="ln">20</span><span class="cl">  步驟數量 = 15 項
</span></span><span class="line"><span class="ln">21</span><span class="cl">  複雜度 = 包含多層整合（x2.0 係數）
</span></span><span class="line"><span class="ln">22</span><span class="cl">  預估工時 = 15 × 30 分鐘 × 2.0 = 15 小時（需檢查）
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">結論: 檔案數量和層級跨度都超標 → God Ticket（必須拆分）</span></span></code></pre></div><p><strong>拆分策略執行</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">選擇策略: 按層級拆分（優先策略）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">拆分結果（5 個 Ticket）:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Ticket 1 [Layer 5]: Rating Domain 模型
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - rating_value.dart
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  - rating.dart
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - book.dart（新增 rating 欄位）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">Ticket 2 [Layer 5 + 4]: Rating Repository
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - i_rating_repository.dart（介面）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - rating_repository_impl.dart（實作）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  - rating_table.dart（資料表）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">Ticket 3 [Layer 3]: RateBook UseCase
</span></span><span class="line"><span class="ln">16</span><span class="cl">  - i_rate_book_use_case.dart（介面）
</span></span><span class="line"><span class="ln">17</span><span class="cl">  - rate_book_use_case.dart（實作）
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">Ticket 4 [Layer 3]: GetBookRating UseCase
</span></span><span class="line"><span class="ln">20</span><span class="cl">  - i_get_book_rating_use_case.dart（介面）
</span></span><span class="line"><span class="ln">21</span><span class="cl">  - get_book_rating_use_case.dart（實作）
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">Ticket 5 [Layer 2]: Rating Controller 整合
</span></span><span class="line"><span class="ln">24</span><span class="cl">  - book_detail_controller.dart（更新）
</span></span><span class="line"><span class="ln">25</span><span class="cl">  - rating_controller.dart（新建）
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">Ticket 6 [Layer 1]: Rating UI 元件
</span></span><span class="line"><span class="ln">28</span><span class="cl">  - rating_widget.dart（新建）
</span></span><span class="line"><span class="ln">29</span><span class="cl">  - book_detail_widget.dart（更新）</span></span></code></pre></div><p><strong>拆分後重新檢測</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket 1 [Layer 5]: Rating Domain 模型
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  檔案數量: 3 個（符合）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  層級跨度: 1 層（符合）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  預估工時: 3 小時（符合）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Ticket 2 [Layer 5 + 4]: Rating Repository
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  檔案數量: 3 個（符合）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  層級跨度: 2 層（需檢查）（Repository 介面和實作可接受）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  預估工時: 4 小時（符合）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Ticket 3 [Layer 3]: RateBook UseCase
</span></span><span class="line"><span class="ln">14</span><span class="cl">  檔案數量: 2 個（符合）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  層級跨度: 1 層（符合）
</span></span><span class="line"><span class="ln">16</span><span class="cl">  預估工時: 3 小時（符合）
</span></span><span class="line"><span class="ln">17</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">Ticket 4 [Layer 3]: GetBookRating UseCase
</span></span><span class="line"><span class="ln">20</span><span class="cl">  檔案數量: 2 個（符合）
</span></span><span class="line"><span class="ln">21</span><span class="cl">  層級跨度: 1 層（符合）
</span></span><span class="line"><span class="ln">22</span><span class="cl">  預估工時: 2.5 小時（符合）
</span></span><span class="line"><span class="ln">23</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">Ticket 5 [Layer 2]: Rating Controller 整合
</span></span><span class="line"><span class="ln">26</span><span class="cl">  檔案數量: 2 個（符合）
</span></span><span class="line"><span class="ln">27</span><span class="cl">  層級跨度: 1 層（符合）
</span></span><span class="line"><span class="ln">28</span><span class="cl">  預估工時: 3 小時（符合）
</span></span><span class="line"><span class="ln">29</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">Ticket 6 [Layer 1]: Rating UI 元件
</span></span><span class="line"><span class="ln">32</span><span class="cl">  檔案數量: 2 個（符合）
</span></span><span class="line"><span class="ln">33</span><span class="cl">  層級跨度: 1 層（符合）
</span></span><span class="line"><span class="ln">34</span><span class="cl">  預估工時: 4 小時（符合）
</span></span><span class="line"><span class="ln">35</span><span class="cl">  結論: 通過
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">最終結論: 所有 Ticket 都通過 C1 檢測</span></span></code></pre></div><p><strong>改善效果</strong>:</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>原始 Ticket</th>
          <th>拆分後（6 個 Ticket）</th>
          <th>改善</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>檔案數量</strong></td>
          <td>14 個（超標）</td>
          <td>2-3 個/Ticket（合格）</td>
          <td>符合標準</td>
      </tr>
      <tr>
          <td><strong>層級跨度</strong></td>
          <td>4 層（超標）</td>
          <td>1-2 層/Ticket（合格）</td>
          <td>符合單層修改原則</td>
      </tr>
      <tr>
          <td><strong>預估工時</strong></td>
          <td>15 小時（需檢查）</td>
          <td>2.5-4 小時/Ticket（合格）</td>
          <td>可在半天內完成</td>
      </tr>
      <tr>
          <td><strong>可並行執行</strong></td>
          <td>否</td>
          <td>是（Ticket 1-4 可並行）（合格）</td>
          <td>加速開發</td>
      </tr>
      <tr>
          <td><strong>風險控制</strong></td>
          <td>高風險</td>
          <td>低風險（合格）</td>
          <td>降低失敗影響</td>
      </tr>
  </tbody>
</table>
<hr>
<h4 id="243-c3-ambiguous-responsibility-檢測">2.4.3 C3. Ambiguous Responsibility 檢測</h4>
<p><strong>定義</strong>（引用 v0.12.G.2 第 2.3.3 節 Ambiguous Responsibility 定義）:</p>
<p>Ambiguous Responsibility 是指 Ticket 的職責定義不明確，無法判斷屬於哪一層級，違反「層級明確原則」。</p>
<p><strong>職責明確 Ticket 必要元素</strong>:</p>
<table>
  <thead>
      <tr>
          <th>必要元素</th>
          <th>檢查項目</th>
          <th>範例</th>
          <th>缺失後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>層級標示</strong></td>
          <td>標題包含 <code>[Layer X]</code> 標籤</td>
          <td><code>[Layer 2] 實作書籍詳情事件處理</code></td>
          <td>無法判斷責任範圍</td>
      </tr>
      <tr>
          <td><strong>職責描述</strong></td>
          <td>目標章節明確定義單一職責</td>
          <td>實作 BookDetailController 的 loadBookDetail 方法</td>
          <td>職責模糊</td>
      </tr>
      <tr>
          <td><strong>檔案範圍</strong></td>
          <td>步驟章節明確列出檔案路徑</td>
          <td>修改 <code>lib/presentation/controllers/book_detail_controller.dart</code></td>
          <td>影響範圍不明</td>
      </tr>
      <tr>
          <td><strong>驗收限定</strong></td>
          <td>驗收條件限定在該層級</td>
          <td>BookDetailController.loadBookDetail() 正確呼叫 UseCase</td>
          <td>驗收標準模糊</td>
      </tr>
  </tbody>
</table>
<p><strong>檢測方法（Ticket 設計階段）</strong>:</p>
<h5 id="步驟-1-檢查層級標示">步驟 1: 檢查層級標示</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">標題格式檢查：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  合格範例：
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  - [Layer 1] 建立書籍評分 UI 元件
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - [Layer 2] 實作書籍詳情 Controller 事件處理
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  - [Layer 3] 實作 GetBookDetail UseCase
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - [Layer 5] 定義 Book Entity 和 Value Objects
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  不合格範例：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 建立書籍評分功能（沒有層級標示）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 實作書籍詳情（層級不明確）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 整合評分功能（跨多層，職責模糊）</span></span></code></pre></div><h6 id="步驟-2-檢查職責描述">步驟 2: 檢查職責描述</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">目標章節檢查：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  合格範例：
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ### 目標
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  實作 BookDetailController 的 loadBookDetail 方法，
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  負責接收使用者事件並呼叫 GetBookDetailUseCase。
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  職責定義明確：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - 明確說明「做什麼」：實作 loadBookDetail 方法
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 明確說明「責任範圍」：接收事件 + 呼叫 UseCase
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 明確說明「不做什麼」：不包含業務邏輯實作
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">  不合格範例：
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ### 目標
</span></span><span class="line"><span class="ln">14</span><span class="cl">  實作書籍詳情功能
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">  職責定義模糊：
</span></span><span class="line"><span class="ln">17</span><span class="cl">  - 「書籍詳情功能」範圍太廣（UI? 邏輯? 資料？）
</span></span><span class="line"><span class="ln">18</span><span class="cl">  - 沒有說明具體職責
</span></span><span class="line"><span class="ln">19</span><span class="cl">  - 沒有說明責任邊界</span></span></code></pre></div><h4 id="步驟-3-檢查檔案範圍">步驟 3: 檢查檔案範圍</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟章節檢查：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  合格範例：
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ### 步驟
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  1. 修改 `lib/presentation/controllers/book_detail_controller.dart`
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  2. 新增 `loadBookDetail` 方法
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  3. 注入 `IGetBookDetailUseCase` 依賴
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  4. 實作事件處理邏輯
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  檔案範圍明確：
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 明確列出檔案路徑
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 避免籠統描述（如「修改相關檔案」）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - 檔案數量合理（1-3 個）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">  不合格範例：
</span></span><span class="line"><span class="ln">15</span><span class="cl">  ### 步驟
</span></span><span class="line"><span class="ln">16</span><span class="cl">  1. 修改書籍詳情相關檔案
</span></span><span class="line"><span class="ln">17</span><span class="cl">  2. 實作相關邏輯
</span></span><span class="line"><span class="ln">18</span><span class="cl">  3. 更新 UI
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">  檔案範圍模糊：
</span></span><span class="line"><span class="ln">21</span><span class="cl">  - 「相關檔案」沒有具體說明是哪些檔案
</span></span><span class="line"><span class="ln">22</span><span class="cl">  - 「相關邏輯」沒有說明在哪裡實作
</span></span><span class="line"><span class="ln">23</span><span class="cl">  - 「更新 UI」沒有說明哪個 UI 檔案</span></span></code></pre></div><h4 id="步驟-4-檢查驗收限定">步驟 4: 檢查驗收限定</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">驗收條件章節檢查：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  合格範例：
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ### 驗收條件
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  - [ ] BookDetailController.loadBookDetail() 方法建立
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  - [ ] 正確注入 IGetBookDetailUseCase 依賴
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  - [ ] loadBookDetail() 正確呼叫 UseCase.execute()
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  - [ ] 事件處理邏輯正確（不包含業務邏輯）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - [ ] dart analyze 0 錯誤
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">  驗收限定在 Layer 2（Behavior）：
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 只驗證 Controller 層的職責
</span></span><span class="line"><span class="ln">12</span><span class="cl">  - 不驗證 UI 呈現（Layer 1）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  - 不驗證業務邏輯（Layer 3）
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">  不合格範例：
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ### 驗收條件
</span></span><span class="line"><span class="ln">17</span><span class="cl">  - [ ] 書籍詳情功能正常運作
</span></span><span class="line"><span class="ln">18</span><span class="cl">  - [ ] 使用者可以看到書籍資訊
</span></span><span class="line"><span class="ln">19</span><span class="cl">  - [ ] 資料正確儲存
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">  驗收未限定在單一層級：
</span></span><span class="line"><span class="ln">22</span><span class="cl">  - 「功能正常運作」涵蓋所有層級
</span></span><span class="line"><span class="ln">23</span><span class="cl">  - 「使用者可以看到」是 UI 層（Layer 1）驗收
</span></span><span class="line"><span class="ln">24</span><span class="cl">  - 「資料正確儲存」是 Repository 層（Layer 5）驗收</span></span></code></pre></div><p><strong>檢測失敗處理</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">C3 檢測失敗 → 執行重新定義步驟
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">步驟 1: 明確層級定位
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  使用 v0.12.G.1 第 6.2 節檔案路徑分析法判斷層級
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  在標題加上 [Layer X] 標籤
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">步驟 2: 重新定義職責
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  基於該層級的職責範圍重寫目標章節
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  明確說明「做什麼」「責任範圍」「不做什麼」
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 3: 明確檔案範圍
</span></span><span class="line"><span class="ln">12</span><span class="cl">  列出具體的檔案路徑（避免籠統描述）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  確保檔案都屬於同一層級
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">步驟 4: 限定驗收條件
</span></span><span class="line"><span class="ln">16</span><span class="cl">  驗收條件只驗證該層級的職責
</span></span><span class="line"><span class="ln">17</span><span class="cl">  移除跨層級的驗收項目
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">步驟 5: 重新檢測
</span></span><span class="line"><span class="ln">20</span><span class="cl">  確認所有 4 個必要元素都已補充</span></span></code></pre></div><p><strong>檢測範例（完整流程）</strong>:</p>
<p><strong>原始 Ticket（違反 C3 標準）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket: 實作書籍詳情功能
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>根據 UC-02 需求，需要實作書籍詳情功能。
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>實作書籍詳情功能，讓使用者可以查看書籍的完整資訊。
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改相關檔案
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 實作查詢邏輯
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 更新 UI 顯示
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 使用者可以看到書籍詳情
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 資料正確顯示
</span></span><span class="line"><span class="ln">17</span><span class="cl">- [ ] 功能正常運作</span></span></code></pre></div><p><strong>C3 檢測執行</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查層級標示
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  標題: &#34;實作書籍詳情功能&#34;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  結果: 沒有 [Layer X] 標籤（不符合）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  問題: 無法判斷屬於哪一層級
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">步驟 2: 檢查職責描述
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  目標: &#34;實作書籍詳情功能，讓使用者可以查看書籍的完整資訊&#34;
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  結果: 職責定義模糊（不符合）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  問題:
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - &#34;書籍詳情功能&#34;範圍太廣（UI? 邏輯? 資料？）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  - 沒有明確說明職責範圍
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">步驟 3: 檢查檔案範圍
</span></span><span class="line"><span class="ln">14</span><span class="cl">  步驟: &#34;修改相關檔案&#34;
</span></span><span class="line"><span class="ln">15</span><span class="cl">  結果: 檔案範圍模糊（不符合）
</span></span><span class="line"><span class="ln">16</span><span class="cl">  問題:
</span></span><span class="line"><span class="ln">17</span><span class="cl">  - &#34;相關檔案&#34;沒有具體說明
</span></span><span class="line"><span class="ln">18</span><span class="cl">  - 沒有明確的檔案路徑
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">步驟 4: 檢查驗收限定
</span></span><span class="line"><span class="ln">21</span><span class="cl">  驗收條件: &#34;使用者可以看到書籍詳情&#34;、&#34;功能正常運作&#34;
</span></span><span class="line"><span class="ln">22</span><span class="cl">  結果: 驗收未限定在單一層級（不符合）
</span></span><span class="line"><span class="ln">23</span><span class="cl">  問題:
</span></span><span class="line"><span class="ln">24</span><span class="cl">  - &#34;使用者可以看到&#34;是 UI 層驗收
</span></span><span class="line"><span class="ln">25</span><span class="cl">  - &#34;功能正常運作&#34;涵蓋所有層級
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">結論: Ambiguous Responsibility（必須重新定義）</span></span></code></pre></div><p><strong>重新定義執行</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 明確層級定位
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  分析 Ticket 內容 → 主要涉及 Controller 事件處理
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  判斷層級 → Layer 2（Behavior）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  更新標題 → 加上 [Layer 2] 標籤
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">步驟 2: 重新定義職責
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  重寫目標章節：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - 明確說明「做什麼」
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 明確說明「責任範圍」
</span></span><span class="line"><span class="ln">10</span><span class="cl">  - 明確說明「不做什麼」
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">步驟 3: 明確檔案範圍
</span></span><span class="line"><span class="ln">13</span><span class="cl">  列出具體檔案路徑
</span></span><span class="line"><span class="ln">14</span><span class="cl">  確認都屬於 Layer 2
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 4: 限定驗收條件
</span></span><span class="line"><span class="ln">17</span><span class="cl">  只驗證 Layer 2 的職責
</span></span><span class="line"><span class="ln">18</span><span class="cl">  移除 UI 和業務邏輯驗收</span></span></code></pre></div><p><strong>修正後 Ticket（符合 C3 標準）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket: [Layer 2] 實作書籍詳情 Controller 事件處理
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>根據 UC-02 需求，需要實作 BookDetailController 的事件處理邏輯。
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>實作 BookDetailController 的 loadBookDetail 方法，
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">負責接收使用者的「查看書籍詳情」事件並呼叫 GetBookDetailUseCase。
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">職責範圍：
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 接收使用者事件（如按鈕點擊）
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 呼叫 GetBookDetailUseCase 獲取書籍資料
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> 將 UseCase 回傳的資料傳遞給 UI
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 不包含業務邏輯實作（業務邏輯在 UseCase 層）
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`lib/presentation/controllers/book_detail_controller.dart`</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">2.</span> 新增 <span class="sb">`loadBookDetail(String isbn)`</span> 方法
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">3.</span> 注入 <span class="sb">`IGetBookDetailUseCase`</span> 依賴
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">4.</span> 實作事件處理邏輯：
</span></span><span class="line"><span class="ln">22</span><span class="cl">   <span class="k">-</span> 呼叫 <span class="sb">`useCase.execute(isbn)`</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">   <span class="k">-</span> 處理回傳的 <span class="sb">`OperationResult&lt;Book&gt;`</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">   <span class="k">-</span> 更新 Controller 狀態（loading/success/error）
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">5.</span> 撰寫單元測試
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> BookDetailController.loadBookDetail() 方法建立在正確位置
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">- [ ]</span> 正確注入 IGetBookDetailUseCase 依賴
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">- [ ]</span> loadBookDetail() 正確呼叫 useCase.execute()
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">- [ ]</span> 正確處理 OperationResult（success/failure）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">- [ ]</span> 事件處理邏輯正確（不包含業務邏輯）
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">- [ ]</span> 單元測試覆蓋率 &gt; 80%
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#3:</span> 定義 IGetBookDetailUseCase 介面（必須先完成）
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu"></span><span class="k">-</span> docs/app-requirements-spec.md <span class="ni">#UC</span>-02
</span></span><span class="line"><span class="ln">41</span><span class="cl">- docs/work-logs/v0.12.8-design-decisions.md <span class="ni">#決策2</span></span></span></code></pre></div><p><strong>修正後重新檢測</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">步驟 1: 檢查層級標示
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  標題: &#34;[Layer 2] 實作書籍詳情 Controller 事件處理&#34;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  結果: 有 [Layer 2] 標籤（符合）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">步驟 2: 檢查職責描述
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  目標: 明確定義職責範圍和邊界
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  結果: 職責定義明確（符合）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  - 明確說明「接收事件」「呼叫 UseCase」
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  - 明確說明「不包含業務邏輯」
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">步驟 3: 檢查檔案範圍
</span></span><span class="line"><span class="ln">12</span><span class="cl">  步驟: 明確列出檔案路徑
</span></span><span class="line"><span class="ln">13</span><span class="cl">  結果: 檔案範圍明確（符合）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  - `lib/presentation/controllers/book_detail_controller.dart`
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">步驟 4: 檢查驗收限定
</span></span><span class="line"><span class="ln">17</span><span class="cl">  驗收條件: 限定在 Layer 2 職責
</span></span><span class="line"><span class="ln">18</span><span class="cl">  結果: 驗收限定正確（符合）
</span></span><span class="line"><span class="ln">19</span><span class="cl">  - 只驗證 Controller 層的職責
</span></span><span class="line"><span class="ln">20</span><span class="cl">  - 不包含 UI 或業務邏輯驗收
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">結論: 通過 C3 檢測</span></span></code></pre></div><p><strong>改善效果</strong>:</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>原始 Ticket</th>
          <th>修正後 Ticket</th>
          <th>改善</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>層級標示</strong></td>
          <td>無（不合格）</td>
          <td>[Layer 2]（合格）</td>
          <td>明確定位</td>
      </tr>
      <tr>
          <td><strong>職責描述</strong></td>
          <td>模糊（不合格）</td>
          <td>明確定義範圍和邊界（合格）</td>
          <td>職責清晰</td>
      </tr>
      <tr>
          <td><strong>檔案範圍</strong></td>
          <td>&ldquo;相關檔案&rdquo;（不合格）</td>
          <td>具體檔案路徑（合格）</td>
          <td>影響範圍明確</td>
      </tr>
      <tr>
          <td><strong>驗收限定</strong></td>
          <td>跨層級（不合格）</td>
          <td>限定在 Layer 2（合格）</td>
          <td>驗收標準清晰</td>
      </tr>
      <tr>
          <td><strong>可執行性</strong></td>
          <td>低（不合格）</td>
          <td>高（合格）</td>
          <td>開發者可直接執行</td>
      </tr>
  </tbody>
</table>
<hr>
<h4 id="244-c2-incomplete-ticket-檢測">2.4.4 C2. Incomplete Ticket 檢測</h4>
<p><strong>定義</strong>（引用 v0.12.G.2 第 2.3.3 節 Incomplete Ticket 定義）:</p>
<p>Incomplete Ticket 是指 Ticket 內容缺失關鍵元素，導致開發者無法明確理解需求、驗收標準或測試方法。</p>
<p><strong>必要元素檢測清單</strong>:</p>
<table>
  <thead>
      <tr>
          <th>元素</th>
          <th>檢查內容</th>
          <th>通過標準</th>
          <th>重要性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>驗收條件</strong></td>
          <td>是否有「### 驗收條件」章節</td>
          <td>至少 3 個可驗證的驗收項目</td>
          <td>必要</td>
      </tr>
      <tr>
          <td><strong>測試規劃</strong></td>
          <td>步驟中是否包含測試</td>
          <td>明確列出測試檔案和測試項目</td>
          <td>必要</td>
      </tr>
      <tr>
          <td><strong>工作日誌規劃</strong></td>
          <td>是否規劃工作日誌檔案</td>
          <td>明確工作日誌檔案名稱和記錄內容</td>
          <td>必要</td>
      </tr>
      <tr>
          <td><strong>參考文件</strong></td>
          <td>是否連結需求規格或設計文件</td>
          <td>至少 1 個有效的文件連結</td>
          <td>必要</td>
      </tr>
  </tbody>
</table>
<p><strong>檢測方法</strong>:</p>
<h5 id="步驟-1-檢查驗收條件完整性">步驟 1: 檢查驗收條件完整性</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">掃描 Ticket 內容
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查是否包含「### 驗收條件」章節？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ Yes → 檢查驗收項目數量
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │         ├─ ≥ 3 個 → 通過
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │         └─ &lt; 3 個 → 失敗（驗收條件不足）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  └─ No → 失敗（缺少驗收條件章節）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">驗收條件品質檢查：
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├─ 是否可量化驗證？（避免模糊描述）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 是否限定在該層級？（不跨層級驗收）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  └─ 是否涵蓋功能性、品質性、整合性？</span></span></code></pre></div><p><strong>範例</strong>:</p>
<p><strong>合格驗收條件</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> <span class="sb">`IBookRepository`</span> 介面檔案建立在 <span class="sb">`lib/domains/library/repositories/`</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">- [ ]</span> <span class="sb">`getBookByIsbn(String isbn)`</span> 方法簽名完整且明確
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">- [ ]</span> 方法包含完整的文檔註解（參數、回傳值、異常）
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">- [ ]</span> 測試覆蓋率 &gt; 80%
</span></span><span class="line"><span class="ln">7</span><span class="cl">- [ ] 不破壞既有功能（回歸測試通過）</span></span></code></pre></div><p><strong>不合格驗收條件</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 功能可以正常運作
</span></span><span class="line"><span class="ln">3</span><span class="cl">- [ ] 程式碼品質良好</span></span></code></pre></div><p>問題：驗收條件過於模糊，無法量化驗證</p>
<h4 id="步驟-2-檢查測試規劃完整性">步驟 2: 檢查測試規劃完整性</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">掃描「步驟」章節
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查是否包含測試相關步驟？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ Yes → 檢查測試內容
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │         ├─ 有測試檔案路徑（符合）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │         ├─ 有測試項目清單（符合）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │         ├─ 有測試覆蓋率要求（符合）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │         └─ 通過
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  │
</span></span><span class="line"><span class="ln">10</span><span class="cl">  └─ No → 失敗（缺少測試規劃）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">測試規劃品質檢查：
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ├─ 是否包含單元測試？
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ├─ 是否包含整合測試？（如需要）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  └─ 是否定義測試覆蓋率標準？</span></span></code></pre></div><p><strong>範例</strong>:</p>
<p><strong>合格測試規劃</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">4.</span> 撰寫 <span class="sb">`test/domain/repositories/book_repository_test.dart`</span> 單元測試
</span></span><span class="line"><span class="ln">3</span><span class="cl">   <span class="k">-</span> 測試 <span class="sb">`getBookByIsbn`</span> 正常流程（書籍存在）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   <span class="k">-</span> 測試書籍不存在異常處理
</span></span><span class="line"><span class="ln">5</span><span class="cl">   <span class="k">-</span> 測試 ISBN 格式錯誤異常處理
</span></span><span class="line"><span class="ln">6</span><span class="cl">   <span class="k">-</span> 測試網路錯誤異常處理
</span></span><span class="line"><span class="ln">7</span><span class="cl">5. 確保測試覆蓋率 &gt; 80%</span></span></code></pre></div><p><strong>不合格測試規劃</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>4. 撰寫測試</span></span></code></pre></div><p>問題：沒有明確測試檔案路徑和測試項目</p>
<h4 id="步驟-3-檢查工作日誌規劃">步驟 3: 檢查工作日誌規劃</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">掃描 Ticket 內容
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查是否包含「### 工作日誌」章節？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ Yes → 檢查檔案名稱
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │         ├─ 符合命名規範（符合）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │         │   格式: docs/work-logs/vX.Y.Z-feature-name.md
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │         └─ 通過
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ No → 失敗（缺少工作日誌規劃）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">命名規範檢查：
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 是否包含版本號？
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ├─ 是否描述性命名？
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ 是否在 docs/work-logs/ 目錄下？</span></span></code></pre></div><p><strong>範例</strong>:</p>
<p><strong>合格工作日誌規劃</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 工作日誌
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>檔案名稱: <span class="sb">`docs/work-logs/v0.12.8-book-repository-interface.md`</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">記錄內容:
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Ticket 執行過程
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 設計決策和理由
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> 遇到的問題和解決方法
</span></span><span class="line"><span class="ln">8</span><span class="cl">- 測試結果和覆蓋率報告</span></span></code></pre></div><p><strong>不合格工作日誌規劃</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">（沒有工作日誌規劃）</span></span></code></pre></div><h4 id="步驟-4-檢查參考文件連結">步驟 4: 檢查參考文件連結</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">掃描 Ticket 內容
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查是否包含「### 參考文件」章節？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ Yes → 檢查連結有效性
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │         ├─ 至少 1 個連結（符合）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │         ├─ 連結格式正確（符合）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │         └─ 連結內容相關（符合）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ No → 失敗（缺少參考文件）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">連結品質檢查：
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 是否連結需求規格？（建議）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ├─ 是否連結設計文件？（建議）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  └─ 是否連結相關 Ticket？（如有依賴）</span></span></code></pre></div><p><strong>範例</strong>:</p>
<p><strong>合格參考文件</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> <span class="sb">`docs/app-requirements-spec.md`</span> <span class="ni">#UC</span>-01 書籍查詢功能
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> <span class="sb">`docs/work-logs/v0.12.7-design-decisions.md`</span> <span class="ni">#決策1</span> Repository 設計模式
</span></span><span class="line"><span class="ln">4</span><span class="cl">- <span class="sb">`.claude/methodologies/layered-ticket-methodology.md`</span> 層級隔離原則</span></span></code></pre></div><p><strong>不合格參考文件</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">（沒有參考文件章節）</span></span></code></pre></div><p><strong>檢測流程完整範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket #1: [Layer 5] 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">執行 C2. Incomplete Ticket 檢測
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">【步驟 1】檢查驗收條件
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ 有「### 驗收條件」章節（符合）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ├─ 驗收項目數量: 6 個（符合）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ 驗收條件可量化（符合）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  └─ 通過
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">【步驟 2】檢查測試規劃
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ 步驟 4 包含測試（符合）
</span></span><span class="line"><span class="ln">13</span><span class="cl">  ├─ 測試檔案路徑明確（符合）
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ├─ 測試項目完整（符合）
</span></span><span class="line"><span class="ln">15</span><span class="cl">  └─ 通過
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">17</span><span class="cl">【步驟 3】檢查工作日誌規劃
</span></span><span class="line"><span class="ln">18</span><span class="cl">  ├─ 有「### 工作日誌」章節（符合）
</span></span><span class="line"><span class="ln">19</span><span class="cl">  ├─ 檔案名稱符合規範（符合）
</span></span><span class="line"><span class="ln">20</span><span class="cl">  └─ 通過
</span></span><span class="line"><span class="ln">21</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">22</span><span class="cl">【步驟 4】檢查參考文件
</span></span><span class="line"><span class="ln">23</span><span class="cl">  ├─ 有「### 參考文件」章節（符合）
</span></span><span class="line"><span class="ln">24</span><span class="cl">  ├─ 連結數量: 3 個（符合）
</span></span><span class="line"><span class="ln">25</span><span class="cl">  ├─ 連結格式正確（符合）
</span></span><span class="line"><span class="ln">26</span><span class="cl">  └─ 通過
</span></span><span class="line"><span class="ln">27</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">28</span><span class="cl">C2 檢測結論: 通過</span></span></code></pre></div><p><strong>修正方法</strong>:</p>
<h4 id="缺失項目-1-沒有驗收條件">缺失項目 1: 沒有驗收條件</h4>
<p>修正步驟:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 新增「### 驗收條件」章節
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 撰寫 3-6 個可量化驗收項目
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 涵蓋三個維度:
</span></span><span class="line"><span class="ln">4</span><span class="cl">   - 功能性驗收（檔案位置、方法簽名、程式邏輯）
</span></span><span class="line"><span class="ln">5</span><span class="cl">   - 品質性驗收（dart analyze、測試覆蓋率、文檔）
</span></span><span class="line"><span class="ln">6</span><span class="cl">   - 整合性驗收（整合測試、不破壞既有功能）</span></span></code></pre></div><h5 id="缺失項目-2-沒有測試規劃">缺失項目 2: 沒有測試規劃</h5>
<p>修正步驟:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 在「### 步驟」章節新增測試步驟
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 明確列出測試檔案路徑
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 列出測試項目清單（正常流程 + 異常處理）
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 定義測試覆蓋率要求（建議 &gt; 80%）</span></span></code></pre></div><h6 id="缺失項目-3-沒有工作日誌規劃">缺失項目 3: 沒有工作日誌規劃</h6>
<p>修正步驟:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 新增「### 工作日誌」章節
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 定義工作日誌檔案名稱
</span></span><span class="line"><span class="ln">3</span><span class="cl">   格式: docs/work-logs/vX.Y.Z-feature-name.md
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. 說明記錄內容範圍</span></span></code></pre></div><h6 id="缺失項目-4-沒有參考文件">缺失項目 4: 沒有參考文件</h6>
<p>修正步驟:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 新增「### 參考文件」章節
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 連結需求規格（必要）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 連結設計文件（建議）
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 連結相關 Ticket（如有依賴）</span></span></code></pre></div><p><strong>完整修正範例</strong>:</p>
<p><strong>修正前（Incomplete Ticket）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## Ticket: 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span>定義書籍 Repository 的介面契約
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立介面檔案
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">2.</span> 定義方法簽名
</span></span><span class="line"><span class="ln">9</span><span class="cl">3. 撰寫文檔註解</span></span></code></pre></div><p><strong>修正後（Complete Ticket）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket: [Layer 5] 定義 IBookRepository 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>定義書籍 Repository 的介面契約，規範 Domain 層與 Infrastructure 層的互動方式
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domains/library/repositories/i_book_repository.dart`</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`getBookByIsbn(String isbn)`</span> 方法簽名
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`getAllBooks()`</span> 方法簽名
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">4.</span> 撰寫完整的文檔註解（參數、回傳值、異常）
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">5.</span> 撰寫 <span class="sb">`test/domain/repositories/book_repository_test.dart`</span> 單元測試
</span></span><span class="line"><span class="ln">12</span><span class="cl">   <span class="k">-</span> 測試 getBookByIsbn 正常流程
</span></span><span class="line"><span class="ln">13</span><span class="cl">   <span class="k">-</span> 測試書籍不存在異常處理
</span></span><span class="line"><span class="ln">14</span><span class="cl">   <span class="k">-</span> 測試 ISBN 格式錯誤異常處理
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">6.</span> 確保測試覆蓋率 &gt; 80%
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> <span class="sb">`IBookRepository`</span> 介面檔案建立在 <span class="sb">`lib/domains/library/repositories/`</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> <span class="sb">`getBookByIsbn(String isbn)`</span> 方法簽名完整且明確
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 方法包含完整的文檔註解
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 測試覆蓋率 &gt; 80%
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">- [ ]</span> 不破壞既有功能
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu">### 工作日誌
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu"></span>檔案名稱: <span class="sb">`docs/work-logs/v0.12.8-book-repository-interface.md`</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu"></span><span class="k">-</span> <span class="sb">`docs/app-requirements-spec.md`</span> <span class="ni">#UC</span>-01 書籍查詢功能
</span></span><span class="line"><span class="ln">30</span><span class="cl">- <span class="sb">`.claude/methodologies/layered-ticket-methodology.md`</span> 層級隔離原則</span></span></code></pre></div><hr>
<h4 id="245-品質閘門執行流程">2.4.5 品質閘門執行流程</h4>
<p><strong>完整執行流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 1: 功能設計（lavender-interface-designer）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Ticket 清單撰寫完成
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│   品質閘門檢測開始                    │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   （lavender-interface-designer）    │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">10</span><span class="cl">對每個 Ticket 執行檢測（按順序）
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln">13</span><span class="cl">│ 【檢測 1】C1. God Ticket 檢測        │
</span></span><span class="line"><span class="ln">14</span><span class="cl">│                                     │
</span></span><span class="line"><span class="ln">15</span><span class="cl">│ 檢查指標:                            │
</span></span><span class="line"><span class="ln">16</span><span class="cl">│  - 檔案數量 ≤ 10？                   │
</span></span><span class="line"><span class="ln">17</span><span class="cl">│  - 層級跨度 ≤ 2？                    │
</span></span><span class="line"><span class="ln">18</span><span class="cl">│  - 預估工時 ≤ 16h？                  │
</span></span><span class="line"><span class="ln">19</span><span class="cl">│                                     │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│ 組合邏輯: 任一項目超標 = God Ticket   │
</span></span><span class="line"><span class="ln">21</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln">22</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">23</span><span class="cl">  ├─ 通過 → 繼續檢測 C3
</span></span><span class="line"><span class="ln">24</span><span class="cl">  └─ 失敗 → 執行拆分 → 重新檢測 C1
</span></span><span class="line"><span class="ln">25</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">26</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln">27</span><span class="cl">│ 【檢測 2】C3. Ambiguous              │
</span></span><span class="line"><span class="ln">28</span><span class="cl">│           Responsibility 檢測         │
</span></span><span class="line"><span class="ln">29</span><span class="cl">│                                     │
</span></span><span class="line"><span class="ln">30</span><span class="cl">│ 檢查要素:                            │
</span></span><span class="line"><span class="ln">31</span><span class="cl">│  - 有層級標示 [Layer X]？            │
</span></span><span class="line"><span class="ln">32</span><span class="cl">│  - 職責描述明確？                    │
</span></span><span class="line"><span class="ln">33</span><span class="cl">│  - 檔案範圍明確？                    │
</span></span><span class="line"><span class="ln">34</span><span class="cl">│  - 驗收條件限定在該層？              │
</span></span><span class="line"><span class="ln">35</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln">36</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">37</span><span class="cl">  ├─ 通過 → 繼續檢測 C2
</span></span><span class="line"><span class="ln">38</span><span class="cl">  └─ 失敗 → 重新定義職責 → 重新檢測 C3
</span></span><span class="line"><span class="ln">39</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">40</span><span class="cl">┌─────────────────────────────────────┐
</span></span><span class="line"><span class="ln">41</span><span class="cl">│ 【檢測 3】C2. Incomplete Ticket 檢測 │
</span></span><span class="line"><span class="ln">42</span><span class="cl">│                                     │
</span></span><span class="line"><span class="ln">43</span><span class="cl">│ 檢查要素:                            │
</span></span><span class="line"><span class="ln">44</span><span class="cl">│  - 有驗收條件章節？                  │
</span></span><span class="line"><span class="ln">45</span><span class="cl">│  - 有測試規劃？                      │
</span></span><span class="line"><span class="ln">46</span><span class="cl">│  - 有工作日誌規劃？                  │
</span></span><span class="line"><span class="ln">47</span><span class="cl">│  - 有參考文件連結？                  │
</span></span><span class="line"><span class="ln">48</span><span class="cl">└─────────────────────────────────────┘
</span></span><span class="line"><span class="ln">49</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">50</span><span class="cl">  ├─ 通過 → Ticket 通過品質閘門
</span></span><span class="line"><span class="ln">51</span><span class="cl">  └─ 失敗 → 補充遺漏項目 → 重新檢測 C2
</span></span><span class="line"><span class="ln">52</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">53</span><span class="cl">所有 Ticket 都通過 C1/C2/C3？
</span></span><span class="line"><span class="ln">54</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">55</span><span class="cl">  ├─ Yes → 提交品質閘門報告給 PM
</span></span><span class="line"><span class="ln">56</span><span class="cl">  │           ↓
</span></span><span class="line"><span class="ln">57</span><span class="cl">  │         PM 審查
</span></span><span class="line"><span class="ln">58</span><span class="cl">  │           ↓
</span></span><span class="line"><span class="ln">59</span><span class="cl">  │         PM 批准 → 進入 Phase 2: 測試驗證
</span></span><span class="line"><span class="ln">60</span><span class="cl">  │
</span></span><span class="line"><span class="ln">61</span><span class="cl">  └─ No → 繼續修正未通過的 Ticket</span></span></code></pre></div><p><strong>檢測順序說明</strong>:</p>
<table>
  <thead>
      <tr>
          <th>順序</th>
          <th>檢測項目</th>
          <th>檢測原因</th>
          <th>失敗影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>1 C1</strong></td>
          <td>God Ticket</td>
          <td>檔案數和層級是最基礎的結構問題</td>
          <td>必須拆分，否則後續檢測無意義</td>
      </tr>
      <tr>
          <td><strong>2 C3</strong></td>
          <td>Ambiguous Responsibility</td>
          <td>職責定義是 Ticket 品質的核心</td>
          <td>必須重新定義，避免跨層級實作</td>
      </tr>
      <tr>
          <td><strong>3 C2</strong></td>
          <td>Incomplete Ticket</td>
          <td>完整性是 Ticket 可執行性的保證</td>
          <td>補充遺漏項目，確保開發者理解需求</td>
      </tr>
  </tbody>
</table>
<p><strong>檢測順序設計理由</strong>:</p>
<ul>
<li><strong>C1 優先</strong>: 檔案數和層級問題會影響職責定義，必須先解決結構問題</li>
<li><strong>C3 次之</strong>: 職責明確後才能評估驗收條件和測試規劃是否完整</li>
<li><strong>C2 最後</strong>: 完整性檢測依賴明確的職責定義</li>
</ul>
<p><strong>檢測失敗處理流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">檢測失敗
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">記錄失敗原因到工作日誌
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">根據失敗類型選擇修正方法
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ C1 失敗 → 執行 Ticket 拆分
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │             ├─ 按層級拆分（優先）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  │             ├─ 按職責拆分（次要）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  │             └─ 按 TDD 階段拆分（特殊）
</span></span><span class="line"><span class="ln">10</span><span class="cl">  │
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├─ C3 失敗 → 重新定義職責
</span></span><span class="line"><span class="ln">12</span><span class="cl">  │             ├─ 確認主要職責層級
</span></span><span class="line"><span class="ln">13</span><span class="cl">  │             ├─ 加入層級標示 [Layer X]
</span></span><span class="line"><span class="ln">14</span><span class="cl">  │             ├─ 限定職責範圍
</span></span><span class="line"><span class="ln">15</span><span class="cl">  │             └─ 明確列出檔案
</span></span><span class="line"><span class="ln">16</span><span class="cl">  │
</span></span><span class="line"><span class="ln">17</span><span class="cl">  └─ C2 失敗 → 補充遺漏項目
</span></span><span class="line"><span class="ln">18</span><span class="cl">                ├─ 補充驗收條件
</span></span><span class="line"><span class="ln">19</span><span class="cl">                ├─ 補充測試規劃
</span></span><span class="line"><span class="ln">20</span><span class="cl">                ├─ 補充工作日誌規劃
</span></span><span class="line"><span class="ln">21</span><span class="cl">                └─ 補充參考文件連結
</span></span><span class="line"><span class="ln">22</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">23</span><span class="cl">修正完成
</span></span><span class="line"><span class="ln">24</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">25</span><span class="cl">重新執行失敗的檢測項目
</span></span><span class="line"><span class="ln">26</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">27</span><span class="cl">  ├─ 通過 → 繼續下一個檢測
</span></span><span class="line"><span class="ln">28</span><span class="cl">  └─ 失敗 → 再次修正（直到通過）</span></span></code></pre></div><p><strong>PM 審查標準</strong>:</p>
<p><strong>審查清單</strong>:</p>
<table>
  <thead>
      <tr>
          <th>審查項目</th>
          <th>檢查內容</th>
          <th>通過標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>品質閘門報告</strong></td>
          <td>每個 Ticket 是否都執行了 C1/C2/C3 檢測</td>
          <td>所有 Ticket 都有檢測記錄（合格）</td>
      </tr>
      <tr>
          <td><strong>檢測結果</strong></td>
          <td>所有 Ticket 是否都通過檢測</td>
          <td>所有 Ticket 標記為「通過」</td>
      </tr>
      <tr>
          <td><strong>修正記錄</strong></td>
          <td>檢測失敗的 Ticket 是否記錄修正過程</td>
          <td>修正過程完整記錄（問題 + 修正方法 + 結果）</td>
      </tr>
      <tr>
          <td><strong>Ticket 數量</strong></td>
          <td>拆分後的 Ticket 數量是否合理</td>
          <td>不過度拆分（&lt; 10 個）、不過度合併（&gt; 1 個）</td>
      </tr>
      <tr>
          <td><strong>依賴關係</strong></td>
          <td>Ticket 間的依賴關係是否明確</td>
          <td>依賴順序符合架構層級（L5→L3→L2→L1）</td>
      </tr>
  </tbody>
</table>
<p><strong>PM 審查決策流程</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">PM 收到品質閘門報告
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">【檢查 1】品質閘門報告完整性
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ 所有 Ticket 都有 C1/C2/C3 檢測記錄？
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  │   ├─ Yes → 繼續檢查 2
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │   └─ No → 要求 lavender 補充檢測
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">【檢查 2】檢測結果正確性
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ 所有 Ticket 都標記為「通過」？
</span></span><span class="line"><span class="ln">10</span><span class="cl">  │   ├─ Yes → 繼續檢查 3
</span></span><span class="line"><span class="ln">11</span><span class="cl">  │   └─ No → 檢查未通過原因
</span></span><span class="line"><span class="ln">12</span><span class="cl">  │                ├─ 未修正 → 要求 lavender 修正
</span></span><span class="line"><span class="ln">13</span><span class="cl">  │                └─ 修正後仍不符合 → 要求重新設計
</span></span><span class="line"><span class="ln">14</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">15</span><span class="cl">【檢查 3】修正記錄完整性
</span></span><span class="line"><span class="ln">16</span><span class="cl">  ├─ 檢測失敗的 Ticket 是否記錄修正過程？
</span></span><span class="line"><span class="ln">17</span><span class="cl">  │   ├─ Yes → 繼續檢查 4
</span></span><span class="line"><span class="ln">18</span><span class="cl">  │   └─ No → 要求補充修正記錄
</span></span><span class="line"><span class="ln">19</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">20</span><span class="cl">【檢查 4】Ticket 數量合理性
</span></span><span class="line"><span class="ln">21</span><span class="cl">  ├─ 拆分後數量是否合理（1-10 個）？
</span></span><span class="line"><span class="ln">22</span><span class="cl">  │   ├─ Yes → 繼續檢查 5
</span></span><span class="line"><span class="ln">23</span><span class="cl">  │   └─ No → 要求調整拆分策略
</span></span><span class="line"><span class="ln">24</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">25</span><span class="cl">【檢查 5】依賴關係正確性
</span></span><span class="line"><span class="ln">26</span><span class="cl">  ├─ Ticket 依賴順序是否符合架構層級？
</span></span><span class="line"><span class="ln">27</span><span class="cl">  │   ├─ Yes → PM 批准 Ticket 清單
</span></span><span class="line"><span class="ln">28</span><span class="cl">  │   └─ No → 要求調整依賴順序
</span></span><span class="line"><span class="ln">29</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">30</span><span class="cl">PM 批准
</span></span><span class="line"><span class="ln">31</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">32</span><span class="cl">分派給開發者執行（進入 Phase 2）</span></span></code></pre></div><p><strong>工作日誌記錄範例</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">  1</span><span class="cl"><span class="gu">## Code Smell 品質閘門檢測
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="gu">### Ticket #1: [Layer 5] 定義 Book Entity
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="gu">#### 檢測記錄（2025-10-11）
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="gs">**C1. God Ticket 檢測**</span>:
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="k">-</span> 檔案數量: 2 個（符合）（book.dart + isbn.dart）
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="k">-</span> 層級跨度: 1 層（Layer 5）（符合）
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="k">-</span> 預估工時: 4 小時（符合）
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="k">-</span> 結論: 通過
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="gs">**C3. Ambiguous Responsibility 檢測**</span>:
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">-</span> 層級標示: [Layer 5]（符合）
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">-</span> 職責描述: 明確（定義 Book Entity 和 ISBN Value Object）（符合）
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">-</span> 檔案範圍: 明確列出 2 個檔案路徑（符合）
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">-</span> 驗收限定: 限定在 Domain 層（符合）
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">-</span> 結論: 通過
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="gs">**C2. Incomplete Ticket 檢測**</span>:
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="k">-</span> 驗收條件: 6 個驗收項目（符合）
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">-</span> 測試規劃: 包含測試步驟和覆蓋率要求（符合）
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="k">-</span> 工作日誌: 規劃檔案名稱（符合）
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">-</span> 參考文件: 連結需求規格和設計文件（符合）
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="k">-</span> 結論: 通過
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="gs">**最終結論**</span>: Ticket <span class="ni">#1</span> 通過品質閘門
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="gu">### Ticket #2: [Layer 3/5] 實作書籍查詢功能（原始設計）
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="gu">#### 檢測記錄（2025-10-11）
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="gs">**C1. God Ticket 檢測**</span>:
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="k">-</span> 檔案數量: 15 個（不符合）（超標，標準 ≤ 10）
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">-</span> 層級跨度: 3 層（Layer 1/2/3/5）（不符合）（超標，標準 ≤ 2）
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="k">-</span> 預估工時: 24 小時（不符合）（超標，標準 ≤ 16h）
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="k">-</span> 結論: 失敗（3 個指標都超標）
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="gs">**修正方法**</span>: 按層級拆分為 4 個 Ticket
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2a:</span> [Layer 5] 定義 Book Entity
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2b:</span> [Layer 3] 實作 GetBookUseCase
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2c:</span> [Layer 2] 實作 BookController
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2d:</span> [Layer 1] 實作 BookListWidget
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="gs">**拆分後重新檢測**</span>:
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="gs">**Ticket #2a 檢測**</span>:
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">-</span> C1: 通過（2 個檔案、1 層、4h）
</span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="k">-</span> C3: 通過（職責明確）
</span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="k">-</span> C2: 通過（4 項必要元素齊全）
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="gs">**Ticket #2b 檢測**</span>:
</span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="k">-</span> C1: 通過（3 個檔案、1 層、6h）
</span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="k">-</span> C3: 通過（職責明確）
</span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="k">-</span> C2: 通過（4 項必要元素齊全）
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="gs">**Ticket #2c 檢測**</span>:
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="k">-</span> C1: 通過（2 個檔案、1 層、4h）
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="k">-</span> C3: 通過（職責明確）
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="k">-</span> C2: 通過（4 項必要元素齊全）
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="gs">**Ticket #2d 檢測**</span>:
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="k">-</span> C1: 通過（3 個檔案、1 層、5h）
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="k">-</span> C3: 通過（職責明確）
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">-</span> C2: 通過（4 項必要元素齊全）
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="gs">**最終結論**</span>: 原 Ticket <span class="ni">#2</span> 拆分為 4 個 Ticket，全部通過品質閘門
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="gu">### PM 審查報告
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="gs">**審查日期**</span>: 2025-10-11
</span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="gs">**審查 Ticket 清單**</span>: v0.12.8 書籍查詢功能（5 個 Ticket）
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="gs">**品質閘門檢測確認**</span>:
</span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#1:</span> 通過 C1/C2/C3 檢測- Ticket <span class="ni">#2a:</span> 通過 C1/C2/C3 檢測（拆分後）- Ticket <span class="ni">#2b:</span> 通過 C1/C2/C3 檢測（拆分後）- Ticket <span class="ni">#2c:</span> 通過 C1/C2/C3 檢測（拆分後）- Ticket <span class="ni">#2d:</span> 通過 C1/C2/C3 檢測（拆分後）
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">**拆分合理性**:
</span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="k">-</span> 原 Ticket 數量: 2 個（1 個正常 + 1 個 God Ticket）
</span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="k">-</span> 拆分後數量: 5 個
</span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="k">-</span> 評估: 合理（符合）
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">-</span> 理由: God Ticket 按層級拆分為 4 個，符合單層修改原則
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="gs">**依賴關係檢查**</span>:
</span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="k">-</span> 依賴順序: Ticket <span class="ni">#1</span> → <span class="ni">#2a</span> → <span class="ni">#2b</span> → <span class="ni">#2c</span> → <span class="ni">#2d</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="k">-</span> 評估: 正確（符合）
</span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="k">-</span> 理由: 遵循架構層級順序（Layer 5 → Layer 3 → Layer 2 → Layer 1）
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="gs">**審查結論**</span>:
</span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="k">-</span> 批准 Ticket 清單- 可分派給開發者執行（進入 Phase 2）```
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="gu">#### 2.4.6 自動化檢測準備（v0.12.G.4）
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">本節為 v0.12.G.4「代理人和 Hook 機制調整」準備自動化檢測規則設計。
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="gs">**自動化檢測目標**</span>:
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="k">-</span> 減少人工檢測工作量
</span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="k">-</span> 提升檢測一致性
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="k">-</span> 即時反饋 Ticket 品質問題
</span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="k">-</span> 支援 lavender-interface-designer 執行檢測
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="gs">**可自動化項目**</span>:
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="gs">**C1. God Ticket 自動化檢測（可自動化程度: 80%）**</span>:
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">| 檢測指標     | 自動化程度 | 自動化方法                             | 需要人工判斷     |
</span></span><span class="line"><span class="ln">112</span><span class="cl">| ------------ | ---------- | -------------------------------------- | ---------------- |
</span></span><span class="line"><span class="ln">113</span><span class="cl">| <span class="gs">**檔案數量**</span> | 100%（合格）    | 掃描步驟章節，提取檔案路徑，計算數量   | 無               |
</span></span><span class="line"><span class="ln">114</span><span class="cl">| <span class="gs">**層級跨度**</span> | 100%（合格）    | 使用 v0.12.G.1 第 6.2 節決策樹判斷層級 | 無               |
</span></span><span class="line"><span class="ln">115</span><span class="cl">| <span class="gs">**預估工時**</span> | 60%（需檢查）      | 根據步驟數量估算（<span class="p">&lt;</span> <span class="nt">10</span> <span class="na">步 </span><span class="o">=</span> <span class="s">2-4h）</span>     <span class="err">|</span> <span class="na">複雜度需人工確認</span> <span class="err">|</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="gs">**自動化規則設計**</span><span class="na">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="err">```</span><span class="na">python</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="na">def</span> <span class="na">check_god_ticket_automated</span><span class="err">(</span><span class="na">ticket_content:</span> <span class="na">str</span><span class="err">)</span> <span class="na">-</span><span class="p">&gt;</span> dict:
</span></span><span class="line"><span class="ln">121</span><span class="cl">    &#34;&#34;&#34;
</span></span><span class="line"><span class="ln">122</span><span class="cl">    自動化檢測 God Ticket
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">    回傳格式:
</span></span><span class="line"><span class="ln">125</span><span class="cl">    {
</span></span><span class="line"><span class="ln">126</span><span class="cl">        &#34;file_count&#34;: int,
</span></span><span class="line"><span class="ln">127</span><span class="cl">        &#34;layer_span&#34;: int,
</span></span><span class="line"><span class="ln">128</span><span class="cl">        &#34;estimated_hours&#34;: int,
</span></span><span class="line"><span class="ln">129</span><span class="cl">        &#34;is_god_ticket&#34;: bool,
</span></span><span class="line"><span class="ln">130</span><span class="cl">        &#34;confidence&#34;: float  # 0.0-1.0
</span></span><span class="line"><span class="ln">131</span><span class="cl">    }
</span></span><span class="line"><span class="ln">132</span><span class="cl">    &#34;&#34;&#34;
</span></span><span class="line"><span class="ln">133</span><span class="cl">    # 1. 提取檔案路徑
</span></span><span class="line"><span class="ln">134</span><span class="cl">    file_paths = extract_file_paths(ticket_content)
</span></span><span class="line"><span class="ln">135</span><span class="cl">    file_count = len(file_paths)
</span></span><span class="line"><span class="ln">136</span><span class="cl">
</span></span><span class="line"><span class="ln">137</span><span class="cl">    # 2. 判斷層級跨度
</span></span><span class="line"><span class="ln">138</span><span class="cl">    layers = [determine_layer(path) for path in file_paths]
</span></span><span class="line"><span class="ln">139</span><span class="cl">    layer_span = max(layers) - min(layers) + 1
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl">    # 3. 預估工時（簡化估算）
</span></span><span class="line"><span class="ln">142</span><span class="cl">    step_count = count_steps(ticket_content)
</span></span><span class="line"><span class="ln">143</span><span class="cl">    estimated_hours = estimate_hours_by_steps(step_count)
</span></span><span class="line"><span class="ln">144</span><span class="cl">
</span></span><span class="line"><span class="ln">145</span><span class="cl">    # 4. 判斷是否為 God Ticket（任一項目超標）
</span></span><span class="line"><span class="ln">146</span><span class="cl">    is_god_ticket = (
</span></span><span class="line"><span class="ln">147</span><span class="cl">        file_count &gt; 10 or
</span></span><span class="line"><span class="ln">148</span><span class="cl">        layer_span &gt; 2 or
</span></span><span class="line"><span class="ln">149</span><span class="cl">        estimated_hours &gt; 16
</span></span><span class="line"><span class="ln">150</span><span class="cl">    )
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    # 5. 計算信心度
</span></span><span class="line"><span class="ln">153</span><span class="cl">    confidence = calculate_confidence(step_count, file_paths)
</span></span><span class="line"><span class="ln">154</span><span class="cl">
</span></span><span class="line"><span class="ln">155</span><span class="cl">    return {
</span></span><span class="line"><span class="ln">156</span><span class="cl">        &#34;file_count&#34;: file_count,
</span></span><span class="line"><span class="ln">157</span><span class="cl">        &#34;layer_span&#34;: layer_span,
</span></span><span class="line"><span class="ln">158</span><span class="cl">        &#34;estimated_hours&#34;: estimated_hours,
</span></span><span class="line"><span class="ln">159</span><span class="cl">        &#34;is_god_ticket&#34;: is_god_ticket,
</span></span><span class="line"><span class="ln">160</span><span class="cl">        &#34;confidence&#34;: confidence
</span></span><span class="line"><span class="ln">161</span><span class="cl">    }</span></span></code></pre></div><p><strong>C2. Incomplete Ticket 自動化檢測（可自動化程度: 90%）</strong>:</p>
<table>
  <thead>
      <tr>
          <th>檢測指標</th>
          <th>自動化程度</th>
          <th>自動化方法</th>
          <th>需要人工判斷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>驗收條件</strong></td>
          <td>100%（合格）</td>
          <td>檢查章節存在性、驗收項目數量</td>
          <td>無</td>
      </tr>
      <tr>
          <td><strong>測試規劃</strong></td>
          <td>80%（需檢查）</td>
          <td>檢查測試檔案和關鍵字</td>
          <td>測試完整性需人工確認</td>
      </tr>
      <tr>
          <td><strong>工作日誌</strong></td>
          <td>100%（合格）</td>
          <td>檢查章節存在性、檔案名稱格式</td>
          <td>無</td>
      </tr>
      <tr>
          <td><strong>參考文件</strong></td>
          <td>100%（合格）</td>
          <td>檢查章節存在性、連結數量</td>
          <td>無</td>
      </tr>
  </tbody>
</table>
<p><strong>自動化規則設計</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_incomplete_ticket_automated</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    自動化檢測 Incomplete Ticket
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    回傳格式:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    {
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        &#34;has_acceptance_criteria&#34;: bool,
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        &#34;acceptance_count&#34;: int,
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        &#34;has_test_plan&#34;: bool,
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        &#34;test_files&#34;: list,
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        &#34;has_work_log&#34;: bool,
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        &#34;work_log_file&#34;: str,
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        &#34;has_references&#34;: bool,
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;reference_count&#34;: int,
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        &#34;is_incomplete&#34;: bool
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    }
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 1. 檢查驗收條件</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">has_acceptance</span> <span class="o">=</span> <span class="n">has_section</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">,</span> <span class="s2">&#34;### 驗收條件&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">acceptance_count</span> <span class="o">=</span> <span class="n">count_acceptance_items</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 2. 檢查測試規劃</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">has_test_plan</span> <span class="o">=</span> <span class="n">has_test_keywords</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">test_files</span> <span class="o">=</span> <span class="n">extract_test_files</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 3. 檢查工作日誌</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">has_work_log</span> <span class="o">=</span> <span class="n">has_section</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">,</span> <span class="s2">&#34;### 工作日誌&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">work_log_file</span> <span class="o">=</span> <span class="n">extract_work_log_file</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="c1"># 4. 檢查參考文件</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">has_references</span> <span class="o">=</span> <span class="n">has_section</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">,</span> <span class="s2">&#34;### 參考文件&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">reference_count</span> <span class="o">=</span> <span class="n">count_references</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># 5. 判斷是否為 Incomplete Ticket</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">is_incomplete</span> <span class="o">=</span> <span class="ow">not</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">has_acceptance</span> <span class="ow">and</span> <span class="n">acceptance_count</span> <span class="o">&gt;=</span> <span class="mi">3</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">has_test_plan</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">test_files</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">has_work_log</span> <span class="ow">and</span> <span class="n">work_log_file</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">has_references</span> <span class="ow">and</span> <span class="n">reference_count</span> <span class="o">&gt;=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="s2">&#34;has_acceptance_criteria&#34;</span><span class="p">:</span> <span class="n">has_acceptance</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="s2">&#34;acceptance_count&#34;</span><span class="p">:</span> <span class="n">acceptance_count</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="s2">&#34;has_test_plan&#34;</span><span class="p">:</span> <span class="n">has_test_plan</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;test_files&#34;</span><span class="p">:</span> <span class="n">test_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="s2">&#34;has_work_log&#34;</span><span class="p">:</span> <span class="n">has_work_log</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="s2">&#34;work_log_file&#34;</span><span class="p">:</span> <span class="n">work_log_file</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="s2">&#34;has_references&#34;</span><span class="p">:</span> <span class="n">has_references</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="s2">&#34;reference_count&#34;</span><span class="p">:</span> <span class="n">reference_count</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;is_incomplete&#34;</span><span class="p">:</span> <span class="n">is_incomplete</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><p><strong>C3. Ambiguous Responsibility 自動化檢測（可自動化程度: 70%）</strong>:</p>
<table>
  <thead>
      <tr>
          <th>檢測指標</th>
          <th>自動化程度</th>
          <th>自動化方法</th>
          <th>需要人工判斷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>層級標示</strong></td>
          <td>100%（合格）</td>
          <td>正則匹配 <code>\[Layer \d+\]</code></td>
          <td>無</td>
      </tr>
      <tr>
          <td><strong>職責描述</strong></td>
          <td>50%（需檢查）</td>
          <td>檢查字數、關鍵字</td>
          <td>明確性需人工確認</td>
      </tr>
      <tr>
          <td><strong>檔案範圍</strong></td>
          <td>100%（合格）</td>
          <td>檢查具體檔案路徑</td>
          <td>無</td>
      </tr>
      <tr>
          <td><strong>驗收限定</strong></td>
          <td>70%（需檢查）</td>
          <td>提取驗收條件的類別名稱</td>
          <td>層級一致性需人工確認</td>
      </tr>
  </tbody>
</table>
<p><strong>自動化規則設計</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_ambiguous_responsibility_automated</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    自動化檢測 Ambiguous Responsibility
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    回傳格式:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    {
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        &#34;has_layer_tag&#34;: bool,
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        &#34;layer_number&#34;: int,
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        &#34;has_clear_responsibility&#34;: bool,
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        &#34;responsibility_length&#34;: int,
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        &#34;has_file_paths&#34;: bool,
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        &#34;file_count&#34;: int,
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        &#34;acceptance_limited_to_layer&#34;: bool,
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;is_ambiguous&#34;: bool,
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        &#34;confidence&#34;: float
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    }
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 1. 檢查層級標示</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">has_layer_tag</span><span class="p">,</span> <span class="n">layer_number</span> <span class="o">=</span> <span class="n">extract_layer_tag</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 2. 檢查職責描述（簡化檢查）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">responsibility</span> <span class="o">=</span> <span class="n">extract_responsibility</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">has_clear_responsibility</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">responsibility</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">20</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 3. 檢查檔案範圍</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">file_paths</span> <span class="o">=</span> <span class="n">extract_file_paths</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">has_file_paths</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">file_paths</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># 4. 檢查驗收限定（需要層級判斷）</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">acceptance_items</span> <span class="o">=</span> <span class="n">extract_acceptance_items</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">acceptance_layers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">determine_layer_from_acceptance</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">acceptance_items</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">acceptance_limited</span> <span class="o">=</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">layer</span> <span class="o">==</span> <span class="n">layer_number</span> <span class="k">for</span> <span class="n">layer</span> <span class="ow">in</span> <span class="n">acceptance_layers</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="c1"># 5. 判斷是否為 Ambiguous Responsibility</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">is_ambiguous</span> <span class="o">=</span> <span class="ow">not</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">has_layer_tag</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">has_clear_responsibility</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">has_file_paths</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">acceptance_limited</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="c1"># 6. 計算信心度（職責描述需人工確認）</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">confidence</span> <span class="o">=</span> <span class="mf">0.7</span> <span class="k">if</span> <span class="n">has_clear_responsibility</span> <span class="k">else</span> <span class="mf">0.5</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;has_layer_tag&#34;</span><span class="p">:</span> <span class="n">has_layer_tag</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="s2">&#34;layer_number&#34;</span><span class="p">:</span> <span class="n">layer_number</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="s2">&#34;has_clear_responsibility&#34;</span><span class="p">:</span> <span class="n">has_clear_responsibility</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="s2">&#34;responsibility_length&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">responsibility</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="s2">&#34;has_file_paths&#34;</span><span class="p">:</span> <span class="n">has_file_paths</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="s2">&#34;file_count&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">file_paths</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="s2">&#34;acceptance_limited_to_layer&#34;</span><span class="p">:</span> <span class="n">acceptance_limited</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="s2">&#34;is_ambiguous&#34;</span><span class="p">:</span> <span class="n">is_ambiguous</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="s2">&#34;confidence&#34;</span><span class="p">:</span> <span class="n">confidence</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><p><strong>需要人工判斷的項目</strong>:</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>原因</th>
          <th>人工判斷者</th>
          <th>自動化支援</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Ticket 拆分合理性</strong></td>
          <td>需要業務知識和架構經驗</td>
          <td>PM 或架構師</td>
          <td>提供拆分建議</td>
      </tr>
      <tr>
          <td><strong>職責定義明確性</strong></td>
          <td>需要理解業務語意</td>
          <td>lavender-interface-designer</td>
          <td>檢測關鍵字</td>
      </tr>
      <tr>
          <td><strong>預估工時準確性</strong></td>
          <td>需要評估任務複雜度</td>
          <td>PM 或開發者</td>
          <td>根據步驟數量估算</td>
      </tr>
      <tr>
          <td><strong>依賴關係正確性</strong></td>
          <td>需要理解架構層級關係</td>
          <td>lavender-interface-designer</td>
          <td>檢查依賴順序</td>
      </tr>
  </tbody>
</table>
<p><strong>Hook 系統檢測規則（v0.12.G.4 規劃）</strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Hook 觸發時機: Ticket 檔案修改時（PostEdit Hook）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">ticket_quality_gate_hook</span><span class="p">(</span><span class="n">ticket_file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Ticket 品質閘門 Hook
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    執行時機:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - Ticket 檔案儲存時
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - lavender-interface-designer 提交工作日誌時
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    回傳格式:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    {
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        &#34;passed&#34;: bool,
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;c1_result&#34;: dict,
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        &#34;c2_result&#34;: dict,
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        &#34;c3_result&#34;: dict,
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">        &#34;suggestions&#34;: list,
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        &#34;blocking_issues&#34;: list
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    }
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 讀取 Ticket 內容</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">ticket_content</span> <span class="o">=</span> <span class="n">read_ticket_file</span><span class="p">(</span><span class="n">ticket_file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="c1"># 執行自動化檢測</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">c1_result</span> <span class="o">=</span> <span class="n">check_god_ticket_automated</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">c2_result</span> <span class="o">=</span> <span class="n">check_incomplete_ticket_automated</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">c3_result</span> <span class="o">=</span> <span class="n">check_ambiguous_responsibility_automated</span><span class="p">(</span><span class="n">ticket_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># 判斷是否通過</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">passed</span> <span class="o">=</span> <span class="ow">not</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">c1_result</span><span class="p">[</span><span class="s2">&#34;is_god_ticket&#34;</span><span class="p">]</span> <span class="ow">or</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">c2_result</span><span class="p">[</span><span class="s2">&#34;is_incomplete&#34;</span><span class="p">]</span> <span class="ow">or</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">c3_result</span><span class="p">[</span><span class="s2">&#34;is_ambiguous&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="c1"># 生成建議</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">suggestions</span> <span class="o">=</span> <span class="n">generate_suggestions</span><span class="p">(</span><span class="n">c1_result</span><span class="p">,</span> <span class="n">c2_result</span><span class="p">,</span> <span class="n">c3_result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="c1"># 生成阻斷問題（需要立即修正）</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">blocking_issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="k">if</span> <span class="n">c1_result</span><span class="p">[</span><span class="s2">&#34;is_god_ticket&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">blocking_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;God Ticket 檢測失敗: &#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;檔案數=</span><span class="si">{</span><span class="n">c1_result</span><span class="p">[</span><span class="s1">&#39;file_count&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;層級跨度=</span><span class="si">{</span><span class="n">c1_result</span><span class="p">[</span><span class="s1">&#39;layer_span&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;工時=</span><span class="si">{</span><span class="n">c1_result</span><span class="p">[</span><span class="s1">&#39;estimated_hours&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">h&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="k">if</span> <span class="n">c2_result</span><span class="p">[</span><span class="s2">&#34;is_incomplete&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">blocking_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Incomplete Ticket 檢測失敗: 缺少必要元素&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">if</span> <span class="n">c3_result</span><span class="p">[</span><span class="s2">&#34;is_ambiguous&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">blocking_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Ambiguous Responsibility 檢測失敗: 職責不明確&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="s2">&#34;passed&#34;</span><span class="p">:</span> <span class="n">passed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="s2">&#34;c1_result&#34;</span><span class="p">:</span> <span class="n">c1_result</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="s2">&#34;c2_result&#34;</span><span class="p">:</span> <span class="n">c2_result</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="s2">&#34;c3_result&#34;</span><span class="p">:</span> <span class="n">c3_result</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="s2">&#34;suggestions&#34;</span><span class="p">:</span> <span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="s2">&#34;blocking_issues&#34;</span><span class="p">:</span> <span class="n">blocking_issues</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="c1"># Hook 輸出範例:</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"># - 品質閘門通過 → 記錄日誌，允許繼續</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"># - 品質閘門失敗 → 阻止提交，提供修正建議</span></span></span></code></pre></div><p><strong>v0.12.G.4 實作準備清單</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 實作檔案路徑提取函式</li>
<li><input disabled="" type="checkbox"> 實作層級判斷函式（基於 v0.12.G.1 第 6.2 節）</li>
<li><input disabled="" type="checkbox"> 實作步驟數量計算和工時估算函式</li>
<li><input disabled="" type="checkbox"> 實作章節檢測函式</li>
<li><input disabled="" type="checkbox"> 實作驗收條件提取和驗證函式</li>
<li><input disabled="" type="checkbox"> 實作測試檔案檢測函式</li>
<li><input disabled="" type="checkbox"> 實作 Hook 整合（PostEdit Hook）</li>
<li><input disabled="" type="checkbox"> 撰寫 Hook 測試（模擬 Ticket 檔案）</li>
<li><input disabled="" type="checkbox"> 建立檢測結果日誌機制</li>
<li><input disabled="" type="checkbox"> 提供修正建議生成器</li>
</ul>
<hr>
<h3 id="25-ticket-大小標準和範例">2.5 Ticket 大小標準和範例</h3>
<p><strong>Ticket 大小標準總結</strong>：</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>職責</th>
          <th>檔案</th>
          <th>測試</th>
          <th>行數</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Interface 定義</strong></td>
          <td>1 個</td>
          <td>1 個</td>
          <td>0 個</td>
          <td>&lt; 30 行</td>
          <td>定義契約</td>
      </tr>
      <tr>
          <td><strong>Value Object 實作</strong></td>
          <td>1 個</td>
          <td>1 個</td>
          <td>2-3 個</td>
          <td>30-40 行</td>
          <td>簡單邏輯</td>
      </tr>
      <tr>
          <td><strong>Repository 實作</strong></td>
          <td>2-3 個</td>
          <td>1 個</td>
          <td>4-6 個</td>
          <td>50-80 行</td>
          <td>CRUD 操作</td>
      </tr>
      <tr>
          <td><strong>Use Case 實作</strong></td>
          <td>2-3 個</td>
          <td>2-3 個</td>
          <td>5-8 個</td>
          <td>40-70 行</td>
          <td>業務邏輯</td>
      </tr>
      <tr>
          <td><strong>整合驗證</strong></td>
          <td>2-3 個</td>
          <td>2-3 個</td>
          <td>3-5 個</td>
          <td>30-50 行</td>
          <td>端到端測試</td>
      </tr>
  </tbody>
</table>
<h3 id="26-拆分決策樹">2.6 拆分決策樹</h3>
<p>當面對一個大任務時，使用以下決策樹判斷如何拆分：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">任務評估
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├─ 是否為單一職責且檔案數 ≤ 1？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│  └─ Yes → 建立單一 Ticket（簡單）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│  └─ No  → 繼續評估
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├─ 可按 Clean Architecture 分層拆分？
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│  └─ Yes → 拆分為 4 種 Ticket
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│  │        1. Interface 定義 Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">│  │        2. 測試驗證 Ticket
</span></span><span class="line"><span class="ln">11</span><span class="cl">│  │        3. 具體實作 Ticket
</span></span><span class="line"><span class="ln">12</span><span class="cl">│  │        4. 整合連接 Ticket
</span></span><span class="line"><span class="ln">13</span><span class="cl">│  └─ No  → 繼續評估
</span></span><span class="line"><span class="ln">14</span><span class="cl">│
</span></span><span class="line"><span class="ln">15</span><span class="cl">├─ 可按功能模組拆分？
</span></span><span class="line"><span class="ln">16</span><span class="cl">│  └─ Yes → 拆分為多個功能 Ticket
</span></span><span class="line"><span class="ln">17</span><span class="cl">│  │        範例：User 模組、Book 模組、Order 模組
</span></span><span class="line"><span class="ln">18</span><span class="cl">│  └─ No  → 繼續評估
</span></span><span class="line"><span class="ln">19</span><span class="cl">│
</span></span><span class="line"><span class="ln">20</span><span class="cl">├─ 可按步驟順序拆分？
</span></span><span class="line"><span class="ln">21</span><span class="cl">│  └─ Yes → 拆分為多個步驟 Ticket
</span></span><span class="line"><span class="ln">22</span><span class="cl">│  │        範例：步驟1 資料準備、步驟2 邏輯處理、步驟3 結果輸出
</span></span><span class="line"><span class="ln">23</span><span class="cl">│  └─ No  → 繼續評估
</span></span><span class="line"><span class="ln">24</span><span class="cl">│
</span></span><span class="line"><span class="ln">25</span><span class="cl">└─ 可按 CRUD 操作拆分？
</span></span><span class="line"><span class="ln">26</span><span class="cl">   └─ Yes → 拆分為 Create、Read、Update、Delete Ticket
</span></span><span class="line"><span class="ln">27</span><span class="cl">   └─ No  → 重新分析任務結構或尋求 PM 協助</span></span></code></pre></div><h3 id="27-ticket-拆分檢查清單">2.7 Ticket 拆分檢查清單</h3>
<p><strong>拆分前檢查</strong>（5 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 任務描述是否清晰明確？</li>
<li><input disabled="" type="checkbox"> 是否有明確的需求來源？</li>
<li><input disabled="" type="checkbox"> 是否理解任務的技術實作方式？</li>
<li><input disabled="" type="checkbox"> 是否識別所有依賴關係？</li>
<li><input disabled="" type="checkbox"> 是否評估職責數量、檔案數、測試數、程式碼行數？</li>
</ul>
<p><strong>拆分策略檢查</strong>（5 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 是否嘗試按 Clean Architecture 分層拆分？</li>
<li><input disabled="" type="checkbox"> 是否嘗試按功能模組拆分？</li>
<li><input disabled="" type="checkbox"> 是否嘗試按步驟順序拆分？</li>
<li><input disabled="" type="checkbox"> 拆分後的 Ticket 是否都可獨立驗收？</li>
<li><input disabled="" type="checkbox"> 拆分後的 Ticket 是否符合複雜度標準（職責、檔案、測試、行數）？</li>
</ul>
<p><strong>拆分後檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 每個 Ticket 是否都有明確的驗收條件？</li>
<li><input disabled="" type="checkbox"> Ticket 間的依賴關係是否明確標註？</li>
<li><input disabled="" type="checkbox"> 是否有並行執行的機會？</li>
<li><input disabled="" type="checkbox"> 所有 Ticket 加總是否完整覆蓋原任務？</li>
</ul>
<hr>
<h2 id="第三章ticket-生命週期管理">第三章：Ticket 生命週期管理</h2>
<h3 id="31-ticket-狀態定義">3.1 Ticket 狀態定義</h3>
<p>Ticket 從建立到關閉經歷 4 個狀態：</p>
<h4 id="狀態-1待執行pending">狀態 1：待執行（Pending）</h4>
<p><strong>定義</strong>：Ticket 已建立並通過準備度檢查，等待開發者執行。</p>
<p><strong>進入條件</strong>：</p>
<ul>
<li>Ticket 建立完成</li>
<li>驗收條件明確</li>
<li>依賴 Ticket 已完成（如適用）</li>
</ul>
<p><strong>可執行動作</strong>：</p>
<ul>
<li>指派開發者</li>
<li>調整優先級</li>
<li>開始執行（轉為「進行中」）</li>
</ul>
<p><strong>流程特性</strong>：快速流轉至 In Progress</p>
<h4 id="狀態-2進行中in-progress">狀態 2：進行中（In Progress）</h4>
<p><strong>定義</strong>：開發者正在執行 Ticket。</p>
<p><strong>進入條件</strong>：</p>
<ul>
<li>開發者開始執行</li>
<li>標記 Ticket 為「進行中」</li>
</ul>
<p><strong>可執行動作</strong>：</p>
<ul>
<li>持續更新進度</li>
<li>遇到問題記錄到 Ticket 日誌</li>
<li>完成後提交 review（轉為「Review 中」）</li>
<li>發現問題暫停（轉回「待執行」）</li>
</ul>
<p><strong>流程特性</strong>：快速完成並進入 Review</p>
<h4 id="狀態-3review-中in-review">狀態 3：Review 中（In Review）</h4>
<p><strong>定義</strong>：Ticket 已完成執行，等待 review 驗收。</p>
<p><strong>進入條件</strong>：</p>
<ul>
<li>開發者認為已完成</li>
<li>提交 review 請求</li>
</ul>
<p><strong>可執行動作</strong>：</p>
<ul>
<li>Review 檢查驗收條件</li>
<li>通過驗收（轉為「已完成」）</li>
<li>發現問題（轉回「進行中」，建立修正 Ticket）</li>
</ul>
<p><strong>流程特性</strong>：快速 Review 並決定結果</p>
<h4 id="狀態-4已完成completed">狀態 4：已完成（Completed）</h4>
<p><strong>定義</strong>：Ticket 通過 review 驗收，已關閉。</p>
<p><strong>進入條件</strong>：</p>
<ul>
<li>所有驗收條件滿足</li>
<li>Review 通過</li>
<li>相關測試 100% 通過</li>
</ul>
<p><strong>可執行動作</strong>：</p>
<ul>
<li>更新主版本日誌 Ticket 索引</li>
<li>更新 todolist 任務狀態</li>
<li>記錄完成時間和實際工時</li>
</ul>
<p><strong>停留時間</strong>：永久（已歸檔）</p>
<p><strong>生命週期流程圖</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">建立 Ticket
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">待執行（Pending）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">進行中（In Progress） ←─┐
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓                   │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Review 中（In Review）   │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ↓                   │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  通過？                │
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ├─ Yes → 已完成（Completed）
</span></span><span class="line"><span class="ln">11</span><span class="cl">    └─ No  ──────────────┘
</span></span><span class="line"><span class="ln">12</span><span class="cl">       （建立修正 Ticket，重新執行）</span></span></code></pre></div><h3 id="32-ticket-建立標準">3.2 Ticket 建立標準</h3>
<h4 id="ticket-標題格式">Ticket 標題格式</h4>
<p><strong>格式</strong>：<code>Ticket #N: [動詞] [目標]</code></p>
<p><strong>動詞選擇</strong>：</p>
<ul>
<li><strong>定義</strong>：定義 Interface、定義 Entity</li>
<li><strong>撰寫</strong>：撰寫測試、撰寫文檔</li>
<li><strong>實作</strong>：實作 Repository、實作 Use Case</li>
<li><strong>整合</strong>：整合 Service、整合 Module</li>
<li><strong>修復</strong>：修復 Bug、修復測試</li>
<li><strong>重構</strong>：重構邏輯、重構架構</li>
</ul>
<p><strong>範例</strong>：</p>
<ul>
<li><code>Ticket #1: 定義 IBookRepository 介面</code>- <code>Ticket #2: 撰寫 BookRepository 測試</code>- <code>Ticket #3: 實作 SQLiteBookRepository</code>- <code>Ticket #1: BookRepository</code>（缺少動詞）- 反例：<code>做一下 Repository</code>（不明確）</li>
</ul>
<h4 id="ticket-描述內容">Ticket 描述內容</h4>
<p>完整的 Ticket 必須包含以下 5 個核心欄位：</p>
<p><strong>Ticket 建立模板</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: [動詞] [目標]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>[為什麼需要這個 Ticket？來自哪個需求或問題？]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>[這個 Ticket 要達成什麼？明確且可驗證]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> [具體步驟 1]
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> [具體步驟 2]
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> [具體步驟 3]
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> [可驗證的條件 1]
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> [可驗證的條件 2]
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> [可驗證的條件 3]
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">-</span> [設計文件連結]
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> [需求文件連結]
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#X</span> (必須先完成)
</span></span><span class="line"><span class="ln">25</span><span class="cl">- Ticket <span class="ni">#Y</span> (可並行)</span></span></code></pre></div><p><strong>欄位說明</strong>：</p>
<p><strong>1. 背景</strong>：</p>
<ul>
<li>說明為什麼需要這個 Ticket</li>
<li>連結到需求或設計決策</li>
<li>提供上下文資訊</li>
</ul>
<p><strong>2. 目標</strong>：</p>
<ul>
<li>一句話說明要達成什麼</li>
<li>必須明確且可驗證</li>
<li>避免模糊用語</li>
</ul>
<p><strong>3. 步驟</strong>：</p>
<ul>
<li>列出具體執行步驟（3-5 步）</li>
<li>步驟必須可操作</li>
<li>幫助開發者快速理解如何執行</li>
</ul>
<p><strong>4. 驗收條件</strong>：</p>
<ul>
<li>列出所有可驗證的條件（3-5 項）</li>
<li>條件必須客觀可檢查</li>
<li>使用 checkbox 格式</li>
</ul>
<p><strong>5. 參考文件</strong>：</p>
<ul>
<li>連結到設計文件</li>
<li>連結到需求規格</li>
<li>連結到相關 Ticket</li>
</ul>
<p><strong>6. 依賴 Ticket</strong>：</p>
<ul>
<li>列出必須先完成的 Ticket</li>
<li>列出可並行的 Ticket</li>
<li>明確依賴關係</li>
</ul>
<h3 id="33-ticket-執行流程">3.3 Ticket 執行流程</h3>
<h4 id="步驟-1領取-ticket">步驟 1：領取 Ticket</h4>
<ul>
<li>開發者從「待執行」清單選擇 Ticket</li>
<li>確認依賴 Ticket 已完成</li>
<li>標記 Ticket 為「進行中」</li>
</ul>
<h5 id="步驟-2閱讀-ticket">步驟 2：閱讀 Ticket</h5>
<ul>
<li>閱讀背景和目標</li>
<li>檢查參考文件</li>
<li>理解驗收條件</li>
</ul>
<h6 id="步驟-3執行步驟">步驟 3：執行步驟</h6>
<ul>
<li>按照步驟執行</li>
<li>遇到問題記錄到 Ticket 日誌</li>
<li>持續更新進度</li>
</ul>
<h6 id="步驟-4自我檢查">步驟 4：自我檢查</h6>
<ul>
<li>逐項檢查驗收條件</li>
<li>確保所有條件滿足</li>
<li>執行相關測試</li>
</ul>
<h6 id="步驟-5提交-review">步驟 5：提交 Review</h6>
<ul>
<li>標記所有驗收條件為完成</li>
<li>標記 Ticket 為「Review 中」</li>
<li>通知 Reviewer</li>
</ul>
<h6 id="步驟-6處理-review-結果">步驟 6：處理 Review 結果</h6>
<ul>
<li>如果通過：Ticket 標記為「已完成」</li>
<li>如果未通過：根據 Review 意見修正，重新執行</li>
</ul>
<h3 id="34-ticket-驗收標準">3.4 Ticket 驗收標準</h3>
<p>驗收條件必須符合 SMART 原則：</p>
<p><strong>S - Specific（具體）</strong>：</p>
<ul>
<li>反例：「功能運作正常」（太模糊）- 正例：「呼叫 getBookByIsbn(&lsquo;123&rsquo;) 回傳正確的 Book 物件」</li>
</ul>
<p><strong>M - Measurable（可測量）</strong>：</p>
<ul>
<li>反例：「程式碼品質良好」（無法測量）- 正例：「dart analyze 0 錯誤，測試覆蓋率 &gt; 80%」</li>
</ul>
<p><strong>A - Achievable（可達成）</strong>：</p>
<ul>
<li>反例：「整合所有第三方 API」（範圍太大）- 正例：「整合 Google Books API 的書籍搜尋功能」</li>
</ul>
<p><strong>R - Relevant（相關）</strong>：</p>
<ul>
<li>反例：「優化 UI 顏色」（與 Repository Ticket 無關）- 正例：「Repository 實作完成，測試通過」</li>
</ul>
<p><strong>T - Time-bound（有明確完成標準）</strong>：</p>
<ul>
<li>反例：「未來會完成」（無明確標準）- 正例：「符合所有驗收條件即完成」</li>
</ul>
<h3 id="35-ticket-關閉條件">3.5 Ticket 關閉條件</h3>
<p>Ticket 必須滿足以下所有條件才能關閉：</p>
<p><strong>強制條件</strong>（5 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 所有驗收條件打勾完成</li>
<li><input disabled="" type="checkbox"> Review 通過</li>
<li><input disabled="" type="checkbox"> 相關測試 100% 通過</li>
<li><input disabled="" type="checkbox"> dart analyze 0 錯誤</li>
<li><input disabled="" type="checkbox"> 工作日誌已更新</li>
</ul>
<p><strong>建議條件</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 程式碼符合專案規範</li>
<li><input disabled="" type="checkbox"> 無技術債務產生</li>
<li><input disabled="" type="checkbox"> 文檔同步更新</li>
</ul>
<h3 id="36-生命週期管理檢查清單">3.6 生命週期管理檢查清單</h3>
<p><strong>Ticket 建立檢查</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 標題格式正確（動詞 + 目標）</li>
<li><input disabled="" type="checkbox"> 5 個核心欄位完整</li>
<li><input disabled="" type="checkbox"> 驗收條件符合 SMART 原則</li>
</ul>
<p><strong>Ticket 執行檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 依賴 Ticket 已完成</li>
<li><input disabled="" type="checkbox"> 開發者理解目標和步驟</li>
<li><input disabled="" type="checkbox"> 進度持續更新</li>
<li><input disabled="" type="checkbox"> 遇到問題即時記錄</li>
</ul>
<p><strong>Ticket Review 檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 所有驗收條件滿足</li>
<li><input disabled="" type="checkbox"> 測試 100% 通過</li>
<li><input disabled="" type="checkbox"> 程式碼品質符合標準</li>
<li><input disabled="" type="checkbox"> 文檔同步更新</li>
</ul>
<p><strong>Ticket 關閉檢查</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Review 通過</li>
<li><input disabled="" type="checkbox"> 工作日誌已更新</li>
<li><input disabled="" type="checkbox"> 主版本日誌索引已更新</li>
</ul>
<hr>
<h2 id="第四章即時-review-機制">第四章：即時 Review 機制</h2>
<h3 id="41-一邊實作一邊-review-的原則">4.1 一邊實作一邊 review 的原則</h3>
<p><strong>傳統 Review 問題</strong>：</p>
<p>傳統的「實作完成後才 review」存在以下問題：</p>
<ol>
<li><strong>發現問題太晚</strong>：實作偏差已形成，修正成本高</li>
<li><strong>返工時間長</strong>：需要大幅修改已完成的程式碼</li>
<li><strong>士氣影響大</strong>：開發者認為「白做了」，影響積極性</li>
<li><strong>無法並行</strong>：必須等所有任務完成才能 review</li>
</ol>
<p><strong>即時 Review 原則</strong>：</p>
<p>「一邊實作一邊 review」的核心原則：</p>
<ol>
<li><strong>每完成一個 Ticket 觸發 review</strong></li>
<li><strong>review 快速完成，聚焦核心問題</strong></li>
<li><strong>只 review 當前 Ticket，不累積</strong></li>
<li><strong>發現問題立即建立修正 Ticket</strong></li>
</ol>
<p><strong>效益</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>傳統 Review</th>
          <th>即時 Review</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>問題發現時機</strong></td>
          <td>實作完成後（數天後）</td>
          <td>每個 Ticket 完成後（即時）</td>
      </tr>
      <tr>
          <td><strong>修正成本</strong></td>
          <td>高（需大幅修改）</td>
          <td>低（只需修正單一 Ticket）</td>
      </tr>
      <tr>
          <td><strong>開發者體驗</strong></td>
          <td>挫折感（白做了）</td>
          <td>即時回饋（快速修正）</td>
      </tr>
      <tr>
          <td><strong>可並行性</strong></td>
          <td>低（必須等全部完成）</td>
          <td>高（可持續並行開發）</td>
      </tr>
  </tbody>
</table>
<h3 id="42-review-觸發時機">4.2 Review 觸發時機</h3>
<p><strong>觸發條件</strong>：</p>
<p>Review 在以下時機觸發：</p>
<ol>
<li><strong>每完成一個 Ticket</strong>：開發者標記 Ticket 為「Review 中」</li>
<li><strong>每完成 3-5 個 Ticket</strong>：觸發階段性 review（可選）</li>
<li><strong>每完成一個模組</strong>：觸發模組整合 review（可選）</li>
</ol>
<p><strong>主要觸發機制</strong>：</p>
<p><strong>每完成一個 Ticket 觸發</strong>：</p>
<ul>
<li>開發者完成 Ticket，自我檢查驗收條件</li>
<li>標記 Ticket 為「Review 中」</li>
<li>Reviewer 收到通知，開始 review</li>
<li>Review 快速完成，聚焦核心問題</li>
</ul>
<p><strong>不等待</strong>：</p>
<ul>
<li>不等待整個任務完成才 review</li>
<li>不累積多個 Ticket 一起 review</li>
<li>不延後 review 時機</li>
</ul>
<h3 id="43-review-檢查項目">4.3 Review 檢查項目</h3>
<p>Review 分為 4 大類，共 16 項檢查項：</p>
<h4 id="類別-1功能正確性檢查4-項">類別 1：功能正確性檢查（4 項）</h4>
<h5 id="檢查項-1ticket-描述的功能是否實現">檢查項 1：Ticket 描述的功能是否實現？</h5>
<ul>
<li>檢查方法：對照 Ticket 目標和實際程式碼</li>
<li>通過標準：功能完全符合 Ticket 描述</li>
</ul>
<h6 id="檢查項-2驗收條件是否全部滿足">檢查項 2：驗收條件是否全部滿足？</h6>
<ul>
<li>檢查方法：逐項檢查驗收條件 checkbox</li>
<li>通過標準：所有條件都打勾且確實滿足</li>
</ul>
<h6 id="檢查項-3是否有未處理的邊界情況">檢查項 3：是否有未處理的邊界情況？</h6>
<ul>
<li>檢查方法：檢查 null、empty、異常輸入處理</li>
<li>通過標準：所有邊界情況都有處理</li>
</ul>
<h6 id="檢查項-4錯誤處理是否完整">檢查項 4：錯誤處理是否完整？</h6>
<ul>
<li>檢查方法：檢查 try-catch、異常拋出</li>
<li>通過標準：所有異常都有妥善處理</li>
</ul>
<h4 id="類別-2架構合規性檢查4-項">類別 2：架構合規性檢查（4 項）</h4>
<h5 id="檢查項-5是否符合-clean-architecture-分層原則">檢查項 5：是否符合 Clean Architecture 分層原則？</h5>
<ul>
<li>檢查方法：檢查檔案位置、模組分層</li>
<li>通過標準：符合 Domain/Application/Infrastructure/Presentation 分層</li>
</ul>
<h6 id="檢查項-6依賴方向是否正確內層不依賴外層">檢查項 6：依賴方向是否正確（內層不依賴外層）？</h6>
<ul>
<li>檢查方法：檢查 import 語句</li>
<li>通過標準：所有依賴都指向內層或 Interface</li>
</ul>
<h6 id="檢查項-7是否使用-interface-driven-開發">檢查項 7：是否使用 Interface-Driven 開發？</h6>
<ul>
<li>檢查方法：檢查是否依賴 Interface 而非具體實作</li>
<li>通過標準：外層依賴 Interface，不直接依賴實作類別</li>
</ul>
<h6 id="檢查項-8是否有架構債務產生">檢查項 8：是否有架構債務產生？</h6>
<ul>
<li>檢查方法：檢查是否有違反 SOLID 原則的程式碼</li>
<li>通過標準：無明顯架構債務</li>
</ul>
<h4 id="類別-3測試通過率檢查4-項">類別 3：測試通過率檢查（4 項）</h4>
<h5 id="檢查項-9相關單元測試是否-100-通過">檢查項 9：相關單元測試是否 100% 通過？</h5>
<ul>
<li>檢查方法：執行 <code>dart test</code> 或 <code>flutter test</code></li>
<li>通過標準：所有測試通過，0 失敗</li>
</ul>
<h6 id="檢查項-10相關整合測試是否-100-通過">檢查項 10：相關整合測試是否 100% 通過？</h6>
<ul>
<li>檢查方法：執行整合測試</li>
<li>通過標準：所有整合測試通過</li>
</ul>
<h6 id="檢查項-11測試覆蓋率是否達標">檢查項 11：測試覆蓋率是否達標？</h6>
<ul>
<li>檢查方法：檢查測試覆蓋率報告</li>
<li>通過標準：覆蓋率 &gt; 80%（建議）</li>
</ul>
<h6 id="檢查項-12是否有測試被-skip">檢查項 12：是否有測試被 skip？</h6>
<ul>
<li>檢查方法：搜尋 <code>skip:</code> 或 <code>.skip</code></li>
<li>通過標準：無 skip 的測試</li>
</ul>
<h4 id="類別-4文檔同步性檢查4-項">類別 4：文檔同步性檢查（4 項）</h4>
<h5 id="檢查項-13ticket-工作日誌是否更新">檢查項 13：Ticket 工作日誌是否更新？</h5>
<ul>
<li>檢查方法：檢查對應的 Ticket 日誌檔案</li>
<li>通過標準：記錄執行過程和決策</li>
</ul>
<h6 id="檢查項-14設計決策是否記錄">檢查項 14：設計決策是否記錄？</h6>
<ul>
<li>檢查方法：檢查設計決策日誌</li>
<li>通過標準：重要決策都有記錄</li>
</ul>
<h6 id="檢查項-15api-文檔是否同步">檢查項 15：API 文檔是否同步？</h6>
<ul>
<li>檢查方法：檢查程式碼註解和 dartdoc</li>
<li>通過標準：公開 API 都有完整文檔註解</li>
</ul>
<h6 id="檢查項-16readme-是否需要更新">檢查項 16：README 是否需要更新？</h6>
<ul>
<li>檢查方法：檢查是否有新增功能或變更使用方式</li>
<li>通過標準：如需更新則已更新</li>
</ul>
<h3 id="44-偏差糾正流程">4.4 偏差糾正流程</h3>
<p>當 Review 發現問題時，執行以下偏差糾正流程：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Review 發現偏差
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">暫停當前 Ticket
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">記錄偏差問題（描述、影響、根因）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">分析根因
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ├─ 理解錯誤？ → 釐清需求，重新執行
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    ├─ 技術問題？ → 尋求技術支援
</span></span><span class="line"><span class="ln">10</span><span class="cl">    └─ 架構問題？ → 修正架構設計
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">建立修正 Ticket
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">14</span><span class="cl">修正 Ticket 執行
</span></span><span class="line"><span class="ln">15</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">16</span><span class="cl">再次 Review
</span></span><span class="line"><span class="ln">17</span><span class="cl">    ├─ 通過 → 標記原 Ticket 為「已完成」
</span></span><span class="line"><span class="ln">18</span><span class="cl">    └─ 未通過 → 重複偏差糾正流程
</span></span><span class="line"><span class="ln">19</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">20</span><span class="cl">總結經驗教訓（更新檢查清單）</span></span></code></pre></div><p><strong>偏差記錄格式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">### Review 偏差記錄 #N
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**發現時間**</span>：2025-10-10 14:30
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**偏差描述**</span>：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">實作的 Repository 方法簽名與 Interface 不一致
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gs">**影響範圍**</span>：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> SQLiteBookRepository.getBookByIsbn 方法
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 相關測試需要調整
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gs">**根因分析**</span>：
</span></span><span class="line"><span class="ln">14</span><span class="cl">開發者未參考最新的 Interface 定義
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gs">**糾正措施**</span>：
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 建立修正 Ticket：修正 Repository 方法簽名
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> 更新相關測試
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 加入檢查清單：實作前必須確認 Interface 最新版本
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gs">**責任歸屬**</span>：
</span></span><span class="line"><span class="ln">23</span><span class="cl">開發者（未確認 Interface）+ Reviewer（未及時發現）
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gs">**經驗教訓**</span>：
</span></span><span class="line"><span class="ln">26</span><span class="cl">實作前必須先閱讀最新的 Interface 定義</span></span></code></pre></div><h3 id="45-review-記錄格式">4.5 Review 記錄格式</h3>
<p>每次 Review 都必須記錄，格式如下：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">### Review 記錄 - Ticket #N
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**Review 時間**</span>：2025-10-10 15:00
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**Reviewer**</span>：張三
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gs">**Ticket 標題**</span>：實作 SQLiteBookRepository
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gs">**Review 結果**</span>：通過 / 未通過
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gs">**檢查項目**</span>：
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">- [x]</span> 功能正確性（4/4 通過）
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">- [x]</span> 架構合規性（4/4 通過）
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">- [x]</span> 測試通過率（4/4 通過）
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [x]</span> 文檔同步性（4/4 通過）
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gs">**發現問題**</span>：
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 無（如果有，列出問題清單）
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gs">**建議改善**</span>：
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> 建議補充更多邊界情況測試（可選）
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gs">**Review 時間**</span>：8 分鐘
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gs">**下一步行動**</span>：
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> 標記 Ticket 為「已完成」
</span></span><span class="line"><span class="ln">31</span><span class="cl">- 更新主版本日誌索引</span></span></code></pre></div><h3 id="46-即時-review-檢查清單">4.6 即時 Review 檢查清單</h3>
<p><strong>Review 前準備</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Reviewer 收到 review 通知</li>
<li><input disabled="" type="checkbox"> Ticket 標記為「Review 中」</li>
<li><input disabled="" type="checkbox"> 所有驗收條件已打勾</li>
</ul>
<p><strong>Review 執行</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 逐項檢查 16 個檢查項目</li>
<li><input disabled="" type="checkbox"> 記錄 Review 結果</li>
<li><input disabled="" type="checkbox"> 發現問題建立偏差記錄</li>
<li><input disabled="" type="checkbox"> Review 快速完成，聚焦核心問題</li>
</ul>
<p><strong>Review 後處理</strong>（2 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 通過：標記 Ticket 為「已完成」</li>
<li><input disabled="" type="checkbox"> 未通過：建立修正 Ticket</li>
</ul>
<p><strong>持續改善</strong>（5 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 總結經驗教訓</li>
<li><input disabled="" type="checkbox"> 更新檢查清單（如需要）</li>
<li><input disabled="" type="checkbox"> 優化 Review 流程</li>
<li><input disabled="" type="checkbox"> 提升 Review 效率</li>
<li><input disabled="" type="checkbox"> 降低偏差發生率</li>
</ul>
<h2 id="第五章文件管理策略">第五章：文件管理策略</h2>
<h3 id="51-避免工作日誌臃腫的三層文件結構">5.1 避免工作日誌臃腫的三層文件結構</h3>
<h4 id="問題分析v0127-工作日誌臃腫案例">問題分析：v0.12.7 工作日誌臃腫案例</h4>
<p><strong>v0.12.7 工作日誌問題</strong>：</p>
<ul>
<li><strong>檔案大小</strong>：6000 行</li>
<li><strong>問題根因</strong>：
<ul>
<li>所有 Ticket 日誌都寫在主版本日誌</li>
<li>設計迭代過程全部記錄在同一檔案</li>
<li>缺乏分層管理機制</li>
</ul>
</li>
</ul>
<p><strong>影響</strong>：</p>
<ul>
<li>開發者難以快速找到關鍵資訊</li>
<li>檔案過大導致編輯器效能下降</li>
<li>歷史記錄難以追溯</li>
</ul>
<h4 id="三層文件結構設計">三層文件結構設計</h4>
<p>為了解決工作日誌臃腫問題，採用三層文件結構：</p>
<h5 id="第-1-層主版本日誌main-version-log">第 1 層：主版本日誌（Main Version Log）</h5>
<p><strong>檔案命名</strong>：<code>vX.Y.Z-main.md</code>
<strong>目標大小</strong>：500-800 行
<strong>內容範圍</strong>：</p>
<ul>
<li>版本目標和規劃</li>
<li>Ticket 索引（只記錄 Ticket 編號和標題）</li>
<li>設計決策索引（連結到決策日誌）</li>
<li>版本總結</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.12.7 主版本日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 版本目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>實現書籍資訊豐富化功能
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">## Ticket 索引
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookInfoEnrichmentService 介面 → [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-001.md</span>)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2:</span> 撰寫 BookInfoEnrichmentService 測試 → [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-002.md</span>)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#3:</span> 實作 GoogleBooksEnrichmentService → [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-003.md</span>)
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">## 設計決策索引
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#1:</span> 選擇 Strategy Pattern 處理多資料源 → [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策1</span>)
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 決策 <span class="ni">#2:</span> 使用 Riverpod 管理服務註冊 → [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策2</span>)
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">## 版本總結
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">-</span> 完成 12 個 Ticket
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> 總開發時間：4.5 小時
</span></span><span class="line"><span class="ln">18</span><span class="cl">- 測試覆蓋率：92%</span></span></code></pre></div><h3 id="第-2-層ticket-工作日誌ticket-work-log">第 2 層：Ticket 工作日誌（Ticket Work Log）</h3>
<p><strong>檔案命名</strong>：<code>vX.Y.Z-ticket-NNN.md</code>
<strong>目標大小</strong>：100-200 行
<strong>內容範圍</strong>：</p>
<ul>
<li>Ticket 描述（背景、目標、步驟、驗收條件）</li>
<li>執行過程記錄</li>
<li>遇到的問題和解決方案</li>
<li>Review 結果</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># Ticket #1 工作日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## Ticket 描述
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>定義 IBookInfoEnrichmentService 介面
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>根據 UC-05 需求，需要建立書籍資訊豐富化服務的介面契約
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`IBookInfoEnrichmentService`</span> 介面，定義書籍資訊豐富化的契約
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立介面檔案
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`enrichBook`</span> 方法
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`enrichProgress`</span> 方法
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">4.</span> 撰寫文檔註解
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">- [x]</span> 介面檔案建立在正確位置
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [x]</span> 方法簽名完整且明確
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [x]</span> 包含完整的文檔註解
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [x]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">## 執行過程
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`lib/domains/import/services/i_book_info_enrichment_service.dart`</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`enrichBook`</span> 方法：接收 ISBN，回傳豐富化後的 Book
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`enrichProgress`</span> 方法：回傳豐富化進度 Stream
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">4.</span> 補充文檔註解說明每個方法的用途
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu">## Review 結果
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu"></span>通過（8 分鐘）
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> 功能正確性：4/4 通過
</span></span><span class="line"><span class="ln">33</span><span class="cl">- 架構合規性：4/4 通過</span></span></code></pre></div><h3 id="第-3-層設計決策日誌design-decision-log">第 3 層：設計決策日誌（Design Decision Log）</h3>
<p><strong>檔案命名</strong>：<code>vX.Y.Z-design-decisions.md</code>
<strong>目標大小</strong>：300-500 行
<strong>內容範圍</strong>：</p>
<ul>
<li>重要設計決策記錄</li>
<li>技術選型理由</li>
<li>架構調整說明</li>
<li>決策的影響分析</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.12.7 設計決策日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 決策 #1: 選擇 Strategy Pattern 處理多資料源
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 決策時間
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>2025-10-09 14:30
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 決策背景
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>書籍資訊豐富化需要支援多個資料來源（Google Books、Open Library、豆瓣讀書）
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 可選方案
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="gs">**方案 A**</span>：使用 if-else 判斷資料來源
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">2.</span> <span class="gs">**方案 B**</span>：使用 Strategy Pattern 封裝每個資料來源
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">3.</span> <span class="gs">**方案 C**</span>：使用 Chain of Responsibility Pattern
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 選擇理由
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span>選擇方案 B（Strategy Pattern）：
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">-</span> 每個資料來源獨立封裝，符合 Open-Closed Principle
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> 新增資料來源只需新增 Strategy，不修改現有程式碼
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> 可以動態切換資料來源策略
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu"></span><span class="k">-</span> 建立 <span class="sb">`IEnrichmentStrategy`</span> 介面
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">-</span> 實作 <span class="sb">`GoogleBooksStrategy`</span>、<span class="sb">`OpenLibraryStrategy`</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> <span class="sb">`BookInfoEnrichmentService`</span> 依賴 <span class="sb">`IEnrichmentStrategy`</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu">### 實作 Ticket
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#4:</span> 定義 IEnrichmentStrategy 介面
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#5:</span> 實作 GoogleBooksStrategy
</span></span><span class="line"><span class="ln">31</span><span class="cl">- Ticket <span class="ni">#6:</span> 實作 OpenLibraryStrategy</span></span></code></pre></div><h4 id="三層結構效益">三層結構效益</h4>
<p><strong>效益總結</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>v0.12.7 單一檔案（6000 行）</th>
          <th>三層結構（800+200*N+400 行）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>可讀性</strong></td>
          <td>低（難以快速找到資訊）</td>
          <td>高（分層清晰）</td>
      </tr>
      <tr>
          <td><strong>維護性</strong></td>
          <td>低（修改影響範圍大）</td>
          <td>高（獨立修改）</td>
      </tr>
      <tr>
          <td><strong>查找效率</strong></td>
          <td>低（需搜尋整個檔案）</td>
          <td>高（索引快速定位）</td>
      </tr>
      <tr>
          <td><strong>協作友善</strong></td>
          <td>低（衝突機率高）</td>
          <td>高（不同檔案並行編輯）</td>
      </tr>
  </tbody>
</table>
<p><strong>改善數據</strong>：</p>
<ul>
<li>主版本日誌：6000 行 → 800 行（減少 87%）</li>
<li>Ticket 日誌：分散到 12 個檔案，每個 150 行</li>
<li>設計決策日誌：獨立 400 行</li>
</ul>
<h3 id="52-設計迭代文件管理">5.2 設計迭代文件管理</h3>
<h4 id="設計迭代的挑戰">設計迭代的挑戰</h4>
<p><strong>問題</strong>：
設計過程中會產生多個版本的設計文件：</p>
<ul>
<li>初版設計（可能有缺陷）</li>
<li>修正版設計（發現問題後調整）</li>
<li>最終設計（經過 review 確認）</li>
</ul>
<p><strong>如果不管理</strong>：</p>
<ul>
<li>開發者不知道哪個是最終版本</li>
<li>過時的設計誤導後續開發</li>
<li>設計演進過程難以追溯</li>
</ul>
<h4 id="版本管理策略">版本管理策略</h4>
<h5 id="策略-1設計檔案版本標記">策略 1：設計檔案版本標記</h5>
<p>在設計決策日誌中明確標記版本狀態：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策 #1: 選擇 Strategy Pattern 處理多資料來源
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策狀態**</span>：最終決策（已實作）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**版本歷史**</span>：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> v1 (2025-10-09 14:30): 初版設計 - 使用 if-else 判斷（已廢棄）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> v2 (2025-10-09 15:00): 修正版 - 改用 Strategy Pattern（最終版本）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**當前版本**</span>: v2</span></span></code></pre></div><h3 id="策略-2最終設計明確標記">策略 2：最終設計明確標記</h3>
<p>在主版本日誌中明確標記最終設計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 設計決策索引
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#1:</span> 選擇 Strategy Pattern（最終決策）→ [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策1</span>)
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 決策 <span class="ni">#2:</span> 使用 Riverpod 管理服務（最終決策）→ [<span class="nt">詳細日誌</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策2</span>)
</span></span><span class="line"><span class="ln">4</span><span class="cl">- <span class="gd">~~決策 #3: 使用 GetIt 管理服務~~</span>（已廢棄，改用決策 <span class="ni">#2</span>）</span></span></code></pre></div><h3 id="策略-3廢棄設計保留但標記">策略 3：廢棄設計保留但標記</h3>
<p>不刪除過時設計，而是標記為廢棄並說明原因：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## ~~決策 #3: 使用 GetIt 管理服務~~（已廢棄）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**廢棄原因**</span>：
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">發現 Riverpod 更適合 Flutter 3.x，提供更好的型別安全
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gs">**廢棄時間**</span>：2025-10-09 16:00
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gs">**替代決策**</span>：決策 <span class="ni">#2</span>（使用 Riverpod）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**保留原因**</span>：
</span></span><span class="line"><span class="ln">11</span><span class="cl">記錄設計演進過程，避免重複評估相同方案</span></span></code></pre></div><h3 id="53-開發者查找設計的指引">5.3 開發者查找設計的指引</h3>
<h4 id="設計查找流程">設計查找流程</h4>
<h5 id="步驟-1從主版本日誌開始">步驟 1：從主版本日誌開始</h5>
<p>開發者接到任務時，先查看主版本日誌：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gh"># v0.12.7 主版本日誌
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">## 快速導航
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">版本目標</span>](<span class="na">#版本目標</span>)
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> [<span class="nt">Ticket 索引</span>](<span class="na">#Ticket索引</span>)
</span></span><span class="line"><span class="ln">6</span><span class="cl">- [<span class="nt">設計決策索引</span>](<span class="na">#設計決策索引</span>)（開發者從這裡開始）</span></span></code></pre></div><h3 id="步驟-2查看設計決策索引">步驟 2：查看設計決策索引</h3>
<p>根據任務相關的領域，找到對應的設計決策：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 設計決策索引
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">### 服務層設計
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#1:</span> 選擇 Strategy Pattern（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策1</span>)
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 決策 <span class="ni">#2:</span> 使用 Riverpod 管理服務（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策2</span>)
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="gu">### 資料層設計
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="gu"></span>- 決策 <span class="ni">#4:</span> 選擇 SQLite 儲存（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策4</span>)</span></span></code></pre></div><h4 id="步驟-3閱讀設計決策詳細內容">步驟 3：閱讀設計決策詳細內容</h4>
<p>點擊連結進入設計決策日誌，閱讀詳細內容：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策 #1: 選擇 Strategy Pattern 處理多資料來源
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策狀態**</span>：最終決策（已實作）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gu">### 決策背景
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu"></span>...
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 選擇理由
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span>...
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span>...
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 實作 Ticket
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#4:</span> 定義 IEnrichmentStrategy 介面
</span></span><span class="line"><span class="ln">16</span><span class="cl">- Ticket <span class="ni">#5:</span> 實作 GoogleBooksStrategy</span></span></code></pre></div><h4 id="步驟-4查看相關-ticket">步驟 4：查看相關 Ticket</h4>
<p>根據設計決策中的「實作 Ticket」，查看具體實作細節：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gh"># Ticket #4 工作日誌
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">## Ticket 描述
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span>定義 IEnrichmentStrategy 介面
</span></span><span class="line"><span class="ln">5</span><span class="cl">...</span></span></code></pre></div><h4 id="設計文件索引規範">設計文件索引規範</h4>
<p><strong>主版本日誌索引格式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 設計決策索引
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### [領域名稱]
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#N:</span> [決策摘要]（最終決策 / 已廢棄）→ [詳細日誌連結]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gs">**索引規則**</span>：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">1.</span> 按領域分組（服務層、資料層、UI 層）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">2.</span> 標記決策狀態（最終決策、已廢棄）
</span></span><span class="line"><span class="ln">10</span><span class="cl">3. 提供直接連結到詳細日誌</span></span></code></pre></div><p><strong>設計決策日誌格式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策 #N: [決策標題]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策狀態**</span>：最終決策 / 已廢棄
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**相關 Ticket**</span>：
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#X:</span> [Ticket 標題]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#Y:</span> [Ticket 標題]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**決策背景**</span>：...
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gs">**選擇理由**</span>：...
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gs">**影響範圍**</span>：...</span></span></code></pre></div><h3 id="54-文件管理檢查清單">5.4 文件管理檢查清單</h3>
<p><strong>主版本日誌檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 版本目標明確且可驗證</li>
<li><input disabled="" type="checkbox"> Ticket 索引完整（所有 Ticket 都有連結）</li>
<li><input disabled="" type="checkbox"> 設計決策索引清晰（按領域分組）</li>
<li><input disabled="" type="checkbox"> 檔案大小控制在 500-800 行</li>
</ul>
<p><strong>Ticket 工作日誌檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 檔案命名符合規範（vX.Y.Z-ticket-NNN.md）</li>
<li><input disabled="" type="checkbox"> Ticket 描述完整（6 個核心欄位）</li>
<li><input disabled="" type="checkbox"> 執行過程記錄清楚</li>
<li><input disabled="" type="checkbox"> 檔案大小控制在 100-200 行</li>
</ul>
<p><strong>設計決策日誌檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 每個決策都有明確的狀態標記</li>
<li><input disabled="" type="checkbox"> 廢棄決策保留但標記廢棄原因</li>
<li><input disabled="" type="checkbox"> 版本歷史記錄完整</li>
<li><input disabled="" type="checkbox"> 檔案大小控制在 300-500 行</li>
</ul>
<p><strong>設計文件索引檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 主版本日誌有「快速導航」區塊</li>
<li><input disabled="" type="checkbox"> 設計決策索引按領域分組</li>
<li><input disabled="" type="checkbox"> 所有連結都正確指向目標</li>
<li><input disabled="" type="checkbox"> 最終決策明確標記</li>
</ul>
<hr>
<h2 id="第六章與敏捷重構和-tdd-的整合">第六章：與敏捷重構和 TDD 的整合</h2>
<h3 id="61-ticket-機制與三重文件原則的關係">6.1 Ticket 機制與三重文件原則的關係</h3>
<h4 id="三重文件原則回顧">三重文件原則回顧</h4>
<p>本專案採用三重文件原則（CHANGELOG + todolist + work-log）進行全方位進度管理：</p>
<p><strong>1. CHANGELOG.md</strong>：</p>
<ul>
<li>面向用戶的版本功能描述</li>
<li>只記錄「做了什麼」，不記錄「怎麼做」</li>
</ul>
<p><strong>2. todolist.md</strong>：</p>
<ul>
<li>記錄整個開發過程所有待處理任務</li>
<li>任務狀態追蹤（待執行/進行中/已完成）</li>
</ul>
<p><strong>3. work-log/</strong>：</p>
<ul>
<li>完整的技術實作細節和決策過程</li>
<li>TDD 四階段進度追蹤</li>
</ul>
<h4 id="ticket-機制如何融入三重文件">Ticket 機制如何融入三重文件</h4>
<p><strong>Ticket 機制定位</strong>：</p>
<p>Ticket 機制是「work-log 層」的細化管理工具：</p>
<ul>
<li><strong>work-log 主版本日誌</strong> = Ticket 索引 + 版本總覽</li>
<li><strong>work-log Ticket 日誌</strong> = 單一 Ticket 的執行記錄</li>
<li><strong>work-log 設計決策日誌</strong> = 設計迭代過程記錄</li>
</ul>
<p><strong>整合關係</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">CHANGELOG.md（版本功能描述）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↑ 提取功能變動
</span></span><span class="line"><span class="ln">3</span><span class="cl">work-log/vX.Y.Z-main.md（主版本日誌）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓ Ticket 索引
</span></span><span class="line"><span class="ln">5</span><span class="cl">work-log/vX.Y.Z-ticket-NNN.md（Ticket 日誌）
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ↑ 完成狀態
</span></span><span class="line"><span class="ln">7</span><span class="cl">todolist.md（任務狀態追蹤）</span></span></code></pre></div><p><strong>協作流程</strong>：</p>
<ol>
<li>
<p><strong>PM 規劃階段</strong>：</p>
<ul>
<li>在 <code>todolist.md</code> 建立任務項目</li>
<li>在 <code>vX.Y.Z-main.md</code> 規劃 Ticket 索引</li>
</ul>
</li>
<li>
<p><strong>開發執行階段</strong>：</p>
<ul>
<li>建立 <code>vX.Y.Z-ticket-NNN.md</code> Ticket 日誌</li>
<li>執行 Ticket，記錄過程</li>
<li>完成後更新 <code>todolist.md</code> 狀態</li>
</ul>
</li>
<li>
<p><strong>版本發布階段</strong>：</p>
<ul>
<li>從 <code>vX.Y.Z-main.md</code> 提取功能變動</li>
<li>更新 <code>CHANGELOG.md</code> 版本說明</li>
</ul>
</li>
</ol>
<h3 id="62-ticket-與-tdd-階段的對應關係">6.2 Ticket 與 TDD 階段的對應關係</h3>
<h4 id="tdd-階段與-ticket-類型對應">TDD 階段與 Ticket 類型對應</h4>
<p><strong>Phase 1（功能設計）→ Interface 定義 Ticket</strong>：</p>
<p>Phase 1 產出的設計決策 → 轉化為 Interface 定義 Ticket：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Phase 1 產出：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">- IBookRepository 介面設計
</span></span><span class="line"><span class="ln">4</span><span class="cl">- IBookInfoEnrichmentService 介面設計
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">↓ 轉化為 Ticket
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">Ticket #1: 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln">9</span><span class="cl">Ticket #2: 定義 IBookInfoEnrichmentService 介面</span></span></code></pre></div><p><strong>Phase 2（測試設計）→ 測試撰寫 Ticket</strong>：</p>
<p>Phase 2 產出的測試設計 → 轉化為測試撰寫 Ticket：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Phase 2 產出：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">- BookRepository 測試用例設計
</span></span><span class="line"><span class="ln">4</span><span class="cl">- BookInfoEnrichmentService 測試用例設計
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">↓ 轉化為 Ticket
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">Ticket #3: 撰寫 BookRepository 測試
</span></span><span class="line"><span class="ln">9</span><span class="cl">Ticket #4: 撰寫 BookInfoEnrichmentService 測試</span></span></code></pre></div><p><strong>Phase 3（實作執行）→ 具體實作 Ticket + 整合 Ticket</strong>：</p>
<p>Phase 3 產出的實作 → 拆分為具體實作和整合 Ticket：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 3 產出：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">- SQLiteBookRepository 實作
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">- GoogleBooksEnrichmentService 實作
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">- 整合到 Use Case
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">↓ 轉化為 Ticket
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Ticket #5: 實作 SQLiteBookRepository
</span></span><span class="line"><span class="ln">10</span><span class="cl">Ticket #6: 實作 GoogleBooksEnrichmentService
</span></span><span class="line"><span class="ln">11</span><span class="cl">Ticket #7: 整合到 GetBookUseCase</span></span></code></pre></div><p><strong>Phase 4（重構優化）→ 重構 Ticket</strong>：</p>
<p>Phase 4 發現的重構需求 → 建立重構 Ticket：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Phase 4 發現：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl">- BookRepository 邏輯複雜，需要拆分
</span></span><span class="line"><span class="ln">4</span><span class="cl">- EnrichmentService 錯誤處理不完整
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">↓ 轉化為 Ticket
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">Ticket #8: 重構 BookRepository，拆分 CRUD 邏輯
</span></span><span class="line"><span class="ln">9</span><span class="cl">Ticket #9: 補充 EnrichmentService 錯誤處理</span></span></code></pre></div><h4 id="tdd-階段完成標準與-ticket-關係">TDD 階段完成標準與 Ticket 關係</h4>
<p><strong>Phase 1 完成標準</strong>：</p>
<ul>
<li>所有 Interface 定義 Ticket 建立並標記為「待執行」</li>
<li>設計決策日誌記錄完整</li>
</ul>
<p><strong>Phase 2 完成標準</strong>：</p>
<ul>
<li>所有測試撰寫 Ticket 建立並標記為「待執行」</li>
<li>測試覆蓋所有 Interface 方法</li>
</ul>
<p><strong>Phase 3 完成標準</strong>：</p>
<ul>
<li>所有具體實作 Ticket 完成並通過 review</li>
<li>所有整合 Ticket 完成並通過測試</li>
<li>測試 100% 通過</li>
</ul>
<p><strong>Phase 4 完成標準</strong>：</p>
<ul>
<li>所有重構 Ticket 完成並通過 review</li>
<li>程式碼品質達標，無技術債務</li>
</ul>
<h3 id="63-ticket-派工前的準備度檢查">6.3 Ticket 派工前的準備度檢查</h3>
<h4 id="準備度檢查清單">準備度檢查清單</h4>
<p>在建立和派工 Ticket 前，必須檢查以下準備度：</p>
<p><strong>設計準備度</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 相關設計決策已完成且標記為「最終決策」</li>
<li><input disabled="" type="checkbox"> Interface 定義已完成（如適用）</li>
<li><input disabled="" type="checkbox"> 設計決策日誌已更新</li>
<li><input disabled="" type="checkbox"> 沒有未解決的設計問題</li>
</ul>
<p><strong>依賴準備度</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 依賴的 Ticket 已完成</li>
<li><input disabled="" type="checkbox"> 依賴的 Interface 已定義</li>
<li><input disabled="" type="checkbox"> 依賴的測試已撰寫（如適用）</li>
</ul>
<p><strong>資源準備度</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 開發者已理解 Ticket 目標</li>
<li><input disabled="" type="checkbox"> 開發環境已準備就緒</li>
<li><input disabled="" type="checkbox"> 相關文檔已閱讀</li>
</ul>
<p><strong>品質準備度</strong>（2 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 驗收條件明確且可驗證</li>
<li><input disabled="" type="checkbox"> Review 機制已建立</li>
</ul>
<h4 id="準備度不足的處理">準備度不足的處理</h4>
<p><strong>發現準備度不足時</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Ticket 派工前檢查
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">準備度檢查
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    ├─ 通過 → 派工執行
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    └─ 不通過 → 暫停派工
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    分析缺失項目
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        ├─ 設計不完整 → 補充設計決策
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        ├─ 依賴未完成 → 等待依賴 Ticket
</span></span><span class="line"><span class="ln">10</span><span class="cl">        └─ 資源不足 → 準備環境和文檔
</span></span><span class="line"><span class="ln">11</span><span class="cl">        ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">    重新檢查準備度</span></span></code></pre></div><h3 id="64-ticket-完成後的文件更新">6.4 Ticket 完成後的文件更新</h3>
<h4 id="更新流程">更新流程</h4>
<p>每完成一個 Ticket，必須更新以下文件：</p>
<h5 id="步驟-1更新-ticket-工作日誌">步驟 1：更新 Ticket 工作日誌</h5>
<p>在 <code>vX.Y.Z-ticket-NNN.md</code> 記錄：</p>
<ul>
<li>執行過程</li>
<li>Review 結果</li>
<li>遇到的問題和解決方案</li>
</ul>
<h6 id="步驟-2更新主版本日誌">步驟 2：更新主版本日誌</h6>
<p>在 <code>vX.Y.Z-main.md</code> 更新：</p>
<ul>
<li>Ticket 索引（標記為已完成）</li>
<li>如有新的設計決策，更新設計決策索引</li>
</ul>
<h6 id="步驟-3更新-todolist">步驟 3：更新 todolist</h6>
<p>在 <code>todolist.md</code> 更新：</p>
<ul>
<li>標記對應任務為「已完成」</li>
<li>如發現新任務，新增到待辦清單</li>
</ul>
<h6 id="步驟-4更新設計決策日誌如適用">步驟 4：更新設計決策日誌（如適用）</h6>
<p>在 <code>vX.Y.Z-design-decisions.md</code> 更新：</p>
<ul>
<li>如有設計調整，記錄新的決策版本</li>
<li>如廢棄舊決策，標記廢棄原因</li>
</ul>
<h3 id="65-整合檢查清單">6.5 整合檢查清單</h3>
<p><strong>Ticket 建立檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 來自 TDD Phase 1-4 的產出</li>
<li><input disabled="" type="checkbox"> Ticket 類型符合 Clean Architecture 分層</li>
<li><input disabled="" type="checkbox"> Ticket 已連結到設計決策日誌</li>
<li><input disabled="" type="checkbox"> Ticket 準備度檢查通過</li>
</ul>
<p><strong>Ticket 執行檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 執行過程記錄到 Ticket 日誌</li>
<li><input disabled="" type="checkbox"> 完成後更新主版本日誌索引</li>
<li><input disabled="" type="checkbox"> 完成後更新 todolist 狀態</li>
<li><input disabled="" type="checkbox"> 如有設計調整，更新設計決策日誌</li>
</ul>
<p><strong>文件同步檢查</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 日誌大小控制在 100-200 行</li>
<li><input disabled="" type="checkbox"> 主版本日誌索引保持最新</li>
<li><input disabled="" type="checkbox"> 設計決策狀態正確標記</li>
<li><input disabled="" type="checkbox"> todolist 任務狀態與實際一致</li>
</ul>
<hr>
<h2 id="第七章實務案例和模板">第七章：實務案例和模板</h2>
<h3 id="71-v0127-工作日誌臃腫問題分析">7.1 v0.12.7 工作日誌臃腫問題分析</h3>
<h4 id="原始問題描述">原始問題描述</h4>
<p><strong>v0.12.7 版本資訊</strong>：</p>
<ul>
<li><strong>功能範圍</strong>：書籍資訊豐富化功能</li>
<li><strong>工作日誌大小</strong>：6000 行</li>
<li><strong>開發時間</strong>：5 天</li>
<li><strong>Ticket 數量</strong>：未明確拆分</li>
</ul>
<p><strong>臃腫問題根因</strong>：</p>
<h5 id="根因-1缺乏-ticket-拆分機制">根因 1：缺乏 Ticket 拆分機制</h5>
<ul>
<li>所有實作細節都寫在主版本日誌</li>
<li>沒有建立獨立的 Ticket 日誌</li>
<li>設計迭代過程全部記錄在同一檔案</li>
</ul>
<h6 id="根因-2設計迭代未分層管理">根因 2：設計迭代未分層管理</h6>
<ul>
<li>初版設計、修正版設計、最終設計混在一起</li>
<li>廢棄的設計沒有明確標記</li>
<li>開發者難以找到最終設計</li>
</ul>
<h6 id="根因-3缺乏文件索引機制">根因 3：缺乏文件索引機制</h6>
<ul>
<li>沒有 Ticket 索引</li>
<li>沒有設計決策索引</li>
<li>開發者需搜尋整個檔案才能找到資訊</li>
</ul>
<h4 id="問題影響分析">問題影響分析</h4>
<h5 id="影響-1開發效率降低">影響 1：開發效率降低</h5>
<ul>
<li>開發者花費 30% 時間搜尋設計資訊</li>
<li>編輯器效能下降（檔案過大）</li>
<li>協作衝突頻繁（多人編輯同一檔案）</li>
</ul>
<h6 id="影響-2品質風險增加">影響 2：品質風險增加</h6>
<ul>
<li>使用過時的設計實作（找錯版本）</li>
<li>Review 困難（難以理解上下文）</li>
<li>技術債務累積（沒時間重構）</li>
</ul>
<h6 id="影響-3知識傳承困難">影響 3：知識傳承困難</h6>
<ul>
<li>新成員難以快速理解設計演進</li>
<li>歷史決策難以追溯</li>
<li>重複犯錯（不知道為什麼廢棄某設計）</li>
</ul>
<h3 id="72-基於-ticket-機制的改善方案">7.2 基於 Ticket 機制的改善方案</h3>
<h4 id="改善方案設計">改善方案設計</h4>
<p><strong>方案目標</strong>：
將 6000 行主版本日誌改善為三層文件結構，控制在約 2000 行總量（800+200*6+400）。</p>
<p><strong>改善步驟</strong>：</p>
<h5 id="步驟-1ticket-拆分回溯分析">步驟 1：Ticket 拆分（回溯分析）</h5>
<p>分析 v0.12.7 的開發過程，回溯拆分為 12 個 Ticket：</p>
<p><strong>Domain 層 Ticket</strong>（3 個）：</p>
<ul>
<li>Ticket #1: 定義 IBookInfoEnrichmentService 介面</li>
<li>Ticket #2: 定義 IEnrichmentStrategy 介面</li>
<li>Ticket #3: 定義 EnrichmentProgress Value Object</li>
</ul>
<p><strong>Application 層 Ticket</strong>（2 個）：</p>
<ul>
<li>Ticket #4: 定義 EnrichBookUseCase 介面</li>
<li>Ticket #5: 實作 EnrichBookInteractor</li>
</ul>
<p><strong>Infrastructure 層 Ticket</strong>（4 個）：</p>
<ul>
<li>Ticket #6: 實作 GoogleBooksStrategy</li>
<li>Ticket #7: 實作 OpenLibraryStrategy</li>
<li>Ticket #8: 實作 BookInfoEnrichmentService</li>
<li>Ticket #9: 實作 Riverpod Provider 註冊</li>
</ul>
<p><strong>測試 Ticket</strong>（3 個）：</p>
<ul>
<li>Ticket #10: 撰寫 BookInfoEnrichmentService 測試</li>
<li>Ticket #11: 撰寫 EnrichBookInteractor 測試</li>
<li>Ticket #12: 撰寫整合測試</li>
</ul>
<h6 id="步驟-2建立三層文件結構">步驟 2：建立三層文件結構</h6>
<p><strong>主版本日誌</strong>（800 行）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.12.7 主版本日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 快速導航
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">版本目標</span>](<span class="na">#版本目標</span>)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> [<span class="nt">Ticket 索引</span>](<span class="na">#Ticket索引</span>)（12 個 Ticket）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> [<span class="nt">設計決策索引</span>](<span class="na">#設計決策索引</span>)（5 個決策）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> [<span class="nt">版本總結</span>](<span class="na">#版本總結</span>)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">## Ticket 索引
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### Domain 層
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookInfoEnrichmentService 介面 → [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-001.md</span>)
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2:</span> 定義 IEnrichmentStrategy 介面 → [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-002.md</span>)
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#3:</span> 定義 EnrichmentProgress Value Object → [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-003.md</span>)
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### Application 層
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#4:</span> 定義 EnrichBookUseCase 介面 → [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-004.md</span>)
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#5:</span> 實作 EnrichBookInteractor → [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-ticket-005.md</span>)
</span></span><span class="line"><span class="ln">19</span><span class="cl">...</span></span></code></pre></div><p><strong>Ticket 工作日誌</strong>（200 行 × 12 = 2400 行，分散到 12 個檔案）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># Ticket #1 工作日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## Ticket 描述
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>定義 IBookInfoEnrichmentService 介面
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>根據 UC-05 需求，需要建立書籍資訊豐富化服務的介面契約
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">...
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">## 執行過程
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立介面檔案
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">2.</span> 定義方法簽名
</span></span><span class="line"><span class="ln">13</span><span class="cl">...
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">## Review 結果
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span>通過（8 分鐘）</span></span></code></pre></div><p><strong>設計決策日誌</strong>（400 行）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.12.7 設計決策日誌
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 決策 #1: 選擇 Strategy Pattern 處理多資料源
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**決策狀態**</span>：最終決策（已實作）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gs">**版本歷史**</span>：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> v1 (2025-10-09 14:30): 初版設計 - 使用 if-else 判斷（已廢棄）
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> v2 (2025-10-09 15:00): 修正版 - 改用 Strategy Pattern（最終版本）
</span></span><span class="line"><span class="ln">11</span><span class="cl">...</span></span></code></pre></div><h3 id="步驟-3建立設計文件索引">步驟 3：建立設計文件索引</h3>
<p>在主版本日誌建立清晰的索引：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 設計決策索引
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">### 服務層設計
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#1:</span> 選擇 Strategy Pattern（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策1</span>)
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 決策 <span class="ni">#2:</span> 使用 Riverpod 管理服務（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策2</span>)
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="gu">### 資料來源設計
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 決策 <span class="ni">#3:</span> Google Books 為主要資料源（最終決策）→ [<span class="nt">詳細</span>](<span class="na">/record/ticket_v2/v0.12.7-design-decisions.md#決策3</span>)
</span></span><span class="line"><span class="ln">9</span><span class="cl">- <span class="gd">~~決策 #4: 豆瓣讀書為備用資料源~~</span>（已廢棄，API 限制）</span></span></code></pre></div><h4 id="改善效果評估">改善效果評估</h4>
<p><strong>檔案大小改善</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>改善前</th>
          <th>改善後</th>
          <th>改善幅度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>主版本日誌</strong></td>
          <td>6000 行</td>
          <td>800 行</td>
          <td>-87%</td>
      </tr>
      <tr>
          <td><strong>Ticket 日誌</strong></td>
          <td>0（混在主日誌）</td>
          <td>200 行 × 12 = 2400 行</td>
          <td>分散管理</td>
      </tr>
      <tr>
          <td><strong>設計決策日誌</strong></td>
          <td>0（混在主日誌）</td>
          <td>400 行</td>
          <td>獨立管理</td>
      </tr>
      <tr>
          <td><strong>總計</strong></td>
          <td>6000 行（單一檔案）</td>
          <td>3600 行（14 個檔案）</td>
          <td>-40% 且分散</td>
      </tr>
  </tbody>
</table>
<p><strong>查找效率改善</strong>：</p>
<table>
  <thead>
      <tr>
          <th>任務</th>
          <th>改善前</th>
          <th>改善後</th>
          <th>效率提升</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>找到特定 Ticket</strong></td>
          <td>搜尋 6000 行</td>
          <td>查索引 → 直接開啟</td>
          <td>90% ↑</td>
      </tr>
      <tr>
          <td><strong>找到最終設計</strong></td>
          <td>搜尋全文</td>
          <td>查設計決策索引</td>
          <td>85% ↑</td>
      </tr>
      <tr>
          <td><strong>理解設計演進</strong></td>
          <td>難以追溯</td>
          <td>查版本歷史</td>
          <td>80% ↑</td>
      </tr>
  </tbody>
</table>
<h3 id="73-ticket-模板集合">7.3 Ticket 模板集合</h3>
<h4 id="模板-1interface-定義-ticket">模板 1：Interface 定義 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: 定義 [Interface 名稱] 介面
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>根據 [需求編號] 需求，需要建立 [功能描述] 的介面契約
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>建立 <span class="sb">`[Interface 名稱]`</span> 介面，定義 [功能描述] 的契約
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 在 <span class="sb">`lib/domains/[domain]/[layer]/`</span> 建立 <span class="sb">`[interface_file].dart`</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 定義 <span class="sb">`[method1]`</span> 方法簽名
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 定義 <span class="sb">`[method2]`</span> 方法簽名
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 撰寫文檔註解
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> Interface 檔案建立在正確位置
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 所有方法簽名完整且明確
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 輸入輸出類型定義清楚
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> 包含完整的文檔註解
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> [設計文件連結]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> [需求文件連結]
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span>- 無（Interface 定義通常是第一個 Ticket）</span></span></code></pre></div><h4 id="模板-2具體實作-ticket">模板 2：具體實作 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: 實作 [類別名稱]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>根據 [Interface 名稱] 介面契約，實作 [功能描述]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>實作 <span class="sb">`[類別名稱]`</span>，提供 [功能描述]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立 <span class="sb">`[類別名稱]`</span> 類別
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 實作 <span class="sb">`[method1]`</span> 方法
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 實作 <span class="sb">`[method2]`</span> 方法
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 處理異常情況
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 實作所有 [Interface 名稱] 方法
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整（列出可能的異常）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> 單元測試 100% 通過
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> [Interface 定義 Ticket]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> [設計決策日誌]
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span>- Ticket <span class="ni">#X:</span> 定義 [Interface 名稱] 介面（必須先完成）</span></span></code></pre></div><h4 id="模板-3測試撰寫-ticket">模板 3：測試撰寫 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: 撰寫 [類別名稱] 測試
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>為 <span class="sb">`[類別名稱]`</span> 撰寫完整的測試用例，確保功能正確性
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>撰寫 <span class="sb">`[類別名稱]`</span> 的完整測試用例
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 建立測試檔案 <span class="sb">`[test_file]_test.dart`</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 撰寫 <span class="sb">`[method1]`</span> 測試（正常流程 + 異常處理）
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 撰寫 <span class="sb">`[method2]`</span> 測試（正常流程 + 異常處理）
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 測試檔案建立在 <span class="sb">`test/unit/[path]/`</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 至少 [N] 個測試用例
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 覆蓋所有方法的正常流程
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> 覆蓋所有方法的異常處理
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 所有測試 100% 通過
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> [Interface 定義 Ticket]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> [測試設計文件]
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span>- Ticket <span class="ni">#X:</span> 定義 [Interface 名稱] 介面（必須先完成）</span></span></code></pre></div><h4 id="模板-4整合連接-ticket">模板 4：整合連接 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: 整合 [Module A] 到 [Module B]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>將 <span class="sb">`[Module A]`</span> 整合到 <span class="sb">`[Module B]`</span>，實現完整流程
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>連接 [Module A] 和 [Module B]，實現端到端功能
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 修改 <span class="sb">`[Module B]`</span> 注入 <span class="sb">`[Module A Interface]`</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 在 <span class="sb">`[method]`</span> 方法中呼叫 <span class="sb">`[Module A].[method]`</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 處理 [Module A] 的異常
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 撰寫整合測試驗證端到端流程
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> <span class="sb">`[Module B]`</span> 正確注入 <span class="sb">`[Module A Interface]`</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 端到端流程正常運作
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> 整合測試 100% 通過
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 異常處理完整
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> [Module A 實作 Ticket]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> [Module B 實作 Ticket]
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">-</span> [整合設計文件]
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">-</span> Ticket <span class="ni">#X:</span> 實作 [Module A]（必須先完成）
</span></span><span class="line"><span class="ln">29</span><span class="cl">- Ticket <span class="ni">#Y:</span> 實作 [Module B]（必須先完成）</span></span></code></pre></div><h4 id="模板-5重構-ticket">模板 5：重構 Ticket</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Ticket #N: 重構 [功能描述]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 背景
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span>Phase 4 發現 [問題描述]，需要重構改善
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 目標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>重構 <span class="sb">`[類別名稱]`</span>，[改善目標]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 步驟
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 分析當前程式碼問題
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">2.</span> 設計重構方案
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 執行重構（保持測試通過）
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">4.</span> 驗證功能未受影響
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">5.</span> 確保所有測試通過
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 驗收條件
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 重構完成，程式碼品質改善
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 所有測試 100% 通過（功能未受影響）
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 無新的技術債務產生
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 參考文件
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> [Phase 4 重構報告]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> [程式碼品質標準]
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 依賴 Ticket
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span>- 無（重構可獨立進行）</span></span></code></pre></div><h3 id="74-常見錯誤和解決方案">7.4 常見錯誤和解決方案</h3>
<h4 id="錯誤-1ticket-拆分過細或過粗">錯誤 1：Ticket 拆分過細或過粗</h4>
<p><strong>問題描述</strong>：</p>
<ul>
<li><strong>過細</strong>：Ticket 的職責過於單一，失去獨立交付價值</li>
<li><strong>過粗</strong>：Ticket 包含過多職責（&gt;5 個）或檔案（&gt;5 個），失去即時 review 的效益</li>
</ul>
<p><strong>解決方案</strong>：</p>
<ul>
<li><strong>標準複雜度</strong>：控制在單一職責或少數相關職責</li>
<li><strong>過細時</strong>：合併相關的小 Ticket 形成完整職責</li>
<li><strong>過粗時</strong>：按 Clean Architecture 分層或步驟拆分</li>
</ul>
<p><strong>範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">反例 - 過細拆分：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#1:</span> 建立檔案（職責不完整）
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2:</span> 定義 class（職責不完整）
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#3:</span> 撰寫註解（職責不完整）
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">正例 - 合理拆分：
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">- Ticket <span class="ni">#1:</span> 定義 Interface（包含建立檔案、定義 class、撰寫註解 - 完整職責）</span></span></code></pre></div><h4 id="錯誤-2驗收條件模糊">錯誤 2：驗收條件模糊</h4>
<p><strong>問題描述</strong>：
驗收條件使用主觀描述，無法客觀驗證。</p>
<p><strong>錯誤範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">反例 - 模糊的驗收條件：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">- [ ]</span> 功能運作正常
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">- [ ]</span> 程式碼品質良好
</span></span><span class="line"><span class="ln">5</span><span class="cl">- [ ] 效能可接受</span></span></code></pre></div><p><strong>正確範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">正例 - 明確的驗收條件：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">- [ ]</span> 呼叫 getBookByIsbn(&#39;123&#39;) 回傳正確的 Book 物件
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">- [ ]</span> dart analyze 0 錯誤，測試覆蓋率 &gt; 80%
</span></span><span class="line"><span class="ln">5</span><span class="cl">- [ ] API 回應時間 <span class="p">&lt;</span> <span class="nt">500ms</span><span class="err">（</span><span class="na">在</span> <span class="na">100</span> <span class="na">筆資料下</span><span class="err">）</span></span></span></code></pre></div><p><strong>解決方案</strong>：
使用 SMART 原則撰寫驗收條件（Specific, Measurable, Achievable, Relevant, Time-bound）。</p>
<h4 id="錯誤-3忽略-ticket-依賴關係">錯誤 3：忽略 Ticket 依賴關係</h4>
<p><strong>問題描述</strong>：
沒有明確標註 Ticket 間的依賴關係，導致執行順序錯誤。</p>
<p><strong>錯誤範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">反例 - 缺少依賴標註：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#1:</span> 實作 Repository
</span></span><span class="line"><span class="ln">4</span><span class="cl">- Ticket <span class="ni">#2:</span> 定義 Repository Interface</span></span></code></pre></div><p><strong>正確範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">正例 - 明確依賴關係：
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#1:</span> 定義 IBookRepository 介面
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> Ticket <span class="ni">#2:</span> 實作 SQLiteBookRepository
</span></span><span class="line"><span class="ln">5</span><span class="cl">  依賴：Ticket <span class="ni">#1</span>（必須先完成）</span></span></code></pre></div><p><strong>解決方案</strong>：</p>
<ul>
<li>Interface 定義 Ticket 優先於實作 Ticket</li>
<li>測試撰寫 Ticket 可與實作 Ticket 並行</li>
<li>整合 Ticket 必須等待所有依賴 Ticket 完成</li>
</ul>
<h4 id="錯誤-4review-累積延後">錯誤 4：Review 累積延後</h4>
<p><strong>問題描述</strong>：
等待多個 Ticket 完成後才一起 review，失去即時 review 的效益。</p>
<p><strong>錯誤做法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">完成 Ticket #1 → 繼續 Ticket #2 → 繼續 Ticket #3 → 一起 Review
</span></span><span class="line"><span class="ln">2</span><span class="cl">（問題：Ticket #1 的錯誤到 Review 時才發現，已完成 #2、#3，需大量返工）</span></span></code></pre></div><p><strong>正確做法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">完成 Ticket #1 → Review #1 → 完成 Ticket #2 → Review #2 → 完成 Ticket #3 → Review #3
</span></span><span class="line"><span class="ln">2</span><span class="cl">（好處：每個 Ticket 完成後立即發現問題，修正成本低）</span></span></code></pre></div><p><strong>解決方案</strong>：</p>
<ul>
<li>每完成一個 Ticket 立即觸發 review</li>
<li>Review 快速完成，聚焦核心問題</li>
<li>發現問題立即建立修正 Ticket</li>
</ul>
<h4 id="錯誤-5工作日誌未同步更新">錯誤 5：工作日誌未同步更新</h4>
<p><strong>問題描述</strong>：
Ticket 完成後未更新工作日誌，導致文件與實際進度不一致。</p>
<p><strong>解決方案</strong>：
每完成一個 Ticket 必須執行以下更新：</p>
<ol>
<li>更新 Ticket 工作日誌（記錄執行過程和 Review 結果）</li>
<li>更新主版本日誌 Ticket 索引（標記為已完成）</li>
<li>更新 todolist 任務狀態</li>
<li>如有設計調整，更新設計決策日誌</li>
</ol>
<h3 id="75-完整-ticket-設計檢查清單">7.5 完整 Ticket 設計檢查清單</h3>
<h4 id="ticket-建立階段檢查清單10-項">Ticket 建立階段檢查清單（10 項）</h4>
<p><strong>基本資訊</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 標題格式正確（動詞 + 目標）</li>
<li><input disabled="" type="checkbox"> Ticket 編號唯一且連續</li>
<li><input disabled="" type="checkbox"> Ticket 分類明確（Interface/實作/測試/整合/重構）</li>
</ul>
<p><strong>內容完整性</strong>（6 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 背景欄位說明來源（需求或設計決策）</li>
<li><input disabled="" type="checkbox"> 目標欄位明確且可驗證</li>
<li><input disabled="" type="checkbox"> 步驟欄位具體且可操作（3-5 步）</li>
<li><input disabled="" type="checkbox"> 驗收條件符合 SMART 原則（3-5 項）</li>
<li><input disabled="" type="checkbox"> 參考文件連結正確</li>
<li><input disabled="" type="checkbox"> 依賴 Ticket 明確標註</li>
</ul>
<h4 id="ticket-執行階段檢查清單8-項">Ticket 執行階段檢查清單（8 項）</h4>
<p><strong>執行前檢查</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 依賴 Ticket 已完成</li>
<li><input disabled="" type="checkbox"> 相關設計決策已確認為「最終決策」</li>
<li><input disabled="" type="checkbox"> 開發者已理解目標和步驟</li>
</ul>
<p><strong>執行中檢查</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 標記為「進行中」</li>
<li><input disabled="" type="checkbox"> 執行過程記錄到 Ticket 日誌</li>
<li><input disabled="" type="checkbox"> 遇到問題即時記錄</li>
</ul>
<p><strong>執行後檢查</strong>（2 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 所有驗收條件已滿足</li>
<li><input disabled="" type="checkbox"> Ticket 標記為「Review 中」</li>
</ul>
<h4 id="ticket-review-階段檢查清單16-項">Ticket Review 階段檢查清單（16 項）</h4>
<p><strong>功能正確性</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 描述的功能是否實現？</li>
<li><input disabled="" type="checkbox"> 驗收條件是否全部滿足？</li>
<li><input disabled="" type="checkbox"> 是否有未處理的邊界情況？</li>
<li><input disabled="" type="checkbox"> 錯誤處理是否完整？</li>
</ul>
<p><strong>架構合規性</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 是否符合 Clean Architecture 分層原則？</li>
<li><input disabled="" type="checkbox"> 依賴方向是否正確（內層不依賴外層）？</li>
<li><input disabled="" type="checkbox"> 是否使用 Interface-Driven 開發？</li>
<li><input disabled="" type="checkbox"> 是否有架構債務產生？</li>
</ul>
<p><strong>測試通過率</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 相關單元測試是否 100% 通過？</li>
<li><input disabled="" type="checkbox"> 相關整合測試是否 100% 通過？</li>
<li><input disabled="" type="checkbox"> 測試覆蓋率是否達標（&gt; 80%）？</li>
<li><input disabled="" type="checkbox"> 是否有測試被 skip？</li>
</ul>
<p><strong>文檔同步性</strong>（4 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> Ticket 工作日誌是否更新？</li>
<li><input disabled="" type="checkbox"> 設計決策是否記錄？</li>
<li><input disabled="" type="checkbox"> API 文檔是否同步？</li>
<li><input disabled="" type="checkbox"> README 是否需要更新？</li>
</ul>
<h4 id="ticket-關閉階段檢查清單8-項">Ticket 關閉階段檢查清單（8 項）</h4>
<p><strong>強制條件</strong>（5 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 所有驗收條件打勾完成</li>
<li><input disabled="" type="checkbox"> Review 通過</li>
<li><input disabled="" type="checkbox"> 相關測試 100% 通過</li>
<li><input disabled="" type="checkbox"> dart analyze 0 錯誤</li>
<li><input disabled="" type="checkbox"> 工作日誌已更新</li>
</ul>
<p><strong>建議條件</strong>（3 項）：</p>
<ul>
<li><input disabled="" type="checkbox"> 程式碼符合專案規範</li>
<li><input disabled="" type="checkbox"> 無技術債務產生</li>
<li><input disabled="" type="checkbox"> 文檔同步更新</li>
</ul>
<hr>
<p><strong>文件版本</strong>: v1.0.0（完整版）
<strong>最後更新</strong>: 2025-10-10</p>
]]></content:encoded></item><item><title>敏捷開發方法論</title><link>https://tarrragon.github.io/blog/record/%E6%95%8F%E6%8D%B7%E9%96%8B%E7%99%BC%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E6%95%8F%E6%8D%B7%E9%96%8B%E7%99%BC%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="方法論概述">方法論概述&lt;/h2>
&lt;p>本方法論定義敏捷開發流程，採用主線程統籌分派、子代理人專責執行的協作模式，確保重構品質和進度控制。
但是在AI開發過程中，不會百分之百按照文件要求執行，所以需要配合hook機制在轉階段的時候檢查是否符合TDD流程，每一層的文件是否有確實填寫，指派任務給代理人之前，文件是否有提供完整的規格資料以及功能描述，不然執行上還是會有偏差，但是依照這套機制可以及早發現出錯的狀況及早糾正&lt;/p>
&lt;p>這篇方法論是延續之前敏捷重構方法論補充內容，但是因為內容更大更完整，所以獨立一篇文章而不是更新舊的敏捷重構方法論(實際專案中是取代舊的方法論)。&lt;/p>
&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;h3 id="1-主線程職責專一化">1. 主線程職責專一化&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>只負責任務分派和統籌管理&lt;/strong>，絕不親自執行具體重構工作&lt;/li>
&lt;li>依照工作日誌規劃分派任務給相應的子代理人&lt;/li>
&lt;li>維持敏捷開發的節奏和品質標準&lt;/li>
&lt;/ul>
&lt;h3 id="2-任務原子化拆分">2. 任務原子化拆分&lt;/h3>
&lt;ul>
&lt;li>每個小版本任務必須在 1-2 小時內完成&lt;/li>
&lt;li>任務影響檔案數量控制在 5 個以下&lt;/li>
&lt;li>每個任務都有明確的完成標準和驗收條件&lt;/li>
&lt;/ul>
&lt;h3 id="3-品質門檻強制執行">3. 品質門檻強制執行&lt;/h3>
&lt;ul>
&lt;li>每個任務完成後必須通過測試檢查&lt;/li>
&lt;li>重構代理人強制驗證程式碼品質&lt;/li>
&lt;li>100% 測試通過率是最低要求&lt;/li>
&lt;/ul>
&lt;h2 id="agent-角色定義">Agent 角色定義&lt;/h2>
&lt;h3 id="主線程-main-thread">主線程 (Main Thread)&lt;/h3>
&lt;p>&lt;strong>職責&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>依照主版本工作日誌分派任務&lt;/li>
&lt;li>維持敏捷開發節奏&lt;/li>
&lt;li>監控整體進度和品質&lt;/li>
&lt;li>處理升級請求和任務重新分派&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>禁止行為&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>親自閱讀或修改程式碼&lt;/li>
&lt;li>執行具體的重構工作&lt;/li>
&lt;li>繞過子代理人直接操作&lt;/li>
&lt;/ul>
&lt;h3 id="子代理人-sub-agents">子代理人 (Sub-Agents)&lt;/h3>
&lt;p>&lt;strong>職責&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>執行指派的具體重構任務&lt;/li>
&lt;li>回報任務完成狀態和結果&lt;/li>
&lt;li>發現任務規模過大時向上回報&lt;/li>
&lt;li>確保任務符合完成標準&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>專業分工&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>lavender-interface-designer&lt;/strong>: TDD Phase 1 功能設計&lt;/li>
&lt;li>&lt;strong>sage-test-architect&lt;/strong>: TDD Phase 2 測試設計&lt;/li>
&lt;li>&lt;strong>pepper-test-implementer&lt;/strong>: TDD Phase 3 實作規劃&lt;/li>
&lt;li>&lt;strong>cinnamon-refactor-owl&lt;/strong>: TDD Phase 4 重構執行&lt;/li>
&lt;li>&lt;strong>mint-format-specialist&lt;/strong>: 程式碼格式化和品質修正&lt;/li>
&lt;/ul>
&lt;h3 id="重構代理人-refactor-validator">重構代理人 (Refactor Validator)&lt;/h3>
&lt;p>&lt;strong>職責&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>檢查子代理人完成的重構工作&lt;/li>
&lt;li>驗證程式碼品質和功能正確性&lt;/li>
&lt;li>確認測試通過率和效能標準&lt;/li>
&lt;li>批准任務完成或要求修正&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>代理人&lt;/strong>: &lt;code>cinnamon-refactor-owl&lt;/code>&lt;/p>
&lt;h3 id="pm-代理人-project-manager">PM 代理人 (Project Manager)&lt;/h3>
&lt;p>&lt;strong>職責&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>任務規模過大時進行二次拆分&lt;/li>
&lt;li>更新小版本工作日誌&lt;/li>
&lt;li>重新規劃任務執行順序&lt;/li>
&lt;li>管理任務依賴關係&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>代理人&lt;/strong>: &lt;code>rosemary-project-manager&lt;/code>&lt;/p>
&lt;h3 id="文件代理人-documentation-agent">文件代理人 (Documentation Agent)&lt;/h3>
&lt;p>&lt;strong>職責&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>更新小版本工作日誌&lt;/li>
&lt;li>標記小版本完成狀態&lt;/li>
&lt;li>記錄任務執行結果&lt;/li>
&lt;li>同步文件狀態&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>代理人&lt;/strong>: &lt;code>memory-network-builder&lt;/code>&lt;/p>
&lt;h2 id="任務分派前強制檢查清單">任務分派前強制檢查清單&lt;/h2>
&lt;p>&lt;strong>重要&lt;/strong>: 在分派任何重構任務前，主線程必須先通過以下檢查清單。如果無法回答這些問題，必須優先建立準備文件。&lt;/p>
&lt;h3 id="準備度檢查問題">準備度檢查問題&lt;/h3>
&lt;h4 id="1-明確的文件規劃">1. 明確的文件規劃&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>API 規格是否完整？&lt;/strong> 代理人知道要實作什麼樣的介面和行為嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>設計文件是否具體？&lt;/strong> 有詳細的類別定義、方法簽名、使用範例嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>架構圖是否清晰？&lt;/strong> 代理人知道新系統的整體架構和元件關係嗎？&lt;/li>
&lt;/ul>
&lt;h4 id="2-測試先行策略">2. 測試先行策略&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>測試規格是否存在？&lt;/strong> 每個元件都有對應的測試規格和驗收標準嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>TDD 流程是否明確？&lt;/strong> 代理人知道要先寫什麼測試再實作嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>效能標準是否定義？&lt;/strong> 有明確的效能基準和測試方法嗎？&lt;/li>
&lt;/ul>
&lt;h4 id="3-實作目標明確性">3. 實作目標明確性&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>完成標準是否可測量？&lt;/strong> 代理人知道什麼程度才算任務完成嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>使用範例是否充足？&lt;/strong> 有具體的程式碼範例展示最終產品嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>移轉策略是否清楚？&lt;/strong> 有舊系統到新系統的具體移轉指引嗎？&lt;/li>
&lt;/ul>
&lt;h4 id="4-風險評估與應對">4. 風險評估與應對&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>潛在問題是否識別？&lt;/strong> 有預見可能的技術難點和解決方案嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>回滾計畫是否準備？&lt;/strong> 出問題時有明確的回滾和修復策略嗎？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>依賴關係是否梳理？&lt;/strong> 知道任務間的依賴順序和影響範圍嗎？&lt;/li>
&lt;/ul>
&lt;h4 id="5-參考文件和影響範圍完整性強制">5. 參考文件和影響範圍完整性（強制）&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>UseCase 參考是否明確？&lt;/strong> 工作日誌是否列出對應的 UseCase 和業務需求？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>流程圖 Event 是否具體？&lt;/strong> 是否指定要實作流程圖的哪些 Event 編號和對應類別/方法？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>架構規範是否引用？&lt;/strong> 是否明確列出適用的架構規範文件（Clean Architecture、DDD、錯誤處理等）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>依賴類別是否列舉？&lt;/strong> 是否列出前置任務產出的依賴類別和版本號？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>測試設計是否參考？&lt;/strong> 是否明確測試檔案、測試數量和效能基準？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>影響檔案是否清單化？&lt;/strong> 是否完整列出需建立、修改、影響的檔案清單？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>影響範圍是否評估？&lt;/strong> 是否評估對上層模組和下層模組的影響？&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>違規處理&lt;/strong>: 缺少任何一項參考文件或影響範圍資訊，視為&lt;strong>任務規劃不合格&lt;/strong>，必須立即補充後才能分派給代理人執行。&lt;/p>
&lt;h4 id="6-設計面效能考量前後端通用">6. 設計面效能考量（前後端通用）&lt;/h4>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>效能瓶頸是否識別？&lt;/strong> 是否分析潛在的效能瓶頸（大量資料處理、複雜運算、API 等待、資料庫查詢）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>優化策略是否規劃？&lt;/strong> 是否規劃對應的優化策略（異步處理、Isolate、快取機制、分頁載入、索引優化）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>資源使用是否評估？&lt;/strong> 是否評估記憶體使用、網路請求數量、CPU 負載、儲存空間需求？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>UI 阻塞是否避免？&lt;/strong> (前端) 是否確保 UI 主執行緒不被阻塞（異步操作、Loading State、compute/Isolate）？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>回應時間是否設計？&lt;/strong> (後端) 是否設計 API 回應時間目標和逾時處理機制？&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> &lt;strong>效能基準是否設定？&lt;/strong> 是否定義具體的效能目標和測量方式（載入時間、渲染幀率、API 回應時間）？&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>適用範圍&lt;/strong>:&lt;/p></description><content:encoded><![CDATA[<h2 id="方法論概述">方法論概述</h2>
<p>本方法論定義敏捷開發流程，採用主線程統籌分派、子代理人專責執行的協作模式，確保重構品質和進度控制。
但是在AI開發過程中，不會百分之百按照文件要求執行，所以需要配合hook機制在轉階段的時候檢查是否符合TDD流程，每一層的文件是否有確實填寫，指派任務給代理人之前，文件是否有提供完整的規格資料以及功能描述，不然執行上還是會有偏差，但是依照這套機制可以及早發現出錯的狀況及早糾正</p>
<p>這篇方法論是延續之前敏捷重構方法論補充內容，但是因為內容更大更完整，所以獨立一篇文章而不是更新舊的敏捷重構方法論(實際專案中是取代舊的方法論)。</p>
<h2 id="核心原則">核心原則</h2>
<h3 id="1-主線程職責專一化">1. 主線程職責專一化</h3>
<ul>
<li><strong>只負責任務分派和統籌管理</strong>，絕不親自執行具體重構工作</li>
<li>依照工作日誌規劃分派任務給相應的子代理人</li>
<li>維持敏捷開發的節奏和品質標準</li>
</ul>
<h3 id="2-任務原子化拆分">2. 任務原子化拆分</h3>
<ul>
<li>每個小版本任務必須在 1-2 小時內完成</li>
<li>任務影響檔案數量控制在 5 個以下</li>
<li>每個任務都有明確的完成標準和驗收條件</li>
</ul>
<h3 id="3-品質門檻強制執行">3. 品質門檻強制執行</h3>
<ul>
<li>每個任務完成後必須通過測試檢查</li>
<li>重構代理人強制驗證程式碼品質</li>
<li>100% 測試通過率是最低要求</li>
</ul>
<h2 id="agent-角色定義">Agent 角色定義</h2>
<h3 id="主線程-main-thread">主線程 (Main Thread)</h3>
<p><strong>職責</strong>：</p>
<ul>
<li>依照主版本工作日誌分派任務</li>
<li>維持敏捷開發節奏</li>
<li>監控整體進度和品質</li>
<li>處理升級請求和任務重新分派</li>
</ul>
<p><strong>禁止行為</strong>：</p>
<ul>
<li>親自閱讀或修改程式碼</li>
<li>執行具體的重構工作</li>
<li>繞過子代理人直接操作</li>
</ul>
<h3 id="子代理人-sub-agents">子代理人 (Sub-Agents)</h3>
<p><strong>職責</strong>：</p>
<ul>
<li>執行指派的具體重構任務</li>
<li>回報任務完成狀態和結果</li>
<li>發現任務規模過大時向上回報</li>
<li>確保任務符合完成標準</li>
</ul>
<p><strong>專業分工</strong>：</p>
<ul>
<li><strong>lavender-interface-designer</strong>: TDD Phase 1 功能設計</li>
<li><strong>sage-test-architect</strong>: TDD Phase 2 測試設計</li>
<li><strong>pepper-test-implementer</strong>: TDD Phase 3 實作規劃</li>
<li><strong>cinnamon-refactor-owl</strong>: TDD Phase 4 重構執行</li>
<li><strong>mint-format-specialist</strong>: 程式碼格式化和品質修正</li>
</ul>
<h3 id="重構代理人-refactor-validator">重構代理人 (Refactor Validator)</h3>
<p><strong>職責</strong>：</p>
<ul>
<li>檢查子代理人完成的重構工作</li>
<li>驗證程式碼品質和功能正確性</li>
<li>確認測試通過率和效能標準</li>
<li>批准任務完成或要求修正</li>
</ul>
<p><strong>代理人</strong>: <code>cinnamon-refactor-owl</code></p>
<h3 id="pm-代理人-project-manager">PM 代理人 (Project Manager)</h3>
<p><strong>職責</strong>：</p>
<ul>
<li>任務規模過大時進行二次拆分</li>
<li>更新小版本工作日誌</li>
<li>重新規劃任務執行順序</li>
<li>管理任務依賴關係</li>
</ul>
<p><strong>代理人</strong>: <code>rosemary-project-manager</code></p>
<h3 id="文件代理人-documentation-agent">文件代理人 (Documentation Agent)</h3>
<p><strong>職責</strong>：</p>
<ul>
<li>更新小版本工作日誌</li>
<li>標記小版本完成狀態</li>
<li>記錄任務執行結果</li>
<li>同步文件狀態</li>
</ul>
<p><strong>代理人</strong>: <code>memory-network-builder</code></p>
<h2 id="任務分派前強制檢查清單">任務分派前強制檢查清單</h2>
<p><strong>重要</strong>: 在分派任何重構任務前，主線程必須先通過以下檢查清單。如果無法回答這些問題，必須優先建立準備文件。</p>
<h3 id="準備度檢查問題">準備度檢查問題</h3>
<h4 id="1-明確的文件規劃">1. 明確的文件規劃</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>API 規格是否完整？</strong> 代理人知道要實作什麼樣的介面和行為嗎？</li>
<li><input disabled="" type="checkbox"> <strong>設計文件是否具體？</strong> 有詳細的類別定義、方法簽名、使用範例嗎？</li>
<li><input disabled="" type="checkbox"> <strong>架構圖是否清晰？</strong> 代理人知道新系統的整體架構和元件關係嗎？</li>
</ul>
<h4 id="2-測試先行策略">2. 測試先行策略</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>測試規格是否存在？</strong> 每個元件都有對應的測試規格和驗收標準嗎？</li>
<li><input disabled="" type="checkbox"> <strong>TDD 流程是否明確？</strong> 代理人知道要先寫什麼測試再實作嗎？</li>
<li><input disabled="" type="checkbox"> <strong>效能標準是否定義？</strong> 有明確的效能基準和測試方法嗎？</li>
</ul>
<h4 id="3-實作目標明確性">3. 實作目標明確性</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>完成標準是否可測量？</strong> 代理人知道什麼程度才算任務完成嗎？</li>
<li><input disabled="" type="checkbox"> <strong>使用範例是否充足？</strong> 有具體的程式碼範例展示最終產品嗎？</li>
<li><input disabled="" type="checkbox"> <strong>移轉策略是否清楚？</strong> 有舊系統到新系統的具體移轉指引嗎？</li>
</ul>
<h4 id="4-風險評估與應對">4. 風險評估與應對</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>潛在問題是否識別？</strong> 有預見可能的技術難點和解決方案嗎？</li>
<li><input disabled="" type="checkbox"> <strong>回滾計畫是否準備？</strong> 出問題時有明確的回滾和修復策略嗎？</li>
<li><input disabled="" type="checkbox"> <strong>依賴關係是否梳理？</strong> 知道任務間的依賴順序和影響範圍嗎？</li>
</ul>
<h4 id="5-參考文件和影響範圍完整性強制">5. 參考文件和影響範圍完整性（強制）</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>UseCase 參考是否明確？</strong> 工作日誌是否列出對應的 UseCase 和業務需求？</li>
<li><input disabled="" type="checkbox"> <strong>流程圖 Event 是否具體？</strong> 是否指定要實作流程圖的哪些 Event 編號和對應類別/方法？</li>
<li><input disabled="" type="checkbox"> <strong>架構規範是否引用？</strong> 是否明確列出適用的架構規範文件（Clean Architecture、DDD、錯誤處理等）？</li>
<li><input disabled="" type="checkbox"> <strong>依賴類別是否列舉？</strong> 是否列出前置任務產出的依賴類別和版本號？</li>
<li><input disabled="" type="checkbox"> <strong>測試設計是否參考？</strong> 是否明確測試檔案、測試數量和效能基準？</li>
<li><input disabled="" type="checkbox"> <strong>影響檔案是否清單化？</strong> 是否完整列出需建立、修改、影響的檔案清單？</li>
<li><input disabled="" type="checkbox"> <strong>影響範圍是否評估？</strong> 是否評估對上層模組和下層模組的影響？</li>
</ul>
<p><strong>違規處理</strong>: 缺少任何一項參考文件或影響範圍資訊，視為<strong>任務規劃不合格</strong>，必須立即補充後才能分派給代理人執行。</p>
<h4 id="6-設計面效能考量前後端通用">6. 設計面效能考量（前後端通用）</h4>
<ul>
<li><input disabled="" type="checkbox"> <strong>效能瓶頸是否識別？</strong> 是否分析潛在的效能瓶頸（大量資料處理、複雜運算、API 等待、資料庫查詢）？</li>
<li><input disabled="" type="checkbox"> <strong>優化策略是否規劃？</strong> 是否規劃對應的優化策略（異步處理、Isolate、快取機制、分頁載入、索引優化）？</li>
<li><input disabled="" type="checkbox"> <strong>資源使用是否評估？</strong> 是否評估記憶體使用、網路請求數量、CPU 負載、儲存空間需求？</li>
<li><input disabled="" type="checkbox"> <strong>UI 阻塞是否避免？</strong> (前端) 是否確保 UI 主執行緒不被阻塞（異步操作、Loading State、compute/Isolate）？</li>
<li><input disabled="" type="checkbox"> <strong>回應時間是否設計？</strong> (後端) 是否設計 API 回應時間目標和逾時處理機制？</li>
<li><input disabled="" type="checkbox"> <strong>效能基準是否設定？</strong> 是否定義具體的效能目標和測量方式（載入時間、渲染幀率、API 回應時間）？</li>
</ul>
<p><strong>適用範圍</strong>:</p>
<ul>
<li>前端開發（UI 渲染、資料處理、狀態管理）</li>
<li>Backend API 開發（查詢優化、快取策略、並發處理）</li>
<li>資料處理邏輯（JSON 解析、大量計算、批次處理）</li>
<li>所有可能影響使用者體驗的功能</li>
</ul>
<p><strong>強制要求</strong>:</p>
<ul>
<li>Phase 1 設計規格必須包含「效能考量」章節</li>
<li>所有任務分派都需要評估效能影響</li>
<li>無論前後端開發，都要從設計面預防效能問題</li>
<li>效能問題在設計階段解決，而非依賴測試發現</li>
</ul>
<p><strong>參考文件</strong>:</p>
<ul>
<li>TDD 協作流程 - Phase 4-1 UI 效能檢查</li>
<li>測試金字塔設計 - 效能測試策略</li>
<li>Widget 測試指導原則 - 禁止效能測試</li>
</ul>
<p><strong>違規處理</strong>: 缺少效能考量分析，視為<strong>設計規劃不完整</strong>，必須補充效能分析後才能分派任務。</p>
<h3 id="準備度檢查失敗處理">準備度檢查失敗處理</h3>
<p><strong>如果任何檢查項目回答為「否」，必須執行以下優先動作</strong>：</p>
<h4 id="優先動作1-建立-api-規格文件">優先動作1: 建立 API 規格文件</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">檔案: docs/[feature-name]-api-specification.md
</span></span><span class="line"><span class="ln">2</span><span class="cl">內容:
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 完整的介面定義
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 詳細的方法簽名
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 具體的使用範例
</span></span><span class="line"><span class="ln">6</span><span class="cl">- 效能要求規格</span></span></code></pre></div><h4 id="優先動作2-建立-tdd-測試規格">優先動作2: 建立 TDD 測試規格</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">檔案: docs/[feature-name]-test-specification.md
</span></span><span class="line"><span class="ln">2</span><span class="cl">內容:
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 每個元件的測試案例
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 驗收測試標準
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 效能基準測試
</span></span><span class="line"><span class="ln">6</span><span class="cl">- 整合測試場景</span></span></code></pre></div><h4 id="優先動作3-建立移轉指引">優先動作3: 建立移轉指引</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">檔案: docs/[feature-name]-migration-guide.md
</span></span><span class="line"><span class="ln">2</span><span class="cl">內容:
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 舊系統→新系統對照表
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 常見使用場景範例
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 最佳實踐指引
</span></span><span class="line"><span class="ln">6</span><span class="cl">- 問題排除手冊</span></span></code></pre></div><h4 id="優先動作4-建立實作範例">優先動作4: 建立實作範例</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">檔案: docs/[feature-name]-implementation-examples.md
</span></span><span class="line"><span class="ln">2</span><span class="cl">內容:
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> 完整的程式碼範例
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> 使用情境示範
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 常見模式展示
</span></span><span class="line"><span class="ln">6</span><span class="cl">- 最佳實踐指引</span></span></code></pre></div><h3 id="準備文件建立流程">準備文件建立流程</h3>
<h4 id="文件建立責任分工">文件建立責任分工</h4>
<ul>
<li><strong>API 規格</strong>: lavender-interface-designer (功能設計專家)</li>
<li><strong>測試規格</strong>: sage-test-architect (測試設計專家)</li>
<li><strong>移轉指引</strong>: memory-network-builder (文件專家)</li>
<li><strong>實作範例</strong>: pepper-test-implementer (實作規劃專家)</li>
</ul>
<h4 id="文件品質標準">文件品質標準</h4>
<ul>
<li><strong>完整性</strong>: 涵蓋所有必要資訊，無遺漏</li>
<li><strong>具體性</strong>: 提供可執行的詳細指引，非概念性描述</li>
<li><strong>一致性</strong>: 與整體架構和設計原則保持一致</li>
<li><strong>可測試性</strong>: 所有規格都可以通過測試驗證</li>
</ul>
<h2 id="三重文件原則-triple-document-principle">三重文件原則 (Triple Document Principle)</h2>
<p>本方法論採用三重文件機制確保不同層級的資訊流動和代理人協調。</p>
<h3 id="文件層級定義">文件層級定義</h3>
<h4 id="1-changelogmd---版本功能變動記錄">1. CHANGELOG.md - 版本功能變動記錄</h4>
<p><strong>職責</strong>：</p>
<ul>
<li>面向用戶的版本功能描述</li>
<li>只記錄「做了什麼」，不記錄「怎麼做」</li>
<li>版本發布時的正式變更說明</li>
<li>符合 Keep a Changelog 規範</li>
</ul>
<p><strong>格式範例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## v0.11.0 (2025-09-30)
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 新增功能
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 統一書籍資訊查詢服務
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 支援 Google Books API 整合
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 改善
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 優化錯誤處理機制
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 提升查詢效能
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 修復
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span>- 修正 ISBN 驗證問題</span></span></code></pre></div><p><strong>更新時機</strong>：</p>
<ul>
<li>版本發布時由文件代理人更新</li>
<li>從 work-log 提取用戶可感知的功能變動</li>
<li>不記錄內部重構或技術細節</li>
</ul>
<h4 id="2-todolistmd---開發任務全景圖">2. todolist.md - 開發任務全景圖</h4>
<p><strong>職責</strong>：</p>
<ul>
<li>記錄整個開發過程所有待處理任務</li>
<li>任務狀態追蹤（待執行/進行中/已完成）</li>
<li>優先級排序和依賴關係管理</li>
<li>跨版本的任務規劃視圖</li>
</ul>
<p><strong>代理人使用場景</strong>：</p>
<ul>
<li><strong>PM 代理人</strong>：規劃任務優先序和依賴關係</li>
<li><strong>主線程</strong>：查看全局並分派下一個任務</li>
<li><strong>執行代理人</strong>：了解任務背景和依賴</li>
<li><strong>所有代理人</strong>：查看整體進度和下一步方向</li>
</ul>
<p><strong>內容結構</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># TodoList
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## v0.11.x 系列任務
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">- [x]</span> v0.11.0 統一書籍資訊查詢服務
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">- [x]</span> Phase 1: 流程設計
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">- [x]</span> Phase 2: 測試設計
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">- [ ]</span> Phase 3: 實作執行
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">- [x]</span> v0.11.3 Domain 輸入驗證
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">- [x]</span> v0.11.4 核心查詢服務
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">- [ ]</span> v0.11.5 資料處理和快取
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">- [ ]</span> v0.11.15 測試修復完成
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">## v0.12.x 系列任務（規劃中）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span>- [ ] v0.12.0 外部 API 整合</span></span></code></pre></div><p><strong>更新責任</strong>：</p>
<ul>
<li>所有代理人在任務狀態變更時即時更新</li>
<li>PM 代理人負責新增和重組任務</li>
<li>主線程負責維護任務優先序</li>
</ul>
<h4 id="3-work-log---詳細實作記錄">3. work-log/ - 詳細實作記錄</h4>
<p><strong>職責</strong>：</p>
<ul>
<li>完整的技術實作細節和決策過程</li>
<li>TDD 四階段進度追蹤</li>
<li>代理人交接的完整上下文</li>
<li>問題發現和解決方案記錄</li>
</ul>
<p><strong>文件結構</strong>：</p>
<ul>
<li><strong>主版本日誌</strong> (<code>vX.Y.0-main.md</code>)：中版本總覽、任務分派清單</li>
<li><strong>小版本日誌</strong> (<code>vX.Y.Z-task.md</code>)：具體任務的詳細執行記錄</li>
</ul>
<p><strong>內容標準</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># vX.Y.Z 任務名稱
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 任務資訊
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 版本號、負責代理人、執行日期
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 前置任務、後續任務
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">## 任務目標
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 明確的技術目標
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">## 參考文件
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">-</span> UseCase、流程圖、架構規範
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 依賴類別、測試設計
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">## 執行過程
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">-</span> Phase 1: 設計
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> Phase 2: 測試
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> Phase 3: 實作
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> Phase 4: 重構
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu">## 完成標準
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu"></span><span class="k">-</span> 測試通過率 100%
</span></span><span class="line"><span class="ln">22</span><span class="cl">- 程式碼品質符合標準</span></span></code></pre></div><p><strong>更新責任</strong>：</p>
<ul>
<li>執行代理人負責記錄詳細過程</li>
<li>文件代理人負責標記完成狀態</li>
<li>PM 代理人負責維護主版本日誌</li>
</ul>
<h3 id="三重文件協調機制">三重文件協調機制</h3>
<h4 id="資訊流動方向">資訊流動方向</h4>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[work-log&lt;br/&gt;實作細節] --&gt;|提取功能變動| B[CHANGELOG&lt;br/&gt;版本說明]
    A --&gt;|完成任務| C[todolist&lt;br/&gt;狀態更新]
    C --&gt;|規劃下一步| D[主線程分派]
    D --&gt;|查看詳細| A
    B --&gt;|版本發布| E[用戶和團隊]

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#e8f5e9</code></pre><p><strong>流程說明</strong>：</p>
<ol>
<li>代理人在 <strong>work-log</strong> 記錄詳細實作過程</li>
<li>任務完成時更新 <strong>todolist</strong> 狀態</li>
<li>版本發布時從 <strong>work-log</strong> 提取變動到 <strong>CHANGELOG</strong></li>
<li>主線程基於 <strong>todolist</strong> 和 <strong>work-log</strong> 規劃下一步</li>
</ol>
<h4 id="代理人交接層級">代理人交接層級</h4>
<h5 id="主線程--pm-代理人">主線程 ↔ PM 代理人</h5>
<p><strong>主要參考</strong>：</p>
<ul>
<li><code>todolist.md</code> - 任務全局和優先序</li>
<li><code>vX.Y.0-main.md</code> - 版本進度和任務拆分</li>
</ul>
<p><strong>交接場景</strong>：</p>
<ul>
<li>任務規模過大需要拆分</li>
<li>規劃下一個版本系列</li>
<li>任務依賴關係調整</li>
</ul>
<h5 id="主線程--執行代理人">主線程 ↔ 執行代理人</h5>
<p><strong>主要參考</strong>：</p>
<ul>
<li><code>vX.Y.Z-task.md</code> - 具體任務詳細說明</li>
<li><code>todolist.md</code> - 任務依賴和背景</li>
</ul>
<p><strong>交接場景</strong>：</p>
<ul>
<li>分派新任務</li>
<li>接收完成回報</li>
<li>處理問題升級</li>
</ul>
<h5 id="執行代理人--執行代理人">執行代理人 ↔ 執行代理人</h5>
<p><strong>主要參考</strong>：</p>
<ul>
<li><code>vX.Y.Z-task.md</code> - 前置任務的產出和發現</li>
<li><code>vX.Y.0-main.md</code> - 重要決策和架構調整</li>
</ul>
<p><strong>交接場景</strong>：</p>
<ul>
<li>Phase 1 → Phase 2：設計交接測試</li>
<li>Phase 2 → Phase 3：測試交接實作</li>
<li>Phase 3 → Phase 4：實作交接重構</li>
<li>依賴任務間的產出交接</li>
</ul>
<h5 id="版本發布時">版本發布時</h5>
<p><strong>主要參考</strong>：</p>
<ul>
<li><code>work-log/</code> - 提取所有功能變動</li>
<li><code>todolist.md</code> - 確認完成項目</li>
</ul>
<p><strong>執行流程</strong>：</p>
<ol>
<li>文件代理人檢視所有已完成的 work-log</li>
<li>提取用戶可感知的功能變動</li>
<li>更新 CHANGELOG.md</li>
<li>確認 todolist.md 狀態一致</li>
</ol>
<h3 id="三重文件更新責任">三重文件更新責任</h3>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>更新時機</th>
          <th>責任代理人</th>
          <th>更新內容</th>
          <th>更新頻率</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>work-log</strong></td>
          <td>任務執行中/完成</td>
          <td>執行代理人</td>
          <td>詳細實作過程、決策記錄</td>
          <td>即時</td>
      </tr>
      <tr>
          <td><strong>todolist</strong></td>
          <td>任務狀態變更</td>
          <td>所有代理人</td>
          <td>任務狀態、優先序</td>
          <td>即時</td>
      </tr>
      <tr>
          <td><strong>CHANGELOG</strong></td>
          <td>版本發布</td>
          <td>文件代理人</td>
          <td>功能變動摘要</td>
          <td>版本發布時</td>
      </tr>
  </tbody>
</table>
<h3 id="三重文件一致性檢查">三重文件一致性檢查</h3>
<h4 id="強制檢查項目">強制檢查項目</h4>
<p><strong>版本號一致性</strong>：</p>
<ul>
<li>CHANGELOG 版本號 = work-log 主版本號</li>
<li>todolist 版本系列 = work-log 版本系列</li>
</ul>
<p><strong>任務狀態一致性</strong>：</p>
<ul>
<li>todolist 標記完成 ⇒ work-log 必須有對應完成記錄</li>
<li>work-log 標記完成 ⇒ todolist 必須同步更新</li>
</ul>
<p><strong>功能描述一致性</strong>：</p>
<ul>
<li>CHANGELOG 功能描述 ⇒ 必須對應 work-log 實作內容</li>
<li>不可在 CHANGELOG 記錄未實作的功能</li>
</ul>
<h4 id="hook-系統整合">Hook 系統整合</h4>
<p><strong>Version Check Hook</strong>：</p>
<ul>
<li>檢查三重文件版本號一致性</li>
<li>建議版本推進策略</li>
</ul>
<p><strong>Document Sync Hook</strong>：</p>
<ul>
<li>提醒 work-log 完成時更新 todolist</li>
<li>提醒版本發布時更新 CHANGELOG</li>
</ul>
<p><strong>Work Log Check Hook</strong>：</p>
<ul>
<li>識別工作狀態（更新/新建/完成）</li>
<li>確保 work-log 記錄完整性</li>
</ul>
<h3 id="強制要求">強制要求</h3>
<h4 id="文件完整性要求">文件完整性要求</h4>
<ol>
<li>
<p><strong>work-log 必須完整</strong>：</p>
<ul>
<li>所有技術細節必須記錄</li>
<li>所有決策必須說明理由</li>
<li>所有問題發現必須記錄</li>
</ul>
</li>
<li>
<p><strong>todolist 必須即時</strong>：</p>
<ul>
<li>任務狀態即時更新，不得延遲</li>
<li>新任務必須立即加入</li>
<li>完成任務必須立即標記</li>
</ul>
</li>
<li>
<p><strong>CHANGELOG 必須準確</strong>：</p>
<ul>
<li>只記錄用戶可感知的變動</li>
<li>不記錄內部重構或技術債務</li>
<li>必須基於實際完成的 work-log</li>
</ul>
</li>
</ol>
<h4 id="代理人責任要求">代理人責任要求</h4>
<ol>
<li>
<p><strong>執行代理人</strong>：</p>
<ul>
<li>任務執行過程必須記錄到 work-log</li>
<li>任務完成時必須更新 todolist 狀態</li>
<li>發現問題必須記錄到 work-log</li>
</ul>
</li>
<li>
<p><strong>文件代理人</strong>：</p>
<ul>
<li>版本發布時必須更新 CHANGELOG</li>
<li>確保三重文件一致性</li>
<li>標記 work-log 完成狀態</li>
</ul>
</li>
<li>
<p><strong>PM 代理人</strong>：</p>
<ul>
<li>維護 todolist 任務結構</li>
<li>維護 vX.Y.0-main.md 任務分派</li>
<li>確保任務依賴關係正確</li>
</ul>
</li>
<li>
<p><strong>主線程</strong>：</p>
<ul>
<li>不得親自修改程式碼</li>
<li>基於 todolist 和 work-log 分派任務</li>
<li>監控三重文件一致性</li>
</ul>
</li>
</ol>
<h3 id="v0110-實戰範例">v0.11.0 實戰範例</h3>
<h4 id="三重文件協調實例">三重文件協調實例</h4>
<p><strong>work-log 記錄</strong>（v0.11.3-domain-input-validation.md）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## Phase 3 實作完成
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span><span class="k">-</span> BookQueryInput Value Object 實作
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> InputValidator 驗證邏輯
</span></span><span class="line"><span class="ln">4</span><span class="cl">- 71 個測試 100% 通過</span></span></code></pre></div><p><strong>todolist 更新</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl">- [x] v0.11.3 Domain 輸入驗證</span></span></code></pre></div><p><strong>CHANGELOG 提取</strong>（版本發布時）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## v0.11.0 (2025-09-30)
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu">### 新增功能
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu"></span>- 統一書籍資訊查詢服務（支援 ISBN、標題、作者查詢）</span></span></code></pre></div><p><strong>協調流程</strong>：</p>
<ol>
<li>pepper-test-implementer 完成 v0.11.3 實作</li>
<li>記錄詳細過程到 work-log</li>
<li>更新 todolist 標記完成</li>
<li>主線程分派下一個任務 v0.11.4</li>
<li>版本發布時，文件代理人從所有 v0.11.x work-log 提取功能到 CHANGELOG</li>
</ol>
<h4 id="代理人交接實例">代理人交接實例</h4>
<p><strong>Phase 2 → Phase 3 交接</strong>：</p>
<p>sage-test-architect 完成測試設計後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.11.2-tdd-phase2-test-design.md
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## Phase 2 產出
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 335 個測試用例設計
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 33 個測試檔案規劃
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 架構修正決策：BookEnrichmentData 遷移到 Domain 層
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">## 傳遞給 Phase 3
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span><span class="k">-</span> 依賴類別：BookEnrichmentData (已遷移)
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 測試優先序：v0.11.3 → v0.11.4 → v0.11.5
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 重要發現：Domain 服務層需從零建立</span></span></code></pre></div><p>pepper-test-implementer 接收後：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gh"># v0.11.3-domain-input-validation.md
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">## 參考文件
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 測試設計：v0.11.2-tdd-phase2-test-design.md<span class="ni">#task</span>-20
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> 前置任務產出：無（首個任務）
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> 架構調整：BookEnrichmentData 已遷移到 Domain 層
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="gu">## Phase 3 執行
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="gu"></span>基於 Phase 2 測試設計，實作 BookQueryInput...</span></span></code></pre></div><p><strong>交接重點</strong>：</p>
<ul>
<li>Phase 2 的架構發現立即傳遞</li>
<li>Phase 3 明確參考 Phase 2 測試設計</li>
<li>work-log 完整記錄交接脈絡</li>
</ul>
<h2 id="敏捷重構流程">敏捷重構流程</h2>
<h3 id="增強版執行循環">增強版執行循環</h3>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[主線程開始任務分派] --&gt; B[執行準備度檢查清單]
    B --&gt; C{檢查是否全部通過?}
    C --&gt;|否| D[呼叫相應代理人建立準備文件]
    D --&gt; E[文件建立完成]
    E --&gt; B
    C --&gt;|是| F[檢查主版本工作日誌]
    F --&gt; G[分派下一個任務]
    G --&gt; H[子代理人執行任務]
    H --&gt; I{任務規模是否適當?}
    I --&gt;|過大| J[向上回報主線程]
    J --&gt; K[PM代理人拆分任務]
    K --&gt; L[建立小版本工作日誌]
    L --&gt; M[依小版本日誌分派]
    M --&gt; H
    I --&gt;|適當| N[完成任務回報]
    N --&gt; O[重構代理人檢查]
    O --&gt; P{品質是否通過?}
    P --&gt;|不通過| Q[要求修正]
    Q --&gt; H
    P --&gt;|通過| R[文件代理人更新日誌]
    R --&gt; S{小版本是否完成?}
    S --&gt;|是| T[回到主版本檢查下一任務]
    S --&gt;|否| U[檢查小版本下一任務]
    U --&gt; M
    T --&gt; F</code></pre><h3 id="標準執行循環">標準執行循環</h3>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[主線程檢查主版本工作日誌] --&gt; B[分派下一個任務]
    B --&gt; C[子代理人執行任務]
    C --&gt; D{任務規模是否適當?}
    D --&gt;|過大| E[向上回報主線程]
    E --&gt; F[PM代理人拆分任務]
    F --&gt; G[建立小版本工作日誌]
    G --&gt; H[依小版本日誌分派]
    H --&gt; C
    D --&gt;|適當| I[完成任務回報]
    I --&gt; J[重構代理人檢查]
    J --&gt; K{品質是否通過?}
    K --&gt;|不通過| L[要求修正]
    L --&gt; C
    K --&gt;|通過| M[文件代理人更新日誌]
    M --&gt; N{小版本是否完成?}
    N --&gt;|是| O[回到主版本檢查下一任務]
    N --&gt;|否| P[檢查小版本下一任務]
    P --&gt; H
    O --&gt; A</code></pre><h2 id="任務拆分實戰範例">任務拆分實戰範例</h2>
<h3 id="v0115-任務拆分情境">v0.11.5 任務拆分情境</h3>
<p><strong>原始任務</strong>：v0.11.5 - 批次 1.3 資料處理和快取</p>
<p><strong>任務描述</strong>（來自 v0.11.0-main.md）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">#### v0.11.5 - 批次 1.3 資料處理和快取
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>**狀態**: 待執行
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gs">**負責**</span>: pepper-test-implementer
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gs">**實作內容**</span>:
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> BookEnrichmentProcessor
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> BookMetadata 實體
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> ApiCacheManager
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="gs">**驗收**</span>: 資料處理鏈測試通過</span></span></code></pre></div><h3 id="代理人回報情境假設">代理人回報（情境假設）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 代理人回報 - v0.11.5
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**回報代理人**</span>: pepper-test-implementer
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**任務**</span>: v0.11.5 - 批次 1.3 資料處理和快取
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 問題描述
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>經過 Phase 2 測試設計分析，發現此任務包含三個獨立且複雜的元件：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">1.</span> BookEnrichmentProcessor - 資料處理邏輯（20+ 個測試用例）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">2.</span> BookMetadata 實體 - Domain 層核心實體（15+ 個測試用例）
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">3.</span> ApiCacheManager - 快取管理機制（18+ 個測試用例）
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">預估總工作量：4-5 小時，超過單一任務標準（1-2 小時）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 建議拆分方案
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span>**方案 A**：依元件職責拆分為三個子任務
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> v0.11.5-a: BookEnrichmentProcessor（資料處理核心）
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> v0.11.5-b: BookMetadata（Domain 實體）
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> v0.11.5-c: ApiCacheManager（快取機制）
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gs">**方案 B**</span>：依依賴關係拆分
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> v0.11.5-a: BookMetadata + BookEnrichmentProcessor
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">-</span> v0.11.5-b: ApiCacheManager
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 推薦方案
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span>推薦方案 A，原因：
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> 每個元件職責單一，測試獨立
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> 符合 Clean Architecture 分層（Domain / Application）
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">-</span> 可並行開發（如有多代理人）
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> 風險隔離，單一元件問題不影響其他
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="gu">### 等待主線程決策
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu"></span>請確認拆分方案，並指示是否需要 PM 代理人建立子版本工作日誌。</span></span></code></pre></div><h3 id="主線程決策處理">主線程決策處理</h3>
<p><strong>決策過程</strong>：</p>
<ol>
<li>
<p><strong>檢視相關文件</strong>：</p>
<ul>
<li>Phase 2 測試設計：確認測試用例數量和複雜度</li>
<li>流程圖 Event 6-7：確認元件間依賴關係</li>
<li>架構規範：確認分層正確性</li>
</ul>
</li>
<li>
<p><strong>評估拆分方案</strong>：</p>
<ul>
<li>方案 A 職責更清晰，符合單一職責原則</li>
<li>每個子任務 1.5-2 小時，符合標準</li>
<li>BookMetadata 是 Domain 層核心，應獨立處理</li>
</ul>
</li>
<li>
<p><strong>做出決策</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策記錄 - v0.11.5 拆分
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策**</span>: 採用方案 A，拆分為三個子任務
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**理由**</span>:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 符合單一職責原則
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> BookMetadata 是 Domain 層核心實體，重要性高
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 測試覆蓋率可逐步建立
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 風險管理更精確
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**執行**</span>:
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">1.</span> 呼叫 rosemary-project-manager 建立子版本工作日誌
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">2.</span> 更新 v0.11.0-main.md 任務清單
</span></span><span class="line"><span class="ln">13</span><span class="cl">3. 依序分派 v0.11.5-a → v0.11.5-b → v0.11.5-c</span></span></code></pre></div></li>
</ol>
<h3 id="pm-代理人建立子版本日誌">PM 代理人建立子版本日誌</h3>
<p><strong>rosemary-project-manager 執行</strong>：</p>
<h4 id="1-建立-v0115-a-工作日誌">1. 建立 v0.11.5-a 工作日誌</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># v0.11.5-a 資料處理核心 - BookEnrichmentProcessor
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 任務資訊
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> **版本號**: v0.11.5-a
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> **父任務**: v0.11.5 批次 1.3 資料處理和快取
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> **負責代理人**: pepper-test-implementer
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> **執行日期**: 2025-09-30
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> **前置任務**: v0.11.4 (核心查詢服務)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> **後續任務**: v0.11.5-b (BookMetadata)
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">## 任務目標
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span>實作 BookEnrichmentProcessor 資料處理核心邏輯。
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">## 參考文件（強制完整）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### UseCase 參考
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">UC-01 ISBN 精確查詢</span>](<span class="na">/app-use-cases.md#uc-01</span>)
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 流程圖參考（具體到 Event）
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">主流程 Event 6</span>](<span class="na">/use-cases/unified-api/book-info-query-flow.md</span>)
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="k">-</span> Event 6: BookEnrichmentProcessor.process(rawData)
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="k">-</span> 輸入: GoogleBooksDto
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">-</span> 輸出: BookEnrichmentData
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu">### 架構規範
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">Application 層設計</span>](<span class="na">/app-requirements-spec.md#application-layer</span>)
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> [<span class="nt">資料轉換規範</span>](<span class="na">/domain-transformation-layer-design.md</span>)
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu">### 依賴類別（前置任務產出）
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu"></span><span class="k">-</span> <span class="sb">`BookQueryInput`</span> (v0.11.3)
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">-</span> <span class="sb">`QueryType`</span> (v0.11.4)
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">-</span> <span class="sb">`GoogleBooksDto`</span> (Infrastructure 既有)
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">### 測試設計參考
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">主流程測試設計</span>](<span class="na">/record/agile-programing-methodology/v0.11.2-tdd-phase2-test-design.md#task-22</span>)
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> 測試檔案: <span class="sb">`book_enrichment_processor_test.dart`</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">-</span> 測試用例: 20 個
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gu">## 影響範圍
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="gu"></span><span class="k">-</span> 新建: <span class="sb">`lib/application/book_info/processors/book_enrichment_processor.dart`</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="k">-</span> 新建: <span class="sb">`test/unit/application/book_info/processors/book_enrichment_processor_test.dart`</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="gu">## 完成標準
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> BookEnrichmentProcessor 實作完成
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="k">- [ ]</span> 20 個測試用例 100% 通過
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">- [ ]</span> 符合 Application 層職責
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">- [ ]</span> 錯誤處理遵循規範
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="gu">## 回到父任務
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="gu"></span>完成後更新 v0.11.0-main.md 標記 v0.11.5-a 完成。</span></span></code></pre></div><h4 id="2-建立-v0115-bv0115-c-工作日誌">2. 建立 v0.11.5-b、v0.11.5-c 工作日誌</h4>
<p>（類似格式，省略詳細內容）</p>
<h3 id="更新主版本工作日誌">更新主版本工作日誌</h3>
<p><strong>更新 v0.11.0-main.md</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">#### v0.11.5 - 批次 1.3 資料處理和快取（待執行）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>**狀態**: 執行中（已拆分為子任務）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**負責**</span>: pepper-test-implementer
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**子任務**</span>:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">- [ ]</span> v0.11.5-a: BookEnrichmentProcessor ([<span class="nt">工作日誌</span>](<span class="na">/record/agile-programing-methodology/v0.11.5-a-enrichment-processor.md</span>))
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">- [ ]</span> v0.11.5-b: BookMetadata ([<span class="nt">工作日誌</span>](<span class="na">/record/agile-programing-methodology/v0.11.5-b-book-metadata.md</span>))
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">- [ ]</span> v0.11.5-c: ApiCacheManager ([<span class="nt">工作日誌</span>](<span class="na">/record/agile-programing-methodology/v0.11.5-c-cache-manager.md</span>))
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**驗收**</span>: 三個子任務全部完成，資料處理鏈測試通過
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gs">**拆分原因**</span>: 任務複雜度超出標準，依元件職責拆分為三個獨立任務
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gs">**拆分決策**</span>: [決策記錄連結]</span></span></code></pre></div><h3 id="任務拆分關鍵原則">任務拆分關鍵原則</h3>
<h4 id="何時拆分">何時拆分</h4>
<ul>
<li>預估工作時間 &gt; 3 小時</li>
<li>影響檔案數量 &gt; 5 個</li>
<li>測試用例數量 &gt; 30 個</li>
<li>包含多個獨立職責的元件</li>
<li>依賴關係複雜需要分階段驗證</li>
</ul>
<h4 id="拆分命名規則">拆分命名規則</h4>
<ul>
<li><strong>單層拆分</strong>: vX.Y.Z-a, vX.Y.Z-b, vX.Y.Z-c</li>
<li><strong>多層拆分</strong>: vX.Y.Z-a1, vX.Y.Z-a2（極少使用）</li>
<li><strong>命名語意</strong>: 清楚表達子任務核心職責</li>
</ul>
<h4 id="拆分後的依賴管理">拆分後的依賴管理</h4>
<ul>
<li><strong>串行依賴</strong>: 明確標註前置任務和後續任務</li>
<li><strong>並行任務</strong>: 標註可同時進行的子任務</li>
<li><strong>共享依賴</strong>: 在父任務層級說明共同依賴</li>
</ul>
<h2 id="動態文件更新機制">動態文件更新機制</h2>
<h3 id="核心原則-1">核心原則</h3>
<p><strong>敏捷開發的資訊流動本質</strong>：前置階段的發現必須立即傳遞並更新後續任務描述，確保所有代理人基於最新、最完整的資訊執行任務。</p>
<h3 id="phase-發現傳遞流程">Phase 發現傳遞流程</h3>
<h4 id="phase-1--phase-2--phase-3-資訊流">Phase 1 → Phase 2 → Phase 3 資訊流</h4>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph LR
    A[Phase 1 設計] --&gt;|架構決策| B[Phase 2 測試設計]
    B --&gt;|實作指引| C[Phase 3 實作]
    B --&gt;|問題識別| D[架構調整]
    D --&gt;|更新設計| A
    D --&gt;|更新任務| C</code></pre><h4 id="v011-實際案例phase-2-發現如何更新-phase-3-任務">v0.11 實際案例：Phase 2 發現如何更新 Phase 3 任務</h4>
<p><strong>Phase 2 發現</strong>（v0.11.2）：</p>
<ol>
<li><strong>架構問題</strong>：BookEnrichmentData 位於 Infrastructure 層（應在 Domain 層）</li>
<li><strong>命名不一致</strong>：4 個流程圖元件命名與程式碼不符</li>
<li><strong>缺失元件</strong>：Domain 服務層完全缺失，需要 20 個新類別</li>
<li><strong>時程調整</strong>：從 4-6 天調整為 6-7 天</li>
</ol>
<p><strong>立即執行的更新動作</strong>：</p>
<h5 id="1-架構修正阻塞性問題">1. 架構修正（阻塞性問題）</h5>
<p><strong>決策</strong>：立即停止 Phase 3 任務，優先修正架構問題</p>
<p><strong>執行</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 緊急任務插入 - v0.11.2-fix
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**任務**</span>: 遷移 BookEnrichmentData 到 Domain 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**優先級**</span>: 最高（阻塞後續開發）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**執行**</span>: 主線程直接處理或指派給 mint-format-specialist
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gs">**完成後動作**</span>:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 更新所有相關任務的依賴類別參考
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 更新流程圖中的層級標註
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 通知所有相關代理人架構變更</span></span></code></pre></div><h5 id="2-更新待執行任務的參考文件">2. 更新待執行任務的參考文件</h5>
<p><strong>原始 v0.11.5 任務描述</strong>（Phase 2 前）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">#### v0.11.5 - 批次 1.3 資料處理和快取
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>**實作內容**:
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> BookEnrichmentProcessor
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> BookMetadata 實體
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> ApiCacheManager
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="gs">**參考文件**</span>: （缺失）</span></span></code></pre></div><p><strong>更新後 v0.11.5 任務描述</strong>（Phase 2 後）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">#### v0.11.5 - 批次 1.3 資料處理和快取
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>**實作內容**:
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> BookEnrichmentProcessor
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> BookMetadata 實體
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> ApiCacheManager
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gs">**參考文件**</span>（Phase 2 補充）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> [<span class="nt">主流程 Event 6-7</span>](<span class="na">/record/use-cases/unified-api/book-info-query-flow.md</span>)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> [<span class="nt">測試設計</span>](<span class="na">/record/agile-programing-methodology/v0.11.2-tdd-phase2-test-design.md#task-22</span>)
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 依賴類別: <span class="sb">`BookEnrichmentData`</span> (**已遷移到 Domain 層**)
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 依賴類別: <span class="sb">`BookQueryInput`</span> (v0.11.3)
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 依賴類別: <span class="sb">`QueryType`</span> (v0.11.4)
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> [<span class="nt">錯誤處理規範</span>](<span class="na">/record/app-error-handling-design.md</span>)
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gs">**Phase 2 發現**</span>:
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> BookEnrichmentData 已從 Infrastructure 遷移到 Domain
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> 需要處理資料轉換和驗證邏輯
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">-</span> 快取策略需要配合速率控制（批次 2）
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gs">**驗收**</span>: 資料處理鏈測試通過，符合 Domain 層職責</span></span></code></pre></div><h5 id="3-更新主版本工作日誌持續性文件">3. 更新主版本工作日誌（持續性文件）</h5>
<p><strong>v0.11.0-main.md 動態更新區域</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## Phase 2 重要發現（持續更新）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 架構調整
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> **BookEnrichmentData 層級修正**（2025-09-30）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">-</span> 原位置: <span class="sb">`lib/infrastructure/`</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">-</span> 新位置: <span class="sb">`lib/domains/book_info/entities/`</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">-</span> 影響任務: v0.11.5, v0.11.6
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">-</span> Git commit: 58b1603
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 命名統一
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">-</span> **流程圖命名修正**（2025-09-30）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">-</span> 修正 4 個元件命名不一致
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="k">-</span> 所有後續任務參考已更新
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">### 實作發現
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">-</span> **Domain 服務層新建**：v0.11.3-4 建立 BookQueryInput, QueryTypeResolver
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="k">-</span> 影響: 所有後續任務依賴這些基礎類別
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="k">-</span> 參考: 所有任務需 import package:book_overview_app/domains/book_info/
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu">### 時程調整
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu"></span><span class="k">-</span> **原估時程**: 4-6 天
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">-</span> **調整時程**: 6-7 天（+1-2 天）
</span></span><span class="line"><span class="ln">23</span><span class="cl">- <span class="gs">**原因**</span>: 28 個 TDD 新建測試，Domain 層從零建立</span></span></code></pre></div><h3 id="動態更新觸發時機">動態更新觸發時機</h3>
<h4 id="1-架構變更時">1. 架構變更時</h4>
<p><strong>觸發條件</strong>：</p>
<ul>
<li>類別位置調整（跨層級遷移）</li>
<li>介面簽名變更</li>
<li>依賴關係變化</li>
</ul>
<p><strong>更新範圍</strong>：</p>
<ul>
<li>所有引用該類別的任務</li>
<li>相關的流程圖和設計文件</li>
<li>測試設計中的 Mock 設定</li>
</ul>
<h4 id="2-測試設計發現時">2. 測試設計發現時</h4>
<p><strong>觸發條件</strong>：</p>
<ul>
<li>識別出新的測試用例</li>
<li>發現缺失的邊界條件</li>
<li>複雜度評估變化</li>
</ul>
<p><strong>更新範圍</strong>：</p>
<ul>
<li>對應實作任務的驗收標準</li>
<li>相關任務的前置條件</li>
<li>時程估算調整</li>
</ul>
<h4 id="3-實作階段發現時">3. 實作階段發現時</h4>
<p><strong>觸發條件</strong>：</p>
<ul>
<li>技術方案調整</li>
<li>新增輔助類別</li>
<li>效能優化需求</li>
</ul>
<p><strong>更新範圍</strong>：</p>
<ul>
<li>後續相關任務的參考實作</li>
<li>測試設計補充</li>
<li>文件範例更新</li>
</ul>
<h3 id="動態更新執行標準">動態更新執行標準</h3>
<h4 id="主線程職責">主線程職責</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 主線程動態更新檢查清單
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">每次任務完成後：
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">- [ ]</span> 檢查是否有架構變更
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">- [ ]</span> 檢查是否有新的依賴類別
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">- [ ]</span> 檢查是否影響後續任務
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">- [ ]</span> 更新主版本工作日誌「重要發現」區域
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">- [ ]</span> 更新所有受影響任務的參考文件
</span></span><span class="line"><span class="ln">9</span><span class="cl">- [ ] 通知相關代理人（如已分派）</span></span></code></pre></div><h4 id="代理人回報職責">代理人回報職責</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 代理人發現回報格式
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 發現類型
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 架構問題
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">- [ ]</span> 依賴變更
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">- [ ]</span> 新增元件
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">- [ ]</span> 技術債務
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 影響評估
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> 影響範圍: [列出受影響的任務版本號]
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 嚴重程度: 阻塞 / 重要 / 資訊性
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 建議動作: [具體建議]
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 需要更新的文件
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 主版本工作日誌
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 相關任務參考文件
</span></span><span class="line"><span class="ln">17</span><span class="cl">- [ ] 流程圖或設計文件</span></span></code></pre></div><h2 id="代理人回報與討論流程">代理人回報與討論流程</h2>
<h3 id="回報時機與類型">回報時機與類型</h3>
<h4 id="1-架構衝突檢測">1. 架構衝突檢測</h4>
<p><strong>觸發情境</strong>：</p>
<ul>
<li>流程圖定義與實作需求衝突</li>
<li>類別職責與層級定位不符</li>
<li>依賴方向違反 Clean Architecture</li>
</ul>
<p><strong>v0.11 實際案例</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 架構衝突回報 - v0.11.2
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**回報代理人**</span>: sage-test-architect
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**檢測階段**</span>: Phase 2 測試設計
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 衝突描述
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>發現 <span class="sb">`BookEnrichmentData`</span> 位於 <span class="sb">`lib/infrastructure/`</span> 目錄，
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">但其職責為 Domain 層實體（純資料結構，無外部依賴）。
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 架構分析
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">-</span> **當前位置**: <span class="sb">`lib/infrastructure/api/google_books/book_enrichment_data.dart`</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> **應該位置**: <span class="sb">`lib/domains/book_info/entities/book_enrichment_data.dart`</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> **衝突原因**: 違反 Clean Architecture 分層原則
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> **影響範圍**:
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="k">-</span> v0.11.5 (資料處理) 需要引用此類別
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">-</span> v0.11.6 (API 客戶端) 需要轉換到此類別
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="k">-</span> 所有測試需要更新 import 路徑
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 建議方案
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span>**推薦**: 立即遷移到 Domain 層
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gs">**理由**</span>:
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">1.</span> Domain 層應該定義核心資料模型
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">2.</span> Infrastructure 層應該依賴 Domain，而非反向
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">3.</span> 後續所有 Domain 服務都需要使用此類別
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 需要決策
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 是否同意遷移到 Domain 層
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">- [ ]</span> 是否需要重新命名（EnrichmentData → BookEnrichmentData）
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">- [ ]</span> 是否需要更新所有流程圖標註
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">#### 等待主線程確認後執行</span></span></code></pre></div><p><strong>主線程處理流程</strong>：</p>
<ol>
<li>
<p><strong>檢視相關規範</strong>：</p>
<ul>
<li>閱讀 Clean Architecture 設計文件</li>
<li>確認 Domain 層職責定義</li>
<li>檢查是否有其他類似問題</li>
</ul>
</li>
<li>
<p><strong>做出決策</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策 - BookEnrichmentData 遷移
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決定**</span>: 同意遷移到 Domain 層
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**執行**</span>:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">1.</span> 立即處理（阻塞後續任務）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">2.</span> 遷移到 <span class="sb">`lib/domains/book_info/entities/`</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">3.</span> 更新所有 import 引用
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">4.</span> 更新流程圖層級標註
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gs">**指派**</span>: mint-format-specialist 執行檔案遷移和路徑更新
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gs">**時程**</span>: 0.5 小時，完成後繼續 v0.11.3</span></span></code></pre></div></li>
<li>
<p><strong>更新受影響任務</strong>：</p>
<ul>
<li>更新 v0.11.5, v0.11.6 參考文件</li>
<li>標註 BookEnrichmentData 新位置</li>
<li>更新所有測試的 import 路徑範例</li>
</ul>
</li>
</ol>
<h4 id="2-任務複雜度超標">2. 任務複雜度超標</h4>
<p><strong>觸發情境</strong>：</p>
<ul>
<li>預估工作時間 &gt; 3 小時</li>
<li>測試用例數量 &gt; 30 個</li>
<li>影響檔案數量 &gt; 5 個</li>
</ul>
<p>（參考「任務拆分實戰範例」章節）</p>
<h4 id="3-流程圖與需求不一致">3. 流程圖與需求不一致</h4>
<p><strong>觸發情境</strong>：</p>
<ul>
<li>Event 描述過於抽象</li>
<li>方法簽名未定義清楚</li>
<li>錯誤處理策略模糊</li>
</ul>
<p><strong>回報格式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 流程圖釐清需求 - vX.Y.Z
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**回報代理人**</span>: [agent-name]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**流程圖**</span>: [flow-diagram-name.md]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**Event**</span>: Event X
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 需要釐清的問題
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="gs">**Event X 描述**</span>: [原始描述]
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   <span class="k">-</span> 疑問: [具體不清楚的地方]
</span></span><span class="line"><span class="ln">10</span><span class="cl">   <span class="k">-</span> 需要確認: [需要確認的技術細節]
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">2.</span> <span class="gs">**方法簽名缺失**</span>:
</span></span><span class="line"><span class="ln">13</span><span class="cl">   <span class="k">-</span> 當前: Event X 只說「處理資料」
</span></span><span class="line"><span class="ln">14</span><span class="cl">   <span class="k">-</span> 需要: 輸入參數類型、輸出返回類型、異常處理
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 建議補充
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span>```dart
</span></span><span class="line"><span class="ln">18</span><span class="cl">// 建議的方法簽名
</span></span><span class="line"><span class="ln">19</span><span class="cl">Future<span class="p">&lt;</span><span class="nt">BookEnrichmentData</span><span class="p">&gt;</span> processRawData(
</span></span><span class="line"><span class="ln">20</span><span class="cl">  GoogleBooksDto rawData,
</span></span><span class="line"><span class="ln">21</span><span class="cl">  QueryType queryType,
</span></span><span class="line"><span class="ln">22</span><span class="cl">) async {
</span></span><span class="line"><span class="ln">23</span><span class="cl">  // 實作邏輯
</span></span><span class="line"><span class="ln">24</span><span class="cl">}</span></span></code></pre></div><h3 id="需要主線程確認">需要主線程確認</h3>
<ul>
<li><input disabled="" type="checkbox"> 方法簽名是否正確</li>
<li><input disabled="" type="checkbox"> 是否需要更新流程圖</li>
<li><input disabled="" type="checkbox"> 錯誤處理策略（拋出異常 vs OperationResult）</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">**主線程處理**：
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">1. 檢視 UseCase 定義
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">2. 確認技術方案
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">3. 更新流程圖或任務描述
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">4. 回覆代理人並記錄決策
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">#### 4. 技術方案需要確認
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">**觸發情境**：
</span></span><span class="line"><span class="ln">10</span><span class="cl">- 多種實作方案可行
</span></span><span class="line"><span class="ln">11</span><span class="cl">- 效能與可讀性取捨
</span></span><span class="line"><span class="ln">12</span><span class="cl">- 第三方套件選擇
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">**回報格式**：
</span></span><span class="line"><span class="ln">15</span><span class="cl">```markdown
</span></span><span class="line"><span class="ln">16</span><span class="cl">## 技術方案確認 - vX.Y.Z
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">**方案 A**: [描述]
</span></span><span class="line"><span class="ln">19</span><span class="cl">- 優點: [列舉]
</span></span><span class="line"><span class="ln">20</span><span class="cl">- 缺點: [列舉]
</span></span><span class="line"><span class="ln">21</span><span class="cl">- 適用場景: [說明]
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">**方案 B**: [描述]
</span></span><span class="line"><span class="ln">24</span><span class="cl">- 優點: [列舉]
</span></span><span class="line"><span class="ln">25</span><span class="cl">- 缺點: [列舉]
</span></span><span class="line"><span class="ln">26</span><span class="cl">- 適用場景: [說明]
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">### 推薦方案及理由
</span></span><span class="line"><span class="ln">29</span><span class="cl">[代理人的推薦與分析]
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">### 等待主線程決策
</span></span><span class="line"><span class="ln">32</span><span class="cl">[需要確認的具體問題]</span></span></code></pre></div><h3 id="回報標準格式">回報標準格式</h3>
<h4 id="完整回報模板">完整回報模板</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 代理人回報 - vX.Y.Z
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**回報代理人**</span>: [agent-name]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**任務**</span>: [task-name]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**回報類型**</span>: 架構衝突 / 複雜度 / 流程圖釐清 / 技術方案
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gs">**嚴重程度**</span>: 阻塞 / 重要 / 資訊性
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 問題描述
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span>[清楚描述發現的問題或疑問]
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 背景資訊
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">-</span> **相關文件**: [列出相關的 UseCase、流程圖、架構文件]
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> **當前狀態**: [任務執行到什麼階段]
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> **發現時機**: [什麼時候發現這個問題]
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 影響評估
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">-</span> **影響任務**: [列出受影響的任務版本號]
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">-</span> **影響範圍**: [程式碼、測試、文件]
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> **風險等級**: [高/中/低]
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu">### 分析與建議
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">#### 原因分析
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span>[為什麼會發生這個問題]
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">#### 建議方案
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span>**方案 A**: [描述]
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gs">**方案 B**</span>: [描述]
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gs">**推薦**</span>: [哪個方案及理由]
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gu">### 等待主線程決策
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> [具體需要決策的問題 1]
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="k">- [ ]</span> [具體需要決策的問題 2]
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">- [ ]</span> [需要更新的文件清單]
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">---
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="gs">**暫停執行**</span>: 是 / 可繼續其他任務 / 不影響當前工作</span></span></code></pre></div><h3 id="主線程討論與決策流程">主線程討論與決策流程</h3>
<h4 id="決策流程圖">決策流程圖</h4>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[代理人回報] --&gt; B[主線程接收]
    B --&gt; C{嚴重程度?}
    C --&gt;|阻塞| D[立即處理]
    C --&gt;|當日| E[當日處理]
    C --&gt;|資訊性| F[記錄並規劃]

    D --&gt; G[檢視相關文件]
    E --&gt; G
    F --&gt; G

    G --&gt; H[分析影響範圍]
    H --&gt; I{需要專家?}
    I --&gt;|是| J[呼叫 PM 或相關代理人]
    I --&gt;|否| K[主線程決策]

    J --&gt; K
    K --&gt; L[記錄決策理由]
    L --&gt; M[更新受影響任務]
    M --&gt; N[通知相關代理人]
    N --&gt; O[繼續執行]</code></pre><h4 id="決策記錄格式">決策記錄格式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策記錄 - [問題簡述]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策日期**</span>: YYYY-MM-DD
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**相關回報**</span>: [代理人回報連結]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**決策者**</span>: 主線程 / PM代理人
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">---
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">### 問題摘要
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span>[簡要描述問題]
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 決策內容
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span>**決定**: [具體決策]
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gs">**理由**</span>:
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">1.</span> [理由 1]
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">2.</span> [理由 2]
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">3.</span> [理由 3]
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 執行計畫
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> [動作 1]
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">- [ ]</span> [動作 2]
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> [動作 3]
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gu"></span><span class="k">-</span> **更新任務**: [列出需要更新的任務]
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">-</span> **更新文件**: [列出需要更新的文件]
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">-</span> **通知對象**: [列出需要通知的代理人]
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="gu">### 追蹤記錄
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="gu"></span><span class="k">-</span> YYYY-MM-DD: [執行狀態更新]
</span></span><span class="line"><span class="ln">31</span><span class="cl">- YYYY-MM-DD: [完成確認]</span></span></code></pre></div><h3 id="決策傳遞機制">決策傳遞機制</h3>
<h4 id="1-更新主版本工作日誌">1. 更新主版本工作日誌</h4>
<p>在 <code>vX.Y.0-main.md</code> 新增「決策記錄」區域：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 重要決策記錄
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu">### [決策 1 標題]
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="gu"></span><span class="k">-</span> **日期**: YYYY-MM-DD
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> **問題**: [簡述]
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> **決策**: [簡述]
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> **影響**: [列出受影響任務]
</span></span><span class="line"><span class="ln">8</span><span class="cl">- <span class="gs">**詳細**</span>: [連結到完整決策記錄]</span></span></code></pre></div><h4 id="2-更新受影響任務描述">2. 更新受影響任務描述</h4>
<p>所有受影響的任務都需要補充「決策影響」區域。</p>
<h4 id="3-通知相關代理人">3. 通知相關代理人</h4>
<p>如果任務已分派，需要明確通知代理人決策內容。</p>
<h3 id="溝通協作原則">溝通協作原則</h3>
<h4 id="代理人責任">代理人責任</h4>
<ul>
<li><strong>主動回報</strong>：發現問題立即回報，不自行假設</li>
<li><strong>完整資訊</strong>：提供足夠的背景資訊供決策</li>
<li><strong>建議方案</strong>：分析並提供具體建議</li>
<li><strong>等待確認</strong>：阻塞性問題必須等待主線程決策</li>
</ul>
<h4 id="主線程責任">主線程責任</h4>
<ul>
<li><strong>快速響應</strong>：阻塞問題當日處理完畢</li>
<li><strong>充分調研</strong>：檢視相關文件和規範</li>
<li><strong>清楚決策</strong>：明確說明決策理由</li>
<li><strong>完整傳遞</strong>：確保所有受影響方收到資訊</li>
</ul>
<h4 id="協作品質標準">協作品質標準</h4>
<ul>
<li><strong>響應時間</strong>: 阻塞問題 &lt; 2 小時，重要問題 &lt; 1 天</li>
<li><strong>決策品質</strong>: 必須基於專案規範和架構原則</li>
<li><strong>資訊完整</strong>: 所有決策必須文件化並傳遞</li>
<li><strong>追蹤閉環</strong>: 決策執行完成必須確認和記錄</li>
</ul>
<h3 id="任務分派規則">任務分派規則</h3>
<h4 id="1-任務優先序">1. 任務優先序</h4>
<ol>
<li><strong>架構核心任務</strong> - 影響多個模組的基礎架構變更</li>
<li><strong>高風險任務</strong> - 影響關鍵業務邏輯的重構</li>
<li><strong>中風險任務</strong> - 次要功能模組的重構</li>
<li><strong>低風險任務</strong> - 測試檔案和工具檔案的更新</li>
</ol>
<h4 id="2-任務大小控制">2. 任務大小控制</h4>
<p><strong>理想任務大小</strong>：</p>
<ul>
<li>執行時間：1-2 小時</li>
<li>影響檔案：1-5 個檔案</li>
<li>測試影響：單一測試套件</li>
<li>複雜度：單一職責變更</li>
</ul>
<p><strong>任務過大指標</strong>：</p>
<ul>
<li>影響檔案 &gt; 5 個</li>
<li>預估時間 &gt; 3 小時</li>
<li>涉及多個模組</li>
<li>需要連環修改</li>
</ul>
<h4 id="3-升級處理機制">3. 升級處理機制</h4>
<p><strong>何時觸發升級</strong>：</p>
<ul>
<li>子代理人回報任務規模過大</li>
<li>發現任務間複雜依賴關係</li>
<li>需要重新評估技術方案</li>
<li>遇到無法解決的技術問題</li>
</ul>
<p><strong>升級處理流程</strong>：</p>
<ol>
<li>子代理人向主線程回報問題</li>
<li>主線程呼叫 PM 代理人進行分析</li>
<li>PM 代理人拆分任務並建立小版本日誌</li>
<li>依新的小版本日誌重新分派任務</li>
</ol>
<h2 id="任務執行標準">任務執行標準</h2>
<h3 id="任務分派標準格式">任務分派標準格式</h3>
<p>每個任務分派必須包含：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 任務 vX.Y.Z - [任務名稱]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 任務目標
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 明確的完成目標描述
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 具體的技術實現要求
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 參考文件（強制完整填寫）
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu">#### UseCase 參考（必須）
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">UC-XX</span>](<span class="na">/record/app-use-cases.md#uc-xx</span>) - [Use Case 名稱]
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> [<span class="nt">UC-YY</span>](<span class="na">/record/app-use-cases.md#uc-yy</span>) - [相關 Use Case]
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gs">**說明**</span>: 明確指出此任務對應哪些 Use Case，確保實作符合業務需求。
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu">#### 流程圖參考（必須，具體到 Event）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">流程圖名稱 Event X-Y</span>](<span class="na">/record/use-cases/[feature]/[flow-name].md</span>)
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="k">-</span> Event X: [Event 描述] - [對應的類別/方法]
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="k">-</span> Event Y: [Event 描述] - [對應的類別/方法]
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gs">**範例**</span>（來自 v0.11.5）:
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">-</span> [<span class="nt">主流程 Event 6-7</span>](<span class="na">/record/use-cases/unified-api/book-info-query-flow.md</span>)
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="k">-</span> Event 6: BookEnrichmentProcessor.process(rawData) - 處理原始資料
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">-</span> Event 7: BookMetadata.create() - 建立 Domain 實體
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="gs">**說明**</span>: 具體到 Event 編號，讓代理人知道要實作流程圖的哪個部分。
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu">#### 架構規範（必須）
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">Domain 層設計</span>](<span class="na">/record/app-requirements-spec.md#domain-layer</span>) - 層級職責定義
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">-</span> [<span class="nt">錯誤處理規範</span>](<span class="na">/record/app-error-handling-design.md</span>) - 異常處理標準
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">-</span> [<span class="nt">資料轉換規範</span>](<span class="na">/record/domain-transformation-layer-design.md</span>) - DTO 轉換規則
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="gs">**說明**</span>: 確保實作符合專案架構原則。
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="gu">#### 依賴類別（前置任務產出）
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="gu"></span><span class="k">-</span> <span class="sb">`ClassName1`</span> (vX.Y.Z) - [簡短說明]
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">-</span> <span class="sb">`ClassName2`</span> (vX.Y.Z) - [簡短說明]
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="gs">**範例**</span>（來自 v0.11.5）:
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">-</span> <span class="sb">`BookQueryInput`</span> (v0.11.3) - 查詢輸入參數 Value Object
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">-</span> <span class="sb">`QueryType`</span> (v0.11.4) - 查詢類型枚舉
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="k">-</span> <span class="sb">`BookEnrichmentData`</span> (Domain 層) - <span class="gs">**已從 Infrastructure 遷移**</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="gs">**說明**</span>: 列出此任務依賴的前置任務產出，包含版本號方便追溯。
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="gu">#### 測試設計參考（TDD 必須）
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">測試設計文件</span>](<span class="na">/record/agile-programing-methodology/vX.Y.Z-test-design.md#section</span>)
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">-</span> 測試檔案: <span class="sb">`test_file_name_test.dart`</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">-</span> 測試用例數: N 個
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">-</span> 效能基準: [如有特殊要求]
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="gs">**說明**</span>: TDD 開發必須先參考測試設計，瞭解驗收標準。
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="gu">#### 實作範例（如有）
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="gu"></span><span class="k">-</span> [<span class="nt">類似實作參考</span>](<span class="na">/record/agile-programing-methodology/vX.Y.Z-work-log.md#implementation</span>)
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="k">-</span> [<span class="nt">程式碼範例</span>](<span class="na">/record/docs/implementation-examples.md</span>)
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="gs">**說明**</span>: 提供參考範例幫助代理人理解預期實作方式。
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="gu"></span><span class="k">-</span> 需要修改的檔案清單
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="k">-</span> 預估影響的測試檔案
</span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="k">-</span> 影響的依賴關係
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="gu">### 依賴關係
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="gu"></span><span class="k">-</span> 前置任務：[必須完成的前置任務清單]
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="k">-</span> 後續任務：[依賴此任務的後續任務]
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="k">-</span> 並行任務：[可同時進行的相關任務]
</span></span><span class="line"><span class="ln">68</span><span class="cl">
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="gu">### 完成標準
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 功能實現完成
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="k">- [ ]</span> 測試通過率 100%
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="k">- [ ]</span> 程式碼品質檢查通過
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">- [ ]</span> 無新增警告或錯誤
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="k">- [ ]</span> 參考文件更新 (如有需要)
</span></span><span class="line"><span class="ln">75</span><span class="cl">
</span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="gu">### 風險評估
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="gu"></span><span class="k">-</span> 技術風險等級：高/中/低
</span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="k">-</span> 潛在影響評估
</span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="k">-</span> 回滾策略
</span></span><span class="line"><span class="ln">80</span><span class="cl">
</span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="gu">### 指派代理人
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="gu"></span><span class="k">-</span> 主要執行代理人：[Agent Name]
</span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="k">-</span> 檢查代理人：cinnamon-refactor-owl
</span></span><span class="line"><span class="ln">84</span><span class="cl">
</span></span><span class="line"><span class="ln">85</span><span class="cl"><span class="gu">### 後續更新責任 (準備階段特有)
</span></span></span><span class="line"><span class="ln">86</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 更新相關任務的參考文件欄位
</span></span><span class="line"><span class="ln">87</span><span class="cl"><span class="k">- [ ]</span> 補充依賴任務的詳細資訊
</span></span><span class="line"><span class="ln">88</span><span class="cl">- [ ] 建立文件間的關聯性</span></span></code></pre></div><h3 id="任務完成回報格式">任務完成回報格式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 任務完成回報 - vX.Y.Z
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 完成項目
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">- [x]</span> 目標1 完成
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">- [x]</span> 目標2 完成
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">- [x]</span> 測試驗證通過
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 品質指標
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span><span class="k">-</span> 測試通過率：100%
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 程式碼覆蓋率：維持或提升
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 效能影響：無負面影響
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 技術變更摘要
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">-</span> 修改檔案數量：N 個
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 新增程式碼行數：+N 行
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> 移除程式碼行數：-N 行
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 發現問題
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">-</span> 無問題 / 發現問題清單
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu">### 後續建議
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span><span class="k">-</span> 下一個任務的建議
</span></span><span class="line"><span class="ln">23</span><span class="cl">- 潛在改善機會</span></span></code></pre></div><h2 id="品質控制機制">品質控制機制</h2>
<h3 id="強制檢查點">強制檢查點</h3>
<h4 id="任務執行前檢查">任務執行前檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> 任務目標明確定義</li>
<li><input disabled="" type="checkbox"> 完成標準可測量</li>
<li><input disabled="" type="checkbox"> 指派代理人明確</li>
<li><input disabled="" type="checkbox"> 風險評估完成</li>
<li><input disabled="" type="checkbox"> <strong>參考文件完整性（強制）</strong> - UseCase、流程圖 Event、架構規範、依賴類別、測試設計全部填寫</li>
<li><input disabled="" type="checkbox"> <strong>影響範圍明確性（強制）</strong> - 需建立/修改/影響的檔案清單完整列出</li>
</ul>
<h4 id="任務執行中檢查">任務執行中檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> 定期進度回報</li>
<li><input disabled="" type="checkbox"> 問題及時升級</li>
<li><input disabled="" type="checkbox"> 品質標準持續監控</li>
<li><input disabled="" type="checkbox"> 測試覆蓋率維持</li>
</ul>
<h4 id="任務完成後檢查">任務完成後檢查</h4>
<ul>
<li><input disabled="" type="checkbox"> 重構代理人驗證通過</li>
<li><input disabled="" type="checkbox"> 測試通過率 100%</li>
<li><input disabled="" type="checkbox"> 程式碼品質符合標準</li>
<li><input disabled="" type="checkbox"> 文件同步更新</li>
</ul>
<h3 id="階段完成驗證機制">階段完成驗證機制</h3>
<p><strong>重要</strong>: 基於實戰經驗學習，每個開發階段必須通過完整驗證才能標記為完成。</p>
<h4 id="階段完成檢查清單-stage-completion-checklist">階段完成檢查清單 (Stage Completion Checklist)</h4>
<h5 id="1-編譯完整性檢查-compilation-integrity">1. <strong>編譯完整性檢查 (Compilation Integrity)</strong></h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 強制執行的編譯檢查指令</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">flutter analyze lib/ --no-fatal-warnings
</span></span><span class="line"><span class="ln">3</span><span class="cl">dart analyze lib/ --no-fatal-warnings
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 檢查是否有 error 級別問題</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">flutter analyze lib/ 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep -E <span class="s2">&#34;error&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;編譯檢查通過&#34;</span></span></span></code></pre></div><p><strong>通過標準</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 0 個 error 級別問題</li>
<li><input disabled="" type="checkbox"> warning 和 info 級別問題可接受</li>
<li><input disabled="" type="checkbox"> 所有 import 語句正確解析</li>
</ul>
<h5 id="2-依賴路徑一致性檢查-dependency-path-consistency">2. <strong>依賴路徑一致性檢查 (Dependency Path Consistency)</strong></h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 檢查是否有引用不存在檔案的問題</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">flutter analyze lib/ 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep <span class="s2">&#34;Target of URI doesn&#39;t exist&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;路徑檢查通過&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 檢查是否符合 package 導入規範</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">grep -r <span class="s2">&#34;import &#39;\.\.&#34;</span> lib/ <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;無相對路徑導入&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 檢查是否有重複或錯誤的路徑引用</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">find lib/ -name <span class="s2">&#34;*.dart&#34;</span> -exec grep -l <span class="s2">&#34;package:book_overview_app&#34;</span> <span class="o">{}</span> <span class="se">\;</span> <span class="p">|</span> sort</span></span></code></pre></div><p><strong>通過標準</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 無「Target of URI doesn&rsquo;t exist」錯誤</li>
<li><input disabled="" type="checkbox"> 100% 使用 package 導入格式，0% 相對路徑</li>
<li><input disabled="" type="checkbox"> 所有路徑指向正確的檔案位置</li>
</ul>
<h5 id="3-測試通過率檢查-test-pass-rate">3. <strong>測試通過率檢查 (Test Pass Rate)</strong></h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 100% 測試通過率要求</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">dart <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln">3</span><span class="cl">flutter <span class="nb">test</span> --coverage
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 確保所有測試套件都能執行</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">dart <span class="nb">test</span> test/unit/ --reporter<span class="o">=</span>expanded
</span></span><span class="line"><span class="ln">7</span><span class="cl">flutter <span class="nb">test</span> test/widget/ --reporter<span class="o">=</span>expanded
</span></span><span class="line"><span class="ln">8</span><span class="cl">flutter <span class="nb">test</span> test/integration/ --reporter<span class="o">=</span>expanded</span></span></code></pre></div><p><strong>通過標準</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 100% 測試通過率，無例外</li>
<li><input disabled="" type="checkbox"> 所有測試套件正常執行</li>
<li><input disabled="" type="checkbox"> 無測試環境錯誤</li>
<li><input disabled="" type="checkbox"> 測試覆蓋率不下降</li>
</ul>
<h5 id="4-重複實作檢查-duplicate-implementation-check">4. <strong>重複實作檢查 (Duplicate Implementation Check)</strong></h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 檢查是否有重複的服務實作</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">find lib/ -name <span class="s2">&#34;*service*.dart&#34;</span> <span class="p">|</span> sort
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">find lib/ -name <span class="s2">&#34;*provider*.dart&#34;</span> <span class="p">|</span> sort
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 檢查類似命名的類別</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">grep -r <span class="s2">&#34;class.*Service&#34;</span> lib/ --include<span class="o">=</span><span class="s2">&#34;*.dart&#34;</span> <span class="p">|</span> sort
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">grep -r <span class="s2">&#34;class.*Provider&#34;</span> lib/ --include<span class="o">=</span><span class="s2">&#34;*.dart&#34;</span> <span class="p">|</span> sort
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 檢查是否有功能重疊的實作</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;手動檢查是否有功能重複的實作&#34;</span></span></span></code></pre></div><p><strong>通過標準</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 無功能重複的服務實作</li>
<li><input disabled="" type="checkbox"> 類別命名符合單一職責原則</li>
<li><input disabled="" type="checkbox"> 無廢棄或未使用的實作</li>
<li><input disabled="" type="checkbox"> 依賴關係清晰無衝突</li>
</ul>
<h5 id="5-架構一致性檢查-architecture-consistency">5. <strong>架構一致性檢查 (Architecture Consistency)</strong></h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 檢查檔案是否在正確的架構層級</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">ls -la lib/core/services/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">ls -la lib/domains/*/services/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">ls -la lib/presentation/*/services/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 驗證 Clean Architecture 分層</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;確認檔案位置符合 Clean Architecture 原則&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 檢查導入路徑是否符合架構設計</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">grep -r <span class="s2">&#34;import.*lib/core&#34;</span> lib/presentation/ <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;無違反架構的導入&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">grep -r <span class="s2">&#34;import.*lib/presentation&#34;</span> lib/core/ <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;無反向依賴&#34;</span></span></span></code></pre></div><p><strong>通過標準</strong>:</p>
<ul>
<li><input disabled="" type="checkbox"> 檔案位置符合 Clean Architecture 分層</li>
<li><input disabled="" type="checkbox"> 無跨層直接依賴違規</li>
<li><input disabled="" type="checkbox"> 依賴方向正確 (外層依賴內層)</li>
<li><input disabled="" type="checkbox"> 服務檔案位於正確的目錄結構</li>
</ul>
<h4 id="階段完成強制流程"><strong>階段完成強制流程</strong></h4>
<p>每個開發階段必須按以下順序執行：</p>
<ol>
<li><strong>完成階段開發工作</strong></li>
<li><strong>執行編譯檢查</strong> - 必須 0 error</li>
<li><strong>執行測試檢查</strong> - 必須 100% 通過</li>
<li><strong>檢查路徑一致性</strong> - 修正所有 import 問題</li>
<li><strong>檢查重複實作</strong> - 確保無功能重複</li>
<li><strong>驗證架構一致性</strong> - 檔案位置符合設計</li>
<li><strong>更新工作日誌</strong> - 記錄檢查結果</li>
<li><strong>標記階段完成</strong> - 更新 todolist 狀態</li>
</ol>
<h4 id="階段失敗處理原則"><strong>階段失敗處理原則</strong></h4>
<ul>
<li><strong>任何檢查項目失敗 = 階段未完成</strong></li>
<li><strong>必須修正所有問題後重新檢查</strong></li>
<li><strong>不允許「暫時跳過」或「之後處理」</strong></li>
<li><strong>問題修正優先於新功能開發</strong></li>
<li><strong>重複檢查直到所有項目通過</strong></li>
</ul>
<h4 id="檢查清單整合到工作日誌"><strong>檢查清單整合到工作日誌</strong></h4>
<h5 id="每個開發階段的工作日誌必須包含">每個開發階段的工作日誌必須包含</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 階段完成檢查
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 編譯檢查
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> flutter analyze 無 error
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">- [ ]</span> dart analyze 無 error
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">- [ ]</span> 無「Target of URI doesn&#39;t exist」錯誤
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 測試檢查
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> dart test 100% 通過
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">- [ ]</span> flutter test 100% 通過
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">- [ ]</span> 測試覆蓋率檢查完成
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 一致性檢查
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 無相對路徑 import
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">- [ ]</span> 無重複服務實作
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 檔案位置符合架構設計
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu">### 最終確認
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 所有檢查項目通過
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">- [ ]</span> 工作日誌更新完成
</span></span><span class="line"><span class="ln">21</span><span class="cl">- [ ] 準備進入下一階段</span></span></code></pre></div><h4 id="hook-系統整合-1"><strong>Hook 系統整合</strong></h4>
<p>此階段完成驗證機制與現有 Hook 系統整合：</p>
<ul>
<li><strong>PostEdit Hook</strong> - 檔案修改後自動檢查編譯狀態</li>
<li><strong>Version Check Hook</strong> - 階段推進前強制執行完整檢查</li>
<li><strong>Code Smell Detection Hook</strong> - 即時偵測重複實作問題</li>
<li><strong>PM Trigger Hook</strong> - 檢查失敗時觸發專案管理介入</li>
</ul>
<h3 id="失敗處理機制">失敗處理機制</h3>
<h4 id="任務失敗類型">任務失敗類型</h4>
<ol>
<li><strong>技術失敗</strong> - 無法達成技術目標</li>
<li><strong>品質失敗</strong> - 無法通過品質檢查</li>
<li><strong>時程失敗</strong> - 超出預估完成時間</li>
<li><strong>範圍失敗</strong> - 任務範圍超出預期</li>
</ol>
<h4 id="失敗處理策略">失敗處理策略</h4>
<ol>
<li><strong>技術失敗</strong> → PM 代理人重新設計技術方案</li>
<li><strong>品質失敗</strong> → 重構代理人指導修正</li>
<li><strong>時程失敗</strong> → PM 代理人拆分任務</li>
<li><strong>範圍失敗</strong> → 主線程重新分派任務</li>
</ol>
<h2 id="進度追蹤機制">進度追蹤機制</h2>
<h3 id="三重文件原則整合">三重文件原則整合</h3>
<p>本方法論的進度追蹤完全基於「三重文件原則」（詳見前述章節）：</p>
<ol>
<li><strong>CHANGELOG.md</strong> - 版本功能變動（面向用戶）</li>
<li><strong>todolist.md</strong> - 任務全景圖（面向開發）</li>
<li><strong>work-log/</strong> - 實作細節（面向交接）</li>
</ol>
<h3 id="三層進度管理對應-work-log">三層進度管理（對應 work-log）</h3>
<p><strong>重要說明</strong>：三層進度管理主要對應到 work-log 文件系統，與 CHANGELOG 和 todolist 協同運作。</p>
<h4 id="1-大版本層級-vxyx---對應主版本工作日誌">1. 大版本層級 (vX.Y.x) - 對應主版本工作日誌</h4>
<ul>
<li><strong>追蹤文件</strong>: <code>docs/work-logs/vX.Y.0-main.md</code> (主版本總覽)</li>
<li><strong>對應三重文件</strong>:
<ul>
<li><strong>work-log</strong>: 詳細任務分派和執行記錄</li>
<li><strong>todolist</strong>: vX.Y.x 系列所有任務清單</li>
<li><strong>CHANGELOG</strong>: 版本發布時提取功能變動</li>
</ul>
</li>
<li><strong>更新頻率</strong>: 任務分派時、階段完成時</li>
<li><strong>責任代理</strong>: 主線程、PM 代理人</li>
</ul>
<h4 id="2-小版本層級-vxy1-vxy2----對應小版本工作日誌">2. 小版本層級 (vX.Y.1, vX.Y.2, &hellip;) - 對應小版本工作日誌</h4>
<ul>
<li><strong>追蹤文件</strong>: <code>docs/work-logs/vX.Y.Z-[specific-task].md</code> (具體任務)</li>
<li><strong>對應三重文件</strong>:
<ul>
<li><strong>work-log</strong>: 完整的實作細節和 TDD 階段記錄</li>
<li><strong>todolist</strong>: 單一任務狀態更新</li>
<li><strong>CHANGELOG</strong>: 任務完成後潛在的功能變動點</li>
</ul>
</li>
<li><strong>更新頻率</strong>: 任務執行中、任務完成時</li>
<li><strong>責任代理</strong>: 執行代理人、文件代理人</li>
</ul>
<h4 id="3-任務層級-todolist---對應-todolistmd">3. 任務層級 (TodoList) - 對應 todolist.md</h4>
<ul>
<li><strong>追蹤工具</strong>: TodoWrite 工具 + <code>docs/todolist.md</code></li>
<li><strong>對應三重文件</strong>:
<ul>
<li><strong>todolist</strong>: 任務狀態即時追蹤</li>
<li><strong>work-log</strong>: 任務詳細資訊來源</li>
<li><strong>CHANGELOG</strong>: 完成任務累積的功能變動</li>
</ul>
</li>
<li><strong>更新頻率</strong>: 即時更新</li>
<li><strong>責任代理</strong>: 所有代理人</li>
</ul>
<h3 id="三重文件同步機制">三重文件同步機制</h3>
<h4 id="任務執行流程中的文件更新">任務執行流程中的文件更新</h4>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">sequenceDiagram
    participant 主線程
    participant 執行代理人
    participant todolist
    participant work-log
    participant CHANGELOG

    主線程-&gt;&gt;todolist: 檢視下一個待執行任務
    主線程-&gt;&gt;work-log: 查看 vX.Y.0-main.md 分派清單
    主線程-&gt;&gt;執行代理人: 分派任務 vX.Y.Z

    執行代理人-&gt;&gt;work-log: 記錄執行過程到 vX.Y.Z.md
    執行代理人-&gt;&gt;todolist: 更新任務狀態為「進行中」

    執行代理人-&gt;&gt;work-log: 標記任務完成
    執行代理人-&gt;&gt;todolist: 更新任務狀態為「完成」

    Note over 主線程,CHANGELOG: 版本發布時
    主線程-&gt;&gt;work-log: 檢視所有已完成任務
    主線程-&gt;&gt;CHANGELOG: 提取功能變動
    主線程-&gt;&gt;todolist: 確認版本完成</code></pre><h4 id="版本發布時的三重文件協調">版本發布時的三重文件協調</h4>
<ol>
<li>
<p><strong>文件代理人檢查 work-log</strong>：</p>
<ul>
<li>讀取 <code>vX.Y.0-main.md</code> 所有已完成任務</li>
<li>讀取每個 <code>vX.Y.Z-task.md</code> 實作細節</li>
</ul>
</li>
<li>
<p><strong>提取功能變動到 CHANGELOG</strong>：</p>
<ul>
<li>只記錄用戶可感知的功能變動</li>
<li>忽略內部重構和技術細節</li>
<li>依照 Keep a Changelog 格式撰寫</li>
</ul>
</li>
<li>
<p><strong>驗證 todolist 一致性</strong>：</p>
<ul>
<li>確認 todolist 標記的完成任務與 work-log 一致</li>
<li>確認版本號對應正確</li>
</ul>
</li>
</ol>
<h3 id="關鍵指標監控">關鍵指標監控</h3>
<h4 id="效率指標">效率指標</h4>
<ul>
<li>任務完成速度 (任務數/天)</li>
<li>平均任務執行時間</li>
<li>升級請求頻率</li>
<li>重做次數</li>
</ul>
<h4 id="品質指標">品質指標</h4>
<ul>
<li>測試通過率趨勢</li>
<li>程式碼覆蓋率變化</li>
<li>新增錯誤/警告數量</li>
<li>效能回歸情況</li>
</ul>
<h4 id="協作指標">協作指標</h4>
<ul>
<li>代理人回應時間</li>
<li>任務交接效率</li>
<li>溝通品質評分</li>
<li>問題解決時間</li>
</ul>
<h2 id="成功標準">成功標準</h2>
<h3 id="方法論成功指標">方法論成功指標</h3>
<h4 id="效率成功標準">效率成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 30-40 個小版本任務如期完成</li>
<li><input disabled="" type="checkbox"> 每個任務平均完成時間 &lt; 2 小時</li>
<li><input disabled="" type="checkbox"> 升級請求 &lt; 10% 總任務數</li>
<li><input disabled="" type="checkbox"> 重做次數 &lt; 5% 總任務數</li>
</ul>
<h4 id="品質成功標準">品質成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 100% 測試通過率維持</li>
<li><input disabled="" type="checkbox"> 程式碼覆蓋率不下降</li>
<li><input disabled="" type="checkbox"> 0 個新增的錯誤或警告</li>
<li><input disabled="" type="checkbox"> 效能目標全部達成</li>
</ul>
<h4 id="協作成功標準">協作成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 代理人角色職責清晰</li>
<li><input disabled="" type="checkbox"> 任務交接無遺漏</li>
<li><input disabled="" type="checkbox"> 問題升級機制有效</li>
<li><input disabled="" type="checkbox"> 文件同步及時準確</li>
</ul>
<h4 id="階段驗證成功標準">階段驗證成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 每個階段 100% 通過編譯檢查</li>
<li><input disabled="" type="checkbox"> 每個階段 100% 通過測試檢查</li>
<li><input disabled="" type="checkbox"> 每個階段 100% 通過路徑一致性檢查</li>
<li><input disabled="" type="checkbox"> 每個階段 100% 通過重複實作檢查</li>
<li><input disabled="" type="checkbox"> 每個階段 100% 通過架構一致性檢查</li>
<li><input disabled="" type="checkbox"> 階段失敗率 &lt; 5%</li>
<li><input disabled="" type="checkbox"> 平均階段驗證時間 &lt; 30 分鐘</li>
</ul>
<h3 id="重構成功標準">重構成功標準</h3>
<h4 id="架構成功標準">架構成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 完全符合設計文件 v2.0 規範</li>
<li><input disabled="" type="checkbox"> 原生 Exception 系統實作完成</li>
<li><input disabled="" type="checkbox"> 舊錯誤處理系統完全移除</li>
<li><input disabled="" type="checkbox"> 統一錯誤處理入口建立</li>
</ul>
<h4 id="效能成功標準">效能成功標準</h4>
<ul>
<li><input disabled="" type="checkbox"> 錯誤建立時間 &lt; 0.1ms</li>
<li><input disabled="" type="checkbox"> 記憶體占用 &lt; 200 bytes</li>
<li><input disabled="" type="checkbox"> 程式碼量減少 ~80%</li>
<li><input disabled="" type="checkbox"> 編譯時間改善</li>
</ul>
<h2 id="敏捷開發本質">敏捷開發本質</h2>
<h3 id="核心理念">核心理念</h3>
<h4 id="敏捷開發是持續發現快速調整資訊流動的動態過程">敏捷開發是持續發現、快速調整、資訊流動的動態過程</h4>
<h3 id="資訊流動機制">資訊流動機制</h3>
<h4 id="phase-間的資訊傳遞">Phase 間的資訊傳遞</h4>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[Phase 1 設計] --&gt;|架構決策| B[Phase 2 測試設計]
    B --&gt;|實作指引| C[Phase 3 實作]
    C --&gt;|重構發現| D[Phase 4 重構]

    B --&gt;|問題識別| E[架構調整]
    E --&gt;|更新設計| A
    E --&gt;|更新測試| B
    E --&gt;|更新實作| C

    C --&gt;|技術債務| F[文件補充]
    F --&gt;|參考資料| C
    F --&gt;|設計優化| A</code></pre><h4 id="v011-實際資訊流範例">v0.11 實際資訊流範例</h4>
<p><strong>Phase 1 → Phase 2 傳遞</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 1 (v0.11.1) 產出:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">-</span> 44 個 Event 流程設計
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> 3 個核心流程圖
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 架構層級劃分
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Phase 2 (v0.11.2) 接收並發現:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 流程圖定義清楚
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 注意：BookEnrichmentData 位置錯誤
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 4 個命名不一致
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> Domain 服務層缺失
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">Phase 2 回饋 Phase 1:
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 修正架構問題 → 立即執行
</span></span><span class="line"><span class="ln">14</span><span class="cl">- 更新流程圖命名 → 完成修正</span></span></code></pre></div><p><strong>Phase 2 → Phase 3 傳遞</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">Phase 2 (v0.11.2) 產出:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">-</span> 335 個測試用例設計
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> 33 個測試檔案規劃
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> 架構修正決策
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 時程調整 (Plan B)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Phase 3 (v0.11.3-14) 接收並執行:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 測試先行 TDD 開發
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 依賴 Phase 2 架構修正
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 參考測試設計驗收標準
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> 遵循調整後時程
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">Phase 3 動態更新 Phase 2 發現:
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> 補充任務參考文件
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 更新依賴類別資訊
</span></span><span class="line"><span class="ln">16</span><span class="cl">- 調整實作優先序</span></span></code></pre></div><h3 id="靈活任務交接">靈活任務交接</h3>
<h4 id="交接不是單向傳遞">交接不是單向傳遞</h4>
<p><strong>傳統錯誤模式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Phase 1 完成 → 凍結
</span></span><span class="line"><span class="ln">2</span><span class="cl">Phase 2 開始 → 發現問題但不敢改 Phase 1
</span></span><span class="line"><span class="ln">3</span><span class="cl">Phase 3 繼續 → 基於錯誤的設計實作</span></span></code></pre></div><p><strong>敏捷正確模式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Phase 1 完成 → 基礎版本
</span></span><span class="line"><span class="ln">2</span><span class="cl">Phase 2 發現 → 立即回報並調整 Phase 1
</span></span><span class="line"><span class="ln">3</span><span class="cl">Phase 1 更新 → 同步更新 Phase 2 和 Phase 3 參考
</span></span><span class="line"><span class="ln">4</span><span class="cl">Phase 3 執行 → 基於最新、最正確的設計</span></span></code></pre></div><h4 id="任務交接檢查清單">任務交接檢查清單</h4>
<p><strong>前置任務完成時</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 任務交接檢查 - vX.Y.Z 完成
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 產出清單
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 程式碼檔案: [列出新建/修改檔案]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">- [ ]</span> 測試檔案: [列出測試覆蓋]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">- [ ]</span> 文件更新: [列出相關文件]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu">### 後續任務更新
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 檢查哪些任務依賴此產出
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">- [ ]</span> 更新後續任務的「依賴類別」參考
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">- [ ]</span> 補充實作範例連結
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">- [ ]</span> 通知相關代理人（如已分派）
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu">### 發現問題記錄
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 是否有架構問題影響後續任務
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">- [ ]</span> 是否有命名需要統一
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">- [ ]</span> 是否有技術債務需要標註
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">- [ ]</span> 是否需要調整後續任務範圍
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu">### 主版本日誌更新
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="gu"></span><span class="k">- [ ]</span> 標記任務完成
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">- [ ]</span> 補充「重要發現」區域
</span></span><span class="line"><span class="ln">23</span><span class="cl">- [ ] 更新進度統計</span></span></code></pre></div><h3 id="持續文件精煉">持續文件精煉</h3>
<h4 id="文件不是一次性產物">文件不是一次性產物</h4>
<p><strong>文件生命週期</strong>：</p>





<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph LR
    A[初始版本] --&gt; B[Phase 1 補充]
    B --&gt; C[Phase 2 修正]
    C --&gt; D[Phase 3 完善]
    D --&gt; E[Phase 4 定版]

    C --&gt;|發現問題| F[緊急更新]
    F --&gt; C

    D --&gt;|實作發現| G[補充範例]
    G --&gt; D</code></pre><h4 id="文件更新觸發點">文件更新觸發點</h4>
<h5 id="1-強制更新時機">1. 強制更新時機</h5>
<ul>
<li>架構變更 → 立即更新所有相關文件</li>
<li>介面簽名變更 → 更新流程圖和參考</li>
<li>依賴關係變化 → 更新任務依賴清單</li>
<li>命名修正 → 全專案統一更新</li>
</ul>
<h5 id="2-建議更新時機">2. 建議更新時機</h5>
<ul>
<li>發現更好的實作方式 → 補充範例</li>
<li>常見問題解決 → 補充 FAQ</li>
<li>效能優化技巧 → 補充最佳實踐</li>
<li>測試技巧發現 → 補充測試指引</li>
</ul>
<h5 id="3-v011-實際更新案例">3. v0.11 實際更新案例</h5>
<h6 id="案例-1架構修正觸發連鎖更新">案例 1：架構修正觸發連鎖更新</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">觸發: BookEnrichmentData 遷移到 Domain 層
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">更新文件清單:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">1.</span> 流程圖 book-info-query-flow.md
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   <span class="k">-</span> Event 6-7 層級標註
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">2.</span> 主版本日誌 v0.11.0-main.md
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   <span class="k">-</span> 架構調整記錄
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   <span class="k">-</span> v0.11.5, v0.11.6 依賴類別更新
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">3.</span> 測試設計 v0.11.2-tdd-phase2-test-design.md
</span></span><span class="line"><span class="ln">12</span><span class="cl">   <span class="k">-</span> Mock 設定路徑更新
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">4.</span> 所有後續任務描述
</span></span><span class="line"><span class="ln">15</span><span class="cl">   <span class="k">-</span> 參考文件補充
</span></span><span class="line"><span class="ln">16</span><span class="cl">   - import 路徑範例</span></span></code></pre></div><h6 id="案例-2phase-2-發現補充任務參考">案例 2：Phase 2 發現補充任務參考</h6>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl">觸發: Phase 2 完成測試設計
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">補充內容:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">1.</span> v0.11.5-14 所有任務
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   <span class="k">-</span> 新增「測試設計參考」欄位
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   <span class="k">-</span> 具體到測試檔案名稱和用例數
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> v0.11.0-main.md
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   <span class="k">-</span> 補充「Phase 2 重要發現」區域
</span></span><span class="line"><span class="ln">10</span><span class="cl">   <span class="k">-</span> 記錄時程調整和原因
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">3.</span> 依賴類別參考
</span></span><span class="line"><span class="ln">13</span><span class="cl">   <span class="k">-</span> v0.11.3, v0.11.4 產出類別
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 列入所有相關任務參考</span></span></code></pre></div><h3 id="響應式規劃">響應式規劃</h3>
<h4 id="規劃不是固定藍圖">規劃不是固定藍圖</h4>
<p><strong>初始規劃基於</strong>：</p>
<ul>
<li>現有知識和經驗</li>
<li>需求分析結果</li>
<li>架構設計假設</li>
<li>風險初步評估</li>
</ul>
<p><strong>執行中調整基於</strong>：</p>
<ul>
<li>實際發現的問題</li>
<li>技術方案驗證結果</li>
<li>測試設計識別的複雜度</li>
<li>代理人的回報和建議</li>
</ul>
<h4 id="調整決策標準">調整決策標準</h4>
<h5 id="何時調整規劃">何時調整規劃？</h5>
<p><strong>立即調整（阻塞性）</strong>：</p>
<ul>
<li>架構設計根本性錯誤</li>
<li>依賴關係衝突無法解決</li>
<li>技術方案不可行</li>
<li>測試通過率無法達成</li>
</ul>
<p><strong>範例</strong>：BookEnrichmentData 位置錯誤 → 立即停止並修正</p>
<p><strong>計畫調整（重要性）</strong>：</p>
<ul>
<li>任務複雜度超出預期</li>
<li>需要額外的前置任務</li>
<li>測試用例數量大幅增加</li>
<li>發現缺失的功能模組</li>
</ul>
<p><strong>範例</strong>：Domain 服務層缺失 → 調整時程從 4-6 天到 6-7 天</p>
<p><strong>優化調整（改善性）</strong>：</p>
<ul>
<li>發現更好的實作方式</li>
<li>可以提前完成的任務</li>
<li>可以合併的重複工作</li>
<li>效能優化機會</li>
</ul>
<h5 id="調整必須文件化">調整必須文件化</h5>
<p><strong>調整決策記錄格式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 規劃調整決策 - [調整標題]
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**調整日期**</span>: YYYY-MM-DD
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**觸發原因**</span>: [為什麼需要調整]
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**影響範圍**</span>: [哪些任務受影響]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 原始規劃
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> 任務數量: N 個
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 預估時程: X 天
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 假設條件: [列出]
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 調整後規劃
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">-</span> 任務數量: M 個
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> 預估時程: Y 天
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 調整原因: [詳細說明]
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">### 具體變更
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu"></span><span class="k">1.</span> [變更 1]
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">2.</span> [變更 2]
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">3.</span> [變更 3]
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 風險評估
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> 新風險: [識別出的新風險]
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> 緩解措施: [對應的處理方式]
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 溝通記錄
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span><span class="k">-</span> YYYY-MM-DD: [通知相關代理人]
</span></span><span class="line"><span class="ln">28</span><span class="cl">- YYYY-MM-DD: [更新主版本日誌]</span></span></code></pre></div><h4 id="v011-規劃調整實例">v0.11 規劃調整實例</h4>
<p><strong>Plan B 決策</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 規劃調整決策 - v0.11.0 採用 Plan B
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**調整日期**</span>: 2025-09-30
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**觸發原因**</span>: Phase 2 識別出 28 個 TDD 新建測試和 Domain 層缺失
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 原始規劃
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">-</span> 任務數量: 10-12 個小版本
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 預估時程: 4-6 天
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 假設: 60% 實作已完成，40% 補充測試
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu">### 調整後規劃
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu"></span><span class="k">-</span> 任務數量: 14 個小版本
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">-</span> 預估時程: 6-7 天 (+1-2 天)
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> 原因: Domain 層需從零建立，測試覆蓋率需 100%
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu">### 具體變更
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 新增 v0.11.3-4：Domain 輸入驗證和查詢服務
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">2.</span> 延後外部 API 整合到 v0.12.0
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">3.</span> 簡化速率控制，移除批次處理和斷路器
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">4.</span> 專注核心功能，確保品質優先
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu">### 風險評估
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="gu"></span><span class="k">-</span> 新風險: 時程延長 1-2 天
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">-</span> 緩解: 簡化外部 API 整合，移到下個版本
</span></span><span class="line"><span class="ln">25</span><span class="cl">- 好處: Domain 層基礎扎實，後續開發更順暢</span></span></code></pre></div><h3 id="敏捷成功關鍵">敏捷成功關鍵</h3>
<h4 id="1-資訊透明化">1. 資訊透明化</h4>
<ul>
<li>所有發現立即記錄</li>
<li>所有決策公開透明</li>
<li>所有變更同步通知</li>
</ul>
<h4 id="2-快速響應能力">2. 快速響應能力</h4>
<ul>
<li>問題當日解決</li>
<li>調整迅速執行</li>
<li>文件即時更新</li>
</ul>
<h4 id="3-持續改進循環">3. 持續改進循環</h4>
<ul>
<li>每個 Phase 總結學習</li>
<li>問題根因分析</li>
<li>流程持續優化</li>
</ul>
<h4 id="4-團隊協作文化">4. 團隊協作文化</h4>
<ul>
<li>鼓勵主動回報</li>
<li>容許計畫調整</li>
<li>品質高於速度</li>
</ul>
<h2 id="使用指引">使用指引</h2>
<h3 id="主線程使用方式">主線程使用方式</h3>
<ol>
<li><strong>檢查主版本工作日誌</strong>，找到下一個待執行任務</li>
<li><strong>分派任務</strong>給適當的子代理人，使用標準任務格式</li>
<li><strong>等待回報</strong>，不親自執行任何程式碼修改</li>
<li><strong>處理升級</strong>，必要時呼叫 PM 代理人</li>
<li><strong>確認完成</strong>，由重構代理人和文件代理人完成驗證</li>
</ol>
<h3 id="子代理人使用方式">子代理人使用方式</h3>
<ol>
<li><strong>接收任務</strong>，確認任務目標和完成標準</li>
<li><strong>評估規模</strong>，如發現過大立即向上回報</li>
<li><strong>執行任務</strong>，專注於指派的具體工作</li>
<li><strong>回報結果</strong>，使用標準回報格式</li>
<li><strong>配合檢查</strong>，與重構代理人協作完成驗證</li>
</ol>
<h3 id="緊急情況處理">緊急情況處理</h3>
<h4 id="代理人無法執行任務">代理人無法執行任務</h4>
<ol>
<li>立即向主線程回報</li>
<li>主線程評估替代方案</li>
<li>必要時調整任務或更換代理人</li>
</ol>
<h4 id="技術方案遇到阻礙">技術方案遇到阻礙</h4>
<ol>
<li>子代理人立即停止執行</li>
<li>向主線程詳細回報問題</li>
<li>主線程呼叫 PM 代理人重新設計</li>
</ol>
<h4 id="品質標準無法達成">品質標準無法達成</h4>
<ol>
<li>重構代理人拒絕通過</li>
<li>明確指出品質問題</li>
<li>要求修正或重新設計</li>
</ol>
<hr>
<h2 id="v0110-實戰案例集">v0.11.0 實戰案例集</h2>
<h3 id="案例總覽">案例總覽</h3>
<p>v0.11.0 統一書籍資訊查詢服務開發過程，完整展現了敏捷重構方法論的實際應用，包含任務拆分、動態調整、代理人協作等核心流程。</p>
<h3 id="案例-1phase-2-發現觸發-plan-b-調整">案例 1：Phase 2 發現觸發 Plan B 調整</h3>
<h4 id="背景">背景</h4>
<p><strong>初始規劃</strong>：</p>
<ul>
<li>預估時程：4-6 天</li>
<li>假設：60% 程式碼已完成，補充測試即可</li>
<li>任務數：10-12 個小版本</li>
</ul>
<h4 id="觸發事件">觸發事件</h4>
<p>sage-test-architect 完成 Phase 2 測試設計後回報：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 重大發現 - Phase 2 測試設計
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 實作狀態檢查結果
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 已完成：GoogleBooksApiImplementation 已完整實作
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 已完成：GoogleBooksDto 和轉換已實作
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> 缺失：Domain 服務層完全缺失（0% 完成度）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> 待辦：需要 28 個 TDD 新建測試
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 注意：BookEnrichmentData 位置錯誤
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 影響評估
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">-</span> 預估工作量：原估 2-3 天 → 實際需 4-5 天
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">-</span> 複雜度：低 → 高（需從零建立 Domain 層）
</span></span><span class="line"><span class="ln">13</span><span class="cl">- 風險：中 → 高（架構債務累積風險）</span></span></code></pre></div><h4 id="主線程決策過程">主線程決策過程</h4>
<ol>
<li>
<p><strong>檢視相關文件</strong>：</p>
<ul>
<li>閱讀 Clean Architecture 規範</li>
<li>確認 Domain 層職責定義</li>
<li>評估架構債務風險</li>
</ul>
</li>
<li>
<p><strong>召開決策會議</strong>（主線程 + PM代理人）：</p>
<ul>
<li>分析時程影響</li>
<li>評估功能範圍調整可能性</li>
<li>制定 Plan A、Plan B 方案</li>
</ul>
</li>
<li>
<p><strong>決策記錄</strong>：</p>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 決策 - v0.11.0 採用 Plan B
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**決策日期**</span>: 2025-09-30
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**決策者**</span>: 主線程
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 方案比較
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span>**Plan A**: 維持原時程，降低測試覆蓋率
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> 優點：時程不變 (4-6 天)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> 缺點：測試覆蓋率 <span class="p">&lt;</span> <span class="nt">80</span><span class="err">%</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> <span class="na">缺點</span><span class="err">：</span><span class="na">架構債務累積</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">-</span> <span class="na">缺點</span><span class="err">：</span><span class="na">違反品質標準</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gs">**Plan B**</span><span class="na">:</span> <span class="na">延長時程</span><span class="err">，</span><span class="na">確保品質</span><span class="err">（</span><span class="na">採用</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> <span class="na">優點</span><span class="err">：</span><span class="na">測試覆蓋率</span> <span class="na">100</span><span class="err">%</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> <span class="na">優點</span><span class="err">：</span><span class="na">Domain</span> <span class="na">層基礎扎實</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">-</span> <span class="na">優點</span><span class="err">：</span><span class="na">架構符合規範</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">-</span> <span class="na">注意</span><span class="err">：</span><span class="na">時程延長</span> <span class="err">+</span><span class="na">1-2</span> <span class="na">天</span> <span class="err">(→</span> <span class="na">6-7</span> <span class="na">天</span><span class="err">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu">### 決定
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="gu"></span><span class="na">採用</span> <span class="na">Plan</span> <span class="na">B</span><span class="err">，</span><span class="na">理由</span><span class="err">：</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">1.</span> <span class="na">品質優於速度</span><span class="err">（</span><span class="na">專案鐵律</span><span class="err">）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">2.</span> <span class="na">Domain</span> <span class="na">層是核心基礎</span><span class="err">，</span><span class="na">不能妥協</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">3.</span> <span class="na">延長</span> <span class="na">1-2</span> <span class="na">天可接受</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">4.</span> <span class="na">簡化外部</span> <span class="na">API</span> <span class="na">整合補償時程</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="gu">### 執行計畫
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="gu"></span><span class="k">1.</span> <span class="na">新增</span> <span class="na">v0</span><span class="err">.</span><span class="na">11</span><span class="err">.</span><span class="na">3-4</span><span class="err">：</span><span class="na">Domain</span> <span class="na">輸入驗證和查詢服務</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">2.</span> <span class="na">延後外部</span> <span class="na">API</span> <span class="na">整合</span><span class="err">（</span><span class="na">Amazon</span><span class="err">、</span><span class="na">台灣出版品</span><span class="err">）</span><span class="na">到</span> <span class="na">v0</span><span class="err">.</span><span class="na">12</span><span class="err">.</span><span class="na">0</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">3.</span> <span class="na">簡化速率控制</span><span class="err">，</span><span class="na">移除批次處理和斷路器</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="na">4</span><span class="err">.</span> <span class="na">更新所有後續任務參考文件</span></span></span></code></pre></div><h4 id="執行結果">執行結果</h4>
<ul>
<li>v0.11.3-4 成功完成（71 個測試 100% 通過）</li>
<li>優點：Domain 層基礎扎實</li>
<li>時程控制良好（實際 +1 天）</li>
<li>品質標準維持 100%</li>
</ul>
<h4 id="學習要點">學習要點</h4>
<ol>
<li><strong>測試設計階段至關重要</strong>：能提前識別實作狀態問題</li>
<li><strong>品質不妥協</strong>：短期時程延長換長期穩定發展</li>
<li><strong>靈活調整功能範圍</strong>：延後次要功能，專注核心價值</li>
<li><strong>決策必須文件化</strong>：清楚記錄原因和權衡</li>
</ol>
<h3 id="案例-2架構衝突檢測與立即修正">案例 2：架構衝突檢測與立即修正</h3>
<h4 id="背景-1">背景</h4>
<p>Phase 2 測試設計過程中，sage-test-architect 發現 <code>BookEnrichmentData</code> 位於錯誤層級。</p>
<h4 id="問題描述">問題描述</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 架構衝突 - BookEnrichmentData 位置錯誤
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 當前狀態
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> 位置: <span class="sb">`lib/infrastructure/api/google_books/book_enrichment_data.dart`</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> 職責: 純資料結構，封裝書籍豐富化資料
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 問題分析
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">-</span> BookEnrichmentData 無外部依賴，應屬於 Domain 層
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">-</span> Infrastructure 層不應定義 Domain 實體
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">-</span> 違反 Clean Architecture 依賴方向
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="gu">### 影響範圍
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></span><span class="k">-</span> v0.11.5 (資料處理) 需要引用
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">-</span> v0.11.6 (API 客戶端) 需要轉換
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">-</span> 所有測試需要更新 import 路徑
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="gu">### 建議
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="gu"></span>立即遷移到 Domain 層，阻塞性問題。</span></span></code></pre></div><h4 id="主線程處理">主線程處理</h4>
<ol>
<li><strong>確認問題嚴重性</strong>：阻塞（影響後續所有任務）</li>
<li><strong>立即停止 Phase 3 啟動</strong>：等待架構修正完成</li>
<li><strong>指派緊急任務</strong>：</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 緊急任務 - v0.11.2-fix BookEnrichmentData 遷移
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**優先級**</span>: 最高
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**執行人**</span>: mint-format-specialist
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="gs">**預估時間**</span>: 0.5 小時
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu">### 執行步驟
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 遷移檔案到 <span class="sb">`lib/domains/book_info/entities/`</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">2.</span> 更新所有 import 引用
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">3.</span> 執行測試確認無錯誤
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">4.</span> 提交變更
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu">### 完成後動作
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 更新流程圖層級標註
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">2.</span> 更新 v0.11.5-6 任務參考
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">3.</span> 通知所有相關代理人
</span></span><span class="line"><span class="ln">17</span><span class="cl">4. 繼續 v0.11.3 執行</span></span></code></pre></div><ol start="4">
<li><strong>執行和追蹤</strong>：
<ul>
<li>0.4 小時完成遷移</li>
<li>Git commit: 58b1603</li>
<li>更新相關文件</li>
<li>解除 Phase 3 阻塞</li>
</ul>
</li>
</ol>
<h4 id="執行結果-1">執行結果</h4>
<ul>
<li>架構問題立即解決</li>
<li>延遲影響最小化（&lt; 1 小時）</li>
<li>後續任務基於正確架構</li>
<li>無技術債務累積</li>
</ul>
<h4 id="學習要點-1">學習要點</h4>
<ol>
<li><strong>架構問題零容忍</strong>：立即停止並修正</li>
<li><strong>緊急任務機制</strong>：快速插入高優先級任務</li>
<li><strong>影響範圍評估</strong>：精確識別受影響任務並更新</li>
<li><strong>追蹤閉環</strong>：確認完成並解除阻塞</li>
</ol>
<h3 id="案例-3命名不一致的系統化修正">案例 3：命名不一致的系統化修正</h3>
<h4 id="背景-2">背景</h4>
<p>Phase 2 測試設計發現 4 個流程圖元件命名與程式碼不符。</p>
<h4 id="問題清單">問題清單</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 命名不一致清單
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">1.</span> 流程圖: QueryValidator
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   程式碼: BookInputValidator
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   影響: Event 1-2
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">2.</span> 流程圖: BookInfoService
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   程式碼: UnifiedBookInfoService
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   影響: Event 3-5
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">3.</span> 流程圖: DataProcessor
</span></span><span class="line"><span class="ln">12</span><span class="cl">   程式碼: BookEnrichmentProcessor
</span></span><span class="line"><span class="ln">13</span><span class="cl">   影響: Event 6-7
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">4.</span> 流程圖: CacheService
</span></span><span class="line"><span class="ln">16</span><span class="cl">   程式碼: ApiCacheManager
</span></span><span class="line"><span class="ln">17</span><span class="cl">   影響: Event 8-9</span></span></code></pre></div><h4 id="決策過程">決策過程</h4>
<p><strong>選項 A</strong>：更新流程圖命名</p>
<ul>
<li>流程圖是設計文件，應該貼近業務語意</li>
<li>程式碼已實作，更新成本高</li>
<li>捨棄：流程圖命名過於簡化</li>
</ul>
<p><strong>選項 B</strong>：更新程式碼命名</p>
<ul>
<li>程式碼命名更具體清楚</li>
<li>符合 Domain 層命名原則</li>
<li>採用：流程圖更新成本低</li>
</ul>
<h4 id="執行計畫">執行計畫</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 命名統一任務
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gs">**執行**</span>: memory-network-builder
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gs">**時程**</span>: 0.5 小時
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="gu">### 更新範圍
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 更新 3 個流程圖文件
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">2.</span> 更新主版本工作日誌
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">3.</span> 更新所有任務參考
</span></span><span class="line"><span class="ln">10</span><span class="cl">4. Git commit 記錄變更</span></span></code></pre></div><h4 id="執行結果-2">執行結果</h4>
<ul>
<li>流程圖命名統一</li>
<li>Git commit: 79992cb</li>
<li>所有任務參考同步更新</li>
<li>代理人無歧義參考</li>
</ul>
<h4 id="學習要點-2">學習要點</h4>
<ol>
<li><strong>命名一致性至關重要</strong>：避免代理人困惑</li>
<li><strong>及早發現及早處理</strong>：Phase 2 發現是最佳時機</li>
<li><strong>全專案同步更新</strong>：確保所有引用一致</li>
<li><strong>決策權衡明確</strong>：記錄為什麼選擇特定方案</li>
</ol>
<h3 id="案例-4工作日誌架構重組">案例 4：工作日誌架構重組</h3>
<h4 id="背景-3">背景</h4>
<p>v0.11.0 初始建立了 6 個工作日誌文件，違反「一個主版本一個日誌」原則。</p>
<h4 id="問題描述-1">問題描述</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">初始狀態:
</span></span><span class="line"><span class="ln">2</span><span class="cl">- v0.11.0-unified-api-implementation.md (主日誌)
</span></span><span class="line"><span class="ln">3</span><span class="cl">- v0.11.0-unified-api-flow-design.md (Phase 1)
</span></span><span class="line"><span class="ln">4</span><span class="cl">- v0.11.0-implementation-status-report.md (Phase 2.1)
</span></span><span class="line"><span class="ln">5</span><span class="cl">- v0.11.0-test-design-main-flow.md (Phase 2.2)
</span></span><span class="line"><span class="ln">6</span><span class="cl">- v0.11.0-test-design-api-fallback.md (Phase 2.3)
</span></span><span class="line"><span class="ln">7</span><span class="cl">- v0.11.0-test-design-rate-limiting.md (Phase 2.4)
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">問題: 6 個檔案都以 v0.11.0 開頭，不符合方法論規範</span></span></code></pre></div><h4 id="重組策略">重組策略</h4>
<p>參考 v0.9.0 成功模式，制定重組計畫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 重組計畫
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">### 新架構
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></span><span class="k">-</span> v0.11.0-main.md (主版本總覽)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> v0.11.1-tdd-phase1-design.md (Phase 1)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">-</span> v0.11.2-tdd-phase2-test-design.md (Phase 2 合併)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">-</span> v0.11.3-domain-input-validation.md (批次 1.1)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">-</span> v0.11.4-core-query-service.md (批次 1.2)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu">### 執行步驟
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="gu"></span><span class="k">1.</span> 重命名主日誌
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">2.</span> Phase 1 重命名為 v0.11.1
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">3.</span> Phase 2 四個文件合併為 v0.11.2
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">4.</span> 批次實作提取為 v0.11.3-4
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">5.</span> 清理舊檔案
</span></span><span class="line"><span class="ln">16</span><span class="cl">6. Git 提交重組結果</span></span></code></pre></div><h4 id="執行結果-3">執行結果</h4>
<ul>
<li>10 個檔案變更（929 行新增，5910 行刪除）</li>
<li>Git commit: d86940a</li>
<li>符合方法論規範</li>
<li>清晰的版本架構</li>
</ul>
<h4 id="學習要點-3">學習要點</h4>
<ol>
<li><strong>方法論合規性檢查</strong>：定期審查是否符合標準</li>
<li><strong>參考成功案例</strong>：v0.9.0 模式可複用</li>
<li><strong>大膽重組</strong>：發現問題立即修正，不將就</li>
<li><strong>文件瘦身</strong>：主版本日誌簡潔，詳細內容在子版本</li>
</ol>
<h3 id="方法論改進建議基於實戰">方法論改進建議（基於實戰）</h3>
<h4 id="1-強化-phase-2-重要性">1. 強化 Phase 2 重要性</h4>
<p><strong>發現</strong>：Phase 2 測試設計階段能提前識別 80% 的實作問題
<strong>建議</strong>：增加 Phase 2 的檢查清單和回報機制</p>
<h4 id="2-任務參考文件標準化已強制執行">2. 任務參考文件標準化（已強制執行）</h4>
<p><strong>發現</strong>：初始任務描述參考文件不足，導致代理人需要詢問
<strong>原建議</strong>：強制要求 UseCase、流程圖 Event、依賴類別完整填寫
<strong>已實施</strong>：</p>
<ul>
<li>更新「準備度檢查問題」加入第 5 項強制檢查</li>
<li>更新「任務執行前檢查」加入參考文件和影響範圍強制項目</li>
<li>違規處理機制：缺少任何一項視為任務規劃不合格</li>
<li>實戰驗證：v0.12.1 已補充完整參考文件和影響範圍章節</li>
</ul>
<h4 id="3-動態更新機制制度化">3. 動態更新機制制度化</h4>
<p><strong>發現</strong>：Phase 發現需要立即傳遞給後續任務
<strong>建議</strong>：建立「重要發現」區域，強制每個 Phase 完成後更新</p>
<h4 id="4-緊急任務插入流程">4. 緊急任務插入流程</h4>
<p><strong>發現</strong>：架構問題需要立即處理
<strong>建議</strong>：定義緊急任務命名規則（vX.Y.Z-fix）和執行流程</p>
<h4 id="5-工作日誌結構模板">5. 工作日誌結構模板</h4>
<p><strong>發現</strong>：初期結構混亂，後期重組成本高
<strong>建議</strong>：提供標準模板，從一開始就符合規範</p>
<h2 id="方法論版本歷史">方法論版本歷史</h2>
<h3 id="v13-2025-10-07">v1.3 (2025-10-07)</h3>
<ul>
<li><strong>新增</strong>: 三重文件協調原則</li>
</ul>
<h3 id="v12-2025-09-30">v1.2 (2025-09-30)</h3>
<ul>
<li><strong>新增</strong>: 任務拆分實戰範例（基於 v0.11.5 情境）</li>
<li><strong>新增</strong>: 動態文件更新機制（Phase 發現傳遞流程）</li>
<li><strong>新增</strong>: 代理人回報與討論流程（4 種回報類型）</li>
<li><strong>新增</strong>: 敏捷開發本質說明（資訊流動、靈活交接、響應式規劃）</li>
<li><strong>新增</strong>: v0.11.0 實戰案例集（4 個完整案例）</li>
<li><strong>更新</strong>: 任務參考文件格式（強制完整填寫要求）</li>
<li><strong>更新</strong>: 基於 v0.11 實戰經驗完善所有流程</li>
</ul>
<h3 id="v11-2025-09-29">v1.1 (2025-09-29)</h3>
<ul>
<li><strong>新增</strong>: 階段完成驗證機制 (基於實戰經驗學習)</li>
<li><strong>新增</strong>: 5 項強制檢查項目 (編譯、測試、路徑、重複、架構)</li>
<li><strong>新增</strong>: 階段失敗處理原則</li>
<li><strong>新增</strong>: Hook 系統整合指引</li>
<li><strong>更新</strong>: 成功標準，加入階段驗證指標</li>
<li><strong>修正</strong>: 移除版本特定內容，改為通用流程</li>
</ul>
<h3 id="v10-2025-09-27">v1.0 (2025-09-27)</h3>
<ul>
<li><strong>建立</strong>: 基礎敏捷重構方法論</li>
<li><strong>定義</strong>: Agent 分工協作模式</li>
<li><strong>建立</strong>: 任務分派和品質控制機制</li>
</ul>
<hr>
<p><strong>當前版本</strong>: v1.2
<strong>適用範圍</strong>: 通用敏捷重構開發流程
<strong>最後更新</strong>: 2025-09-30
<strong>責任人</strong>: Claude Code AI Assistant
<strong>重大更新</strong>: 基於 v0.11.0 實戰經驗，新增任務拆分範例、動態更新機制、代理人回報流程、敏捷開發本質說明</p>
]]></content:encoded></item><item><title>系統化除錯方法論</title><link>https://tarrragon.github.io/blog/record/%E7%B3%BB%E7%B5%B1%E5%8C%96%E9%99%A4%E9%8C%AF%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%B3%BB%E7%B5%B1%E5%8C%96%E9%99%A4%E9%8C%AF%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="為什麼需要系統化除錯方法論">為什麼需要系統化除錯方法論&lt;/h2>
&lt;p>除錯不是試錯過程，是品質提升的系統性工程。隨機修復會產生隨機品質。系統化除錯產生一致的架構改善。&lt;/p></description><content:encoded><![CDATA[<h2 id="為什麼需要系統化除錯方法論">為什麼需要系統化除錯方法論</h2>
<p>除錯不是試錯過程，是品質提升的系統性工程。隨機修復會產生隨機品質。系統化除錯產生一致的架構改善。</p>
<p>當AI協作處理複雜程式問題時，系統化除錯方法論成為唯一的執行準則。模糊的修復策略會產生模糊的結果。明確的除錯方法論產生一致的品質改善。</p>
<h2 id="系統化除錯的本質">系統化除錯的本質</h2>
<h3 id="系統化除錯不是什麼">系統化除錯不是什麼</h3>
<p>系統化除錯不是：</p>
<ul>
<li><strong>症狀修復</strong>：不掩蓋警告，只找根本原因</li>
<li><strong>批量處理</strong>：不自動修復，只精確分析</li>
<li><strong>簡單先行</strong>：不從容易修的開始，只按風險優先級</li>
<li><strong>表面清理</strong>：不只消除警告，只完成未完成的設計</li>
</ul>
<h3 id="系統化除錯是什麼">系統化除錯是什麼</h3>
<p>系統化除錯是：</p>
<ul>
<li><strong>根因分析</strong>：明確區分未完成實作vs過度設計</li>
<li><strong>風險導向</strong>：按業務風險和架構影響排序修復</li>
<li><strong>主從分工</strong>：主線程管控進度，代理人執行修復</li>
<li><strong>品質提升</strong>：每次修復都強化程式設計完整性</li>
</ul>
<h2 id="除錯的第一原則根因分析優先">除錯的第一原則：根因分析優先</h2>
<h3 id="問題本質分類">問題本質分類</h3>
<p>每個unused警告都屬於以下三類之一：</p>
<h4 id="未完成實作">未完成實作</h4>
<ul>
<li><strong>識別</strong>：功能設計完整但驗證邏輯缺失</li>
<li><strong>處理</strong>：補完實作而非移除程式碼</li>
<li><strong>範例</strong>：測試中建立了secondImport變數但未驗證重複匯入行為</li>
</ul>
<h4 id="過度設計">過度設計</h4>
<ul>
<li><strong>識別</strong>：功能已完成但包含不必要的複雜性</li>
<li><strong>處理</strong>：移除多餘程式碼保持精簡設計</li>
<li><strong>範例</strong>：建立獨立服務實例但架構採用單例模式</li>
</ul>
<h4 id="程式碼風格問題">程式碼風格問題</h4>
<ul>
<li><strong>識別</strong>：邏輯正確但命名或結構不一致</li>
<li><strong>處理</strong>：重構改善可讀性和一致性</li>
<li><strong>範例</strong>：使用File物件但混用path字串操作</li>
</ul>
<h3 id="分析判斷標準">分析判斷標準</h3>
<ul>
<li>變數有明確的業務意圖 → 未完成實作</li>
<li>變數創建後立即被丟棄 → 過度設計</li>
<li>變數使用方式不一致 → 程式碼風格問題</li>
</ul>
<p>不存在「可能是」的情況。如果無法明確分類，則需要更深入的程式碼分析。</p>
<h3 id="範例完整的根因分析">範例：完整的根因分析</h3>
<h4 id="情境test檔案中unused變數-initialmemory">情境：test檔案中unused變數 &lsquo;initialMemory&rsquo;</h4>
<h5 id="錯誤分析">錯誤分析</h5>
<p>「這個變數沒用到，直接刪掉。」</p>
<h5 id="正確分析過程">正確分析過程</h5>
<ol>
<li><strong>變數意圖</strong>：記憶體效率測試的基線測量</li>
<li><strong>使用模式</strong>：建立但未在驗證邏輯中引用</li>
<li><strong>分類判斷</strong>：未完成實作（測試設計完整但驗證缺失）</li>
<li><strong>修復策略</strong>：補完基線比較邏輯而非移除變數</li>
</ol>
<h2 id="除錯的第二原則風險導向排序">除錯的第二原則：風險導向排序</h2>
<h3 id="檔案風險等級">檔案風險等級</h3>
<p>檔案修復必須按風險等級執行：</p>
<h4 id="高風險檔案立即修復">高風險檔案（立即修復）</h4>
<ul>
<li><strong>核心業務邏輯</strong>：Domain層實作檔案</li>
<li><strong>基礎設施元件</strong>：Database、Service、Repository</li>
<li><strong>關鍵測試</strong>：端到端測試、整合測試</li>
</ul>
<h4 id="中風險檔案計畫修復">中風險檔案（計畫修復）</h4>
<ul>
<li><strong>輔助功能</strong>：Utility、Helper類別</li>
<li><strong>測試工具</strong>：Mock、TestData產生器</li>
<li><strong>配置檔案</strong>：Configuration、Environment設定</li>
</ul>
<h4 id="低風險檔案可延後修復">低風險檔案（可延後修復）</h4>
<ul>
<li><strong>單元測試變數</strong>：純測試輔助變數</li>
<li><strong>範例程式碼</strong>：Demo、Sample實作</li>
<li><strong>文件產生器</strong>：Documentation工具</li>
</ul>
<h3 id="風險評估標準">風險評估標準</h3>
<ul>
<li>影響核心功能 → 高風險</li>
<li>影響開發效率 → 中風險</li>
<li>純粹警告清理 → 低風險</li>
</ul>
<p>每個檔案只能歸類到一個風險等級。無法分類表示需要進一步的架構分析。</p>
<h3 id="修復優先序執行規則">修復優先序執行規則</h3>
<ul>
<li>高風險檔案：立即修復，不考慮複雜度</li>
<li>中風險檔案：當前Sprint完成</li>
<li>低風險檔案：下個版本或維護期處理</li>
</ul>
<h2 id="除錯的第三原則主從分工模式">除錯的第三原則：主從分工模式</h2>
<h3 id="角色定義">角色定義</h3>
<p>系統化除錯採用明確的角色分工：</p>
<h4 id="主線程職責">主線程職責</h4>
<ul>
<li><strong>進度管控</strong>：追蹤修復狀態和整體進展</li>
<li><strong>策略決策</strong>：確定修復優先序和資源配置</li>
<li><strong>品質檢查</strong>：驗證修復結果符合品質要求</li>
<li><strong>工作記錄</strong>：更新工作日誌避免遺漏</li>
</ul>
<h4 id="代理人職責">代理人職責</h4>
<ul>
<li><strong>詳細分析</strong>：深入檢查程式碼設計意圖</li>
<li><strong>修復執行</strong>：實際編寫和修改程式碼</li>
<li><strong>測試驗證</strong>：確保修復後功能正常</li>
<li><strong>技術回報</strong>：提供修復細節和影響評估</li>
</ul>
<h3 id="協作執行規則">協作執行規則</h3>
<ul>
<li>主線程永不直接修復程式碼</li>
<li>代理人永不決定修復優先序</li>
<li>每修復一個檔案都必須更新工作日誌</li>
<li>所有修復決策都必須通過主線程確認</li>
</ul>
<h3 id="範例完整的協作流程">範例：完整的協作流程</h3>
<h4 id="情境發現5個檔案有unused警告">情境：發現5個檔案有unused警告</h4>
<h5 id="主線程執行">主線程執行</h5>
<ol>
<li><strong>風險評估</strong>：將5個檔案按業務風險分類</li>
<li><strong>優先排序</strong>：確定高風險檔案的修復順序</li>
<li><strong>委託分析</strong>：要求代理人分析第一個檔案</li>
<li><strong>進度追蹤</strong>：更新TodoList標記當前處理檔案</li>
</ol>
<h5 id="代理人執行">代理人執行</h5>
<ol>
<li><strong>根因分析</strong>：判斷unused變數屬於未完成實作vs過度設計</li>
<li><strong>修復實施</strong>：根據分析結果執行對應的修復策略</li>
<li><strong>結果驗證</strong>：執行靜態分析工具確認警告消除</li>
<li><strong>影響報告</strong>：回報修復內容和對整體架構的影響</li>
</ol>
<h5 id="主線程確認">主線程確認</h5>
<ol>
<li><strong>驗證結果</strong>：檢查靜態分析工具輸出確認修復成功</li>
<li><strong>更新記錄</strong>：在工作日誌中記錄修復成果</li>
<li><strong>繼續協作</strong>：標記完成並委託下一個檔案分析</li>
</ol>
<h2 id="品質標準">品質標準</h2>
<h3 id="修復完成的判斷標準">修復完成的判斷標準</h3>
<p>每個檔案修復完成必須滿足：</p>
<ul>
<li><strong>警告消除</strong>：靜態分析工具不再顯示該檔案的unused警告</li>
<li><strong>功能完整</strong>：所有測試通過，不引入新的錯誤</li>
<li><strong>架構一致</strong>：修復符合Clean Architecture分層原則</li>
<li><strong>文件更新</strong>：工作日誌記錄修復內容和影響</li>
</ul>
<h3 id="整體品質提升指標">整體品質提升指標</h3>
<ul>
<li><strong>警告減少率</strong>：unused警告數量持續下降</li>
<li><strong>功能完整性</strong>：修復過程中完成更多未完成的實作</li>
<li><strong>架構一致性</strong>：程式碼風格和設計模式更加統一</li>
<li><strong>可維護性</strong>：程式碼可讀性和邏輯清晰度提升</li>
</ul>
<h3 id="品質驗證機制">品質驗證機制</h3>
<ul>
<li>每個檔案修復後立即執行靜態分析工具驗證</li>
<li>定期檢查整體警告數量變化趨勢</li>
<li>記錄修復過程中發現的架構改善機會</li>
<li>確認每次修復都強化而非弱化程式品質</li>
</ul>
<h2 id="執行流程">執行流程</h2>
<h3 id="標準修復流程">標準修復流程</h3>
<ol>
<li>
<p><strong>問題評估</strong>
執行靜態分析工具識別所有unused警告</p>
</li>
<li>
<p><strong>風險分析</strong>
將含有警告的檔案按風險等級分類</p>
</li>
<li>
<p><strong>優先排序</strong>
確定高風險檔案的修復順序</p>
</li>
<li>
<p><strong>逐檔修復</strong>
按優先序對每個檔案執行：</p>
<ul>
<li>委託代理人詳細分析</li>
<li>根因判斷(未完成實作vs過度設計vs程式碼風格)</li>
<li>執行對應修復策略</li>
<li>驗證修復結果</li>
<li>更新工作記錄</li>
</ul>
</li>
<li>
<p><strong>整體驗證</strong>
確認警告總數下降且無新錯誤引入</p>
</li>
</ol>
<h3 id="修復策略對應表">修復策略對應表</h3>
<table>
  <thead>
      <tr>
          <th>根因類型</th>
          <th>修復策略</th>
          <th>驗證標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>未完成實作</td>
          <td>補完功能實作</td>
          <td>變數在邏輯中被正確使用</td>
      </tr>
      <tr>
          <td>過度設計</td>
          <td>移除多餘程式碼</td>
          <td>功能完整但程式碼更簡潔</td>
      </tr>
      <tr>
          <td>程式碼風格</td>
          <td>重構改善一致性</td>
          <td>邏輯不變但可讀性提升</td>
      </tr>
  </tbody>
</table>
<h3 id="例外處理原則">例外處理原則</h3>
<ul>
<li><strong>分析器誤報</strong>：確認變數確實被使用後保持現狀</li>
<li><strong>架構衝突</strong>：優先解決架構問題後再處理警告</li>
<li><strong>測試失敗</strong>：立即修復測試問題，暫停警告修復</li>
<li><strong>複雜邊界</strong>：分解為更小的問題單位處理</li>
</ul>
<h2 id="成果評估">成果評估</h2>
<h3 id="量化指標">量化指標</h3>
<ul>
<li><strong>警告消除數量</strong>：已修復的unused警告總數</li>
<li><strong>警告減少率</strong>：相對於初始狀態的改善百分比</li>
<li><strong>檔案修復數量</strong>：完成修復的檔案總數</li>
<li><strong>架構改善項目</strong>：修復過程中完成的設計改善</li>
</ul>
<h3 id="質化評估">質化評估</h3>
<ul>
<li><strong>根因解決率</strong>：真正解決問題vs僅消除警告的比例</li>
<li><strong>架構一致性</strong>：程式碼風格和設計模式統一程度</li>
<li><strong>功能完整性</strong>：修復過程中完成的未完成實作數量</li>
<li><strong>可維護性提升</strong>：程式碼清晰度和邏輯簡潔性改善</li>
</ul>
<h3 id="實戰案例v0819成果">實戰案例：v0.8.19成果</h3>
<p><strong>量化成果</strong>：</p>
<ul>
<li>初始警告：49個</li>
<li>最終警告：25個</li>
<li>改善率：49.0%</li>
<li>修復檔案：7個高風險檔案</li>
</ul>
<p><strong>質化成果</strong>：</p>
<ul>
<li>補完3個未完成的功能實作</li>
<li>移除4處過度設計的複雜程式碼</li>
<li>統一5個檔案的程式碼風格</li>
<li>解決2個架構不一致問題</li>
</ul>
<h2 id="持續改進">持續改進</h2>
<h3 id="方法論優化">方法論優化</h3>
<p>系統化除錯方法論必須持續優化：</p>
<ul>
<li><strong>記錄邊界案例</strong>：遇到的特殊情況和處理方式</li>
<li><strong>更新風險分類</strong>：基於實戰經驗調整風險評估標準</li>
<li><strong>改進協作模式</strong>：優化主線程和代理人的分工效率</li>
<li><strong>補充修復策略</strong>：新增針對特定問題類型的處理方法</li>
</ul>
<h3 id="知識累積">知識累積</h3>
<p>每次系統化除錯的經驗都必須沉澱為方法論改進：</p>
<ul>
<li>成功的修復策略納入標準流程</li>
<li>失效的方法從規範中移除</li>
<li>新發現的問題模式補充到分類標準</li>
<li>協作過程中的效率改善點持續優化</li>
</ul>
<h2 id="結論">結論</h2>
<p>系統化除錯方法論是品質提升的執行標準。它的價值在精確，它的目的是完成設計。</p>
<p>每個修復都是一次架構改善。每個分析都是一次設計檢視。每個協作都是一次品質提升。</p>
<p>執行系統化除錯就是執行品質標準。遵循這個方法論，我們能持續強化程式架構完整性和設計一致性。</p>
<p>這是工程規範，確保每次除錯都提升而非妥協專案品質。</p>
<h2 id="延伸套用到-linux-系統除錯">延伸：套用到 Linux 系統除錯</h2>
<p>這套方法論是語言與領域無關的通則。把它落到 Linux 系統除錯這個具體領域——「讀權威狀態而非肉眼猜表象」的紀律、症狀到情境的分流、逐層定位——見 <a href="/blog/linux/debug/diagnosis-read-authoritative-state/" data-link-title="診斷心法：讀權威狀態，不靠肉眼猜表象" data-link-desc="Linux 上一個現象看起來像 A 卻可能是 B、想建立一套先讀權威狀態再下判斷的除錯紀律、避免看畫面就猜而猜錯時回來讀">Linux 除錯與診斷：診斷心法</a>。那裡用實機案例（把鎖屏誤判兩次的教訓）展示同一套系統化紀律在 Linux 現場長什麼樣。</p>]]></content:encoded></item><item><title>Package 導入路徑語意化方法論</title><link>https://tarrragon.github.io/blog/record/package-%E5%B0%8E%E5%85%A5%E8%B7%AF%E5%BE%91%E8%AA%9E%E6%84%8F%E5%8C%96%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Sun, 21 Sep 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/package-%E5%B0%8E%E5%85%A5%E8%B7%AF%E5%BE%91%E8%AA%9E%E6%84%8F%E5%8C%96%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="為什麼導入聲明很重要">為什麼導入聲明很重要&lt;/h2>
&lt;p>在程式開發中，導入聲明往往被視為技術細節，但它們實際上是&lt;strong>架構文件的第一行&lt;/strong>。每個 import/require 都在告訴讀者：這個模組的依賴關係、系統的組織方式、以及設計者的架構思考。&lt;/p></description><content:encoded><![CDATA[<h2 id="為什麼導入聲明很重要">為什麼導入聲明很重要</h2>
<p>在程式開發中，導入聲明往往被視為技術細節，但它們實際上是<strong>架構文件的第一行</strong>。每個 import/require 都在告訴讀者：這個模組的依賴關係、系統的組織方式、以及設計者的架構思考。</p>
<p>相對路徑如 <code>import '../../../utils/helper.js'</code> 只是路徑的機械表達，而語意化路徑如 <code>import 'package:app/core/utils/helper.dart'</code> 則清楚傳達了模組的架構位置和責任。</p>
<h2 id="導入聲明的本質">導入聲明的本質</h2>
<h3 id="導入不是什麼">導入不是什麼</h3>
<p>導入聲明不是：</p>
<ul>
<li><strong>文件路徑的機械化表達</strong>：不是為了節省字元數而設計</li>
<li><strong>相對位置的簡化表示</strong>：不是為了避免長路徑而妥協</li>
<li><strong>開發便利性的工具</strong>：不是為了快速輸入而犧牲可讀性</li>
<li><strong>IDE 自動生成的結果</strong>：不是讓工具決定程式碼結構</li>
</ul>
<h3 id="導入是什麼">導入是什麼</h3>
<p>導入聲明是：</p>
<ul>
<li><strong>依賴關係的明確宣告</strong>：清楚表達模組間的連接</li>
<li><strong>程式碼架構的文件化</strong>：展示系統的組織結構</li>
<li><strong>依賴來源的即時說明</strong>：讓讀者立即理解依賴的性質</li>
<li><strong>架構意圖的表達方式</strong>：體現設計者對模組劃分的思考</li>
</ul>
<h2 id="核心原則">核心原則</h2>
<h3 id="第一原則導入路徑的架構語意性">第一原則：導入路徑的架構語意性</h3>
<p>每個導入都必須清楚表達來源的架構位置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 正例：清楚表達架構層級
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/core/errors/standard_error.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 反例：隱藏架構關係
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;../entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;../../../core/errors/standard_error.dart&#39;</span><span class="p">;</span></span></span></code></pre></div><h3 id="第二原則依賴來源的即時識別">第二原則：依賴來源的即時識別</h3>
<p>從導入聲明立即理解依賴性質：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 從導入立即理解：這是 Library Domain 的核心實體
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">Book</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@app/domains/library/entities/book&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 從導入立即理解：這是 Core 基礎設施
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">StandardError</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@app/core/errors/standard-error&#39;</span><span class="p">;</span></span></span></code></pre></div><h3 id="第三原則禁用別名與妥協">第三原則：禁用別名與妥協</h3>
<p><strong>別名是程式設計不佳的象徵</strong>。當我們遇到重名衝突時，核心解決方案是重構和改善命名，而不是用別名掩蓋設計問題。</p>
<h4 id="為什麼禁用別名">為什麼禁用別名</h4>
<p>別名反映的根本問題：</p>
<ol>
<li><strong>命名不夠清晰明確</strong>：導致不同模組產生重名衝突</li>
<li><strong>架構設計缺陷</strong>：同一概念在不同領域使用相同名稱</li>
<li><strong>職責劃分不清</strong>：模組邊界和責任沒有明確定義</li>
<li><strong>技術債務累積</strong>：用別名掩蓋設計問題而非解決問題</li>
</ol>
<h4 id="錯誤的別名解決方案">錯誤的別名解決方案</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 反例：使用別名掩蓋設計問題
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:app/domains/library/entities/book.dart&#39;</span> <span class="k">as</span> <span class="n">LibBook</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:app/domains/search/entities/book.dart&#39;</span> <span class="k">as</span> <span class="n">SearchBook</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 使用時仍然模糊不清
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">LibBook</span><span class="p">.</span><span class="n">Book</span> <span class="n">bookEntity</span> <span class="o">=</span> <span class="n">LibBook</span><span class="p">.</span><span class="n">Book</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">SearchBook</span><span class="p">.</span><span class="n">Book</span> <span class="n">searchResult</span> <span class="o">=</span> <span class="n">SearchBook</span><span class="p">.</span><span class="n">Book</span><span class="p">();</span></span></span></code></pre></div><h4 id="正確的重構解決方案">正確的重構解決方案</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 正例：重構命名，消除重名衝突
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:app/domains/search/entities/search_result.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 使用時語意清楚，職責明確
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">Book</span> <span class="n">libraryBook</span> <span class="o">=</span> <span class="n">Book</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">SearchResult</span> <span class="n">searchData</span> <span class="o">=</span> <span class="n">SearchResult</span><span class="p">();</span></span></span></code></pre></div><h4 id="重構策略">重構策略</h4>
<p><strong>1. 重新審視命名責任</strong>：</p>
<ul>
<li>保留核心領域的概念名稱（如 Library Domain 的 Book）</li>
<li>重構其他領域的名稱為更精確的描述（如 Search Domain 的 SearchResult）</li>
</ul>
<p><strong>2. 領域邊界清晰化</strong>：</p>
<ul>
<li>根據職責重新命名類別和模組</li>
<li>確保每個名稱都有明確的領域歸屬</li>
</ul>
<p><strong>3. 架構重構優於別名妥協</strong>：</p>
<ul>
<li>禁用別名迫使開發者正視設計缺陷</li>
<li>推動更清晰的領域劃分</li>
<li>維護程式碼品質標準</li>
</ul>
<h2 id="跨語言實踐標準">跨語言實踐標準</h2>
<h3 id="dartflutter-package-系統">Dart/Flutter: Package 系統</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Package 導入 + 完整路徑語意
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/search/services/api_service.dart&#39;</span><span class="p">;</span></span></span></code></pre></div><p><strong>實現機制</strong>：<code>pubspec.yaml</code> 定義 package 名稱，Dart 編譯器將 <code>package:</code> 映射到 <code>lib/</code> 目錄。</p>
<h3 id="nodejs-混合策略-v1-專案實踐">Node.js: 混合策略 (V1 專案實踐)</h3>
<p><strong>生產環境</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 嚴格的目錄規範 + 明確的相對路徑
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">BaseModule</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;./lifecycle/base-module&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">PageDomainCoordinator</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;./domains/page/page-domain-coordinator&#39;</span><span class="p">);</span></span></span></code></pre></div><p><strong>測試環境</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Jest moduleNameMapper 實現語意化
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="p">{</span> <span class="nx">ErrorCodes</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;src/core/errors/ErrorCodes&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">QualityAssessmentService</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;src/background/domains/data-management/services/quality-assessment-service.js&#39;</span><span class="p">);</span></span></span></code></pre></div><p><strong>Jest 配置關鍵</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// tests/jest.config.js
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">moduleNameMapper</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="s1">&#39;^src/(.*)$&#39;</span><span class="o">:</span> <span class="s1">&#39;&lt;rootDir&gt;/src/$1&#39;</span><span class="p">,</span>           <span class="c1">// src/ 路徑語意化
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>    <span class="s1">&#39;^@/(.*)$&#39;</span><span class="o">:</span> <span class="s1">&#39;&lt;rootDir&gt;/src/$1&#39;</span><span class="p">,</span>             <span class="c1">// @ 別名映射
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span>    <span class="s1">&#39;^@tests/(.*)$&#39;</span><span class="o">:</span> <span class="s1">&#39;&lt;rootDir&gt;/tests/$1&#39;</span><span class="p">,</span>      <span class="c1">// 測試檔案語意化
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span>    <span class="s1">&#39;^@mocks/(.*)$&#39;</span><span class="o">:</span> <span class="s1">&#39;&lt;rootDir&gt;/tests/mocks/$1&#39;</span> <span class="c1">// Mock 檔案語意化
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="php-laravel-框架--composer">PHP Laravel: 框架 + Composer</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// Laravel 的命名空間 + Composer Autoloader
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="k">namespace</span> <span class="nx">App\Domains\Library\Entities</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">use</span> <span class="nx">App\Domains\Search\Services\ApiService</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">use</span> <span class="nx">App\Core\Errors\StandardError</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Model</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">Book</span> <span class="k">extends</span> <span class="nx">Model</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1">// 完整命名空間讓讀者立即理解依賴來源
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><h3 id="go-module-system">Go: Module System</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1">// 外部依賴使用 module path</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s">&#34;gorm.io/gorm&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">// 內部模組使用完整 module path</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s">&#34;book-overview-app/domains/library/entities&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s">&#34;book-overview-app/domains/search/services&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s">&#34;book-overview-app/core/errors&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="typescript-module-resolution">TypeScript: Module Resolution</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Module 導入 + 完整路徑語意
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">Book</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@app/domains/library/entities/book&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ApiService</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@app/domains/search/services/api-service&#39;</span><span class="p">;</span></span></span></code></pre></div><h2 id="v1-專案無框架的成功實踐">V1 專案：無框架的成功實踐</h2>
<h3 id="為什麼-v1-可以不需要絕對路徑">為什麼 V1 可以不需要絕對路徑</h3>
<ol>
<li><strong>一致的目錄結構</strong>：所有模組都在 <code>/src/background/</code> 下，層級關係固定</li>
<li><strong>明確的相對路徑語意</strong>：<code>./</code> = 同級，<code>../</code> = 上一級，路徑語意清楚</li>
<li><strong>避免深層嵌套</strong>：最多 3-4 層目錄，相對路徑仍然可讀</li>
<li><strong>Jest 測試環境的語意化支援</strong>：透過配置實現測試檔案的語意化路徑</li>
</ol>
<h3 id="npm-test-vs-jest-直接執行的選擇">npm test vs Jest 直接執行的選擇</h3>
<p>V1 專案選擇 <strong>npm test</strong> 的原因：</p>
<ol>
<li><strong>一致性管理</strong>：統一的測試入口</li>
<li><strong>環境隔離</strong>：確保所有開發者使用相同配置</li>
<li><strong>工具鏈整合</strong>：可以執行測試前後的額外工作</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// package.json - 統一的測試入口
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;npm run test:core&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;test:core&#34;</span><span class="p">:</span> <span class="s2">&#34;jest tests/unit tests/integration&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nt">&#34;test:unit&#34;</span><span class="p">:</span> <span class="s2">&#34;jest tests/unit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nt">&#34;test:integration&#34;</span><span class="p">:</span> <span class="s2">&#34;jest tests/integration&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="測試路徑語意化的實現機制">測試路徑語意化的實現機制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Jest 如何解析語意化路徑：
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 1. 測試檔案寫入: require(&#39;src/core/errors/ErrorCodes&#39;)
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 2. Jest moduleNameMapper 攔截: &#39;^src/(.*)$&#39;: &#39;&lt;rootDir&gt;/src/$1&#39;
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 3. 實際解析路徑: /project-root/src/core/errors/ErrorCodes.js
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 4. Node.js 載入模組: 成功匯入 ErrorCodes
</span></span></span></code></pre></div><h2 id="語言特性對比">語言特性對比</h2>
<table>
  <thead>
      <tr>
          <th>語言</th>
          <th>實現機制</th>
          <th>優勢</th>
          <th>測試環境支援</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Dart</strong></td>
          <td>Package system</td>
          <td>編譯時解析，IDE 支援佳</td>
          <td>原生支援 package: 導入</td>
      </tr>
      <tr>
          <td><strong>Go</strong></td>
          <td>Module system</td>
          <td>強制語意化，無相對路徑</td>
          <td>測試檔案使用相同 module path</td>
      </tr>
      <tr>
          <td><strong>PHP Laravel</strong></td>
          <td>框架 + Composer</td>
          <td>自動載入，標準化目錄</td>
          <td>PHPUnit 自動載入命名空間</td>
      </tr>
      <tr>
          <td><strong>TypeScript</strong></td>
          <td>Module resolution</td>
          <td>彈性配置，工具支援</td>
          <td>Jest/Vitest 支援 path mapping</td>
      </tr>
      <tr>
          <td><strong>Node.js (V1)</strong></td>
          <td>相對路徑 + Jest 映射</td>
          <td>生產簡單，測試語意化</td>
          <td><strong>Jest moduleNameMapper 實現語意化</strong></td>
      </tr>
      <tr>
          <td><strong>Python</strong></td>
          <td>Package imports</td>
          <td>簡潔語法，標準化</td>
          <td>pytest 原生支援 package 導入</td>
      </tr>
  </tbody>
</table>
<h2 id="實踐選擇指南">實踐選擇指南</h2>
<h3 id="有框架的專案">有框架的專案</h3>
<ul>
<li>使用框架提供的模組系統</li>
<li>例如：Laravel、Angular、Next.js</li>
<li>測試環境通常自動繼承框架的模組解析</li>
</ul>
<h3 id="無框架的專案-如-v1-範例">無框架的專案 (如 V1 範例)</h3>
<ul>
<li><strong>生產環境</strong>：採用嚴格的目錄規範 + 相對路徑</li>
<li><strong>測試環境</strong>：使用 Jest moduleNameMapper 實現語意化</li>
<li><strong>關鍵優勢</strong>：生產簡單，測試語意化</li>
</ul>
<h3 id="混合策略專案">混合策略專案</h3>
<ul>
<li>生產環境使用簡單的相對路徑</li>
<li>測試環境透過工具配置實現語意化</li>
<li>適合輕量級 Node.js 專案</li>
</ul>
<h2 id="架構透明性的價值">架構透明性的價值</h2>
<h3 id="從導入理解系統設計">從導入理解系統設計</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 從這個檔案的導入可以立即理解：
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// 1. 這是一個跨 Domain 的協調器
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// 2. 主要整合 Library、Import、Search 三個領域
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 3. 使用 Core 的標準錯誤處理
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 4. 依賴外部的 HTTP 套件
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:http/http.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/core/errors/standard_error.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/services/library_service.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/import/services/import_service.dart&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/search/services/search_service.dart&#39;</span><span class="p">;</span></span></span></code></pre></div><h3 id="依賴方向的視覺化">依賴方向的視覺化</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 讀者可以立即看出依賴方向：
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// UI → Domain → Core
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 沒有反向依賴，符合乾淨架構原則
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/core/interfaces/repository.dart&#39;</span><span class="p">;</span>           <span class="c1">// 向下依賴
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/entities/book.dart&#39;</span><span class="p">;</span>       <span class="c1">// 平行依賴
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="k">import</span> <span class="s1">&#39;package:book_overview_app/domains/library/value_objects/book_id.dart&#39;</span><span class="p">;</span> <span class="o">//</span> <span class="err">向下依賴</span></span></span></code></pre></div><h2 id="總結">總結</h2>
<p>Package 導入路徑語意化方法論的核心價值：</p>
<ol>
<li><strong>架構透明性</strong>：從導入立即理解系統結構</li>
<li><strong>維護便利性</strong>：減少理解和修改的認知負擔</li>
<li><strong>團隊協作</strong>：統一的導入風格提升溝通效率</li>
<li><strong>跨語言一致性</strong>：建立統一的程式碼組織哲學</li>
</ol>
<p>遵循這個方法論，程式碼將成為自說明的架構文件，每個導入聲明都清楚表達系統的設計意圖和模組關係。無論是有框架還是無框架的專案，都能找到適合的語意化導入策略。</p>
<p>V1 專案證明了即使在無框架環境下，透過嚴格的目錄規範和 Jest 測試配置，仍然可以實現生產環境簡潔、測試環境語意化的理想狀態。這為其他類似專案提供了寶貴的實踐參考。</p>
<h2 id="結論">結論</h2>
<p>這是架構透明化機制，讓每個導入聲明都成為架構的即時文件。</p>]]></content:encoded></item><item><title>AI 任務逃避偵測與預防三層防護方法論</title><link>https://tarrragon.github.io/blog/record/ai-%E4%BB%BB%E5%8B%99%E9%80%83%E9%81%BF%E5%81%B5%E6%B8%AC%E8%88%87%E9%A0%90%E9%98%B2%E4%B8%89%E5%B1%A4%E9%98%B2%E8%AD%B7%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Sat, 20 Sep 2025 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/ai-%E4%BB%BB%E5%8B%99%E9%80%83%E9%81%BF%E5%81%B5%E6%B8%AC%E8%88%87%E9%A0%90%E9%98%B2%E4%B8%89%E5%B1%A4%E9%98%B2%E8%AD%B7%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;h2 id="背景與演進">背景與演進&lt;/h2>
&lt;p>在與 LLM AI 協作過程中，我們發現了一個關鍵問題：AI 面對複雜問題會產生逃避行為，包括簡化問題、延後處理、或使用臨時解決方案。&lt;/p></description><content:encoded><![CDATA[<h2 id="背景與演進">背景與演進</h2>
<p>在與 LLM AI 協作過程中，我們發現了一個關鍵問題：AI 面對複雜問題會產生逃避行為，包括簡化問題、延後處理、或使用臨時解決方案。</p>
<p>為了徹底解決這個問題，我們發展了<strong>三層防護機制</strong>：</p>
<ol>
<li><strong>第一層：AI 內建自檢</strong> - 在每次回應前強制執行檢查，從源頭預防逃避行為</li>
<li><strong>第二層：Hook 系統驗證</strong> - 透過自動化腳本進行事後驗證和持續監控</li>
<li><strong>第三層：修復模式</strong> - 發現問題時提供補救措施和修正指引</li>
</ol>
<p>這套方法論不只確保 AI 協作品質，也成為我們所有開發決策的指導原則。</p>
<h2 id="三層防護機制架構">三層防護機制架構</h2>
<h3 id="第一層ai-內建自檢機制">第一層：AI 內建自檢機制</h3>
<p>這是最重要的防線，AI 在生成任何回應前都必須執行以下強制檢查：</p>
<h4 id="30秒核心檢查清單">30秒核心檢查清單</h4>
<ol>
<li>
<p>三大鐵律檢查</p>
<ul>
<li>測試通過率鐵律：是否包含「測試失敗可接受」的思維？</li>
<li>永不放棄鐵律：是否想跳過、暫時處理、或延後任何問題？</li>
<li>架構債務零容忍鐵律：是否發現架構問題卻想稍後處理？</li>
</ul>
</li>
<li>
<p>逃避詞彙檢查</p>
<ul>
<li>中文禁用詞：「太複雜」「先將就」「暫時性修正」「症狀緩解」等</li>
<li>英文禁用詞：workaround, bypass, hack, quick fix 等</li>
<li>簡化妥協詞：「更簡單的方法」「簡化處理」「簡化測試」等</li>
</ul>
</li>
<li>
<p>品質標準檢查</p>
<ul>
<li>解決方案是完整的，還是妥協的？</li>
<li>是否在迴避根本問題？</li>
<li>回應是否體現專業工程師標準？</li>
</ul>
</li>
</ol>
<h4 id="檢查失敗處理">檢查失敗處理</h4>
<p>如果任何檢查項目失敗：</p>
<ol>
<li>立即停止當前回應生成</li>
<li>重新分析問題和解決方案</li>
<li>重新構思符合三大鐵律的回應</li>
<li>再次檢查直到通過所有項目</li>
</ol>
<h3 id="第二層hook-系統驗證">第二層：Hook 系統驗證</h3>
<p>自動化腳本在關鍵時刻執行檢查：</p>
<h4 id="觸發時機">觸發時機</h4>
<ul>
<li>
<p><strong>UserPromptSubmit</strong>: 每次用戶輸入時檢查工作流程合規性</p>
</li>
<li>
<p><strong>PostToolUse</strong>: 檔案編輯後檢查程式異味和品質</p>
</li>
<li>
<p><strong>PreToolUse</strong>: 工具使用前檢查是否有阻止狀態</p>
</li>
<li>
<p><strong>Stop</strong>: 回應完成後分析版本推進狀態</p>
</li>
</ul>
<h4 id="檢查項目">檢查項目</h4>
<ul>
<li>ESLint 錯誤偵測和追蹤</li>
<li>技術債務累積監控</li>
<li>任務逃避行為偵測</li>
<li>程式異味自動分析</li>
<li>效能指標監控</li>
</ul>
<h3 id="第三層修復模式">第三層：修復模式</h3>
<p>當前兩層發現問題時的補救機制：</p>
<h4 id="進入條件">進入條件</h4>
<ul>
<li>AI 自檢發現違規詞彙或思維</li>
<li>Hook 系統偵測到逃避行為</li>
<li>品質指標超過容忍閾值</li>
</ul>
<h4 id="修復流程">修復流程</h4>
<ol>
<li>停止所有開發活動</li>
<li>分析問題根因和影響範圍</li>
<li>制定具體的修正計劃</li>
<li>執行修正並驗證結果</li>
<li>記錄學習並更新防護機制</li>
</ol>
<h4 id="完成標準">完成標準</h4>
<ul>
<li>所有違規行為已修正</li>
<li>品質指標回到可接受範圍</li>
<li>問題根因已徹底解決</li>
<li>預防措施已建立</li>
</ul>
<h2 id="核心立場">核心立場</h2>
<h3 id="我們接受的計劃性延後">我們接受的：計劃性延後</h3>
<p>計劃性延後是正當的開發策略。我們接受並鼓勵以下形式的延後：</p>
<h4 id="版本規劃的延後">版本規劃的延後</h4>
<ul>
<li>v0.1 做基礎功能，v0.2 加進階功能 → <strong>正確</strong></li>
<li>明確標註「此功能於 v0.3 實作」→ <strong>正確</strong></li>
<li>每個版本有完整可用的交付物 → <strong>必要</strong></li>
</ul>
<h4 id="tdd-最小實現">TDD 最小實現</h4>
<ul>
<li>先通過測試，再優化效能 → <strong>正確</strong></li>
<li>實作最小功能，重構階段再改善 → <strong>正確</strong></li>
<li>為擴展預留介面，但不過度設計 → <strong>正確</strong></li>
</ul>
<h4 id="風險管理的優先級">風險管理的優先級</h4>
<ul>
<li>核心流程優先，邊緣案例延後 → <strong>合理</strong></li>
<li>安全問題優先，美觀問題延後 → <strong>必要</strong></li>
<li>已知範圍的問題，計劃性處理 → <strong>專業</strong></li>
</ul>
<h3 id="我們拒絕的逃避行為">我們拒絕的：逃避行為</h3>
<p>以下行為是逃避，必須立即糾正：</p>
<h4 id="模糊的拖延">模糊的拖延</h4>
<ul>
<li>「太複雜，之後再說」→ <strong>錯誤</strong></li>
<li>「暫時跳過」但無具體計劃 → <strong>錯誤</strong></li>
<li>「先將就用」沒有改善時程 → <strong>錯誤</strong></li>
</ul>
<h4 id="核心責任的迴避">核心責任的迴避</h4>
<ul>
<li>發現架構問題但繞過 → <strong>不可接受</strong></li>
<li>省略錯誤處理 → <strong>不可接受</strong></li>
<li>降低測試標準來通過 → <strong>不可接受</strong></li>
</ul>
<h4 id="債務的累積">債務的累積</h4>
<ul>
<li>TODO 沒有追蹤和時程 → <strong>失職</strong></li>
<li>臨時方案變永久 → <strong>技術腐敗</strong></li>
<li>問題擴散不處理 → <strong>專案危機</strong></li>
</ul>
<h3 id="完整違規詞彙對照表">完整違規詞彙對照表</h3>
<p>為了確保一致性執行，以下是完整的違規詞彙清單：</p>
<h4 id="品質妥協和逃避責任類">品質妥協和逃避責任類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「太複雜」</td>
          <td>&ldquo;too complex&rdquo;, &ldquo;too complicated&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「需要深度分析，讓我分解問題」</td>
      </tr>
      <tr>
          <td>「先將就」</td>
          <td>&ldquo;workaround&rdquo;, &ldquo;hack&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「建立完整解決方案」</td>
      </tr>
      <tr>
          <td>「暫時性修正」</td>
          <td>&ldquo;temporary fix&rdquo;, &ldquo;quick fix&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「現在就正確實作」</td>
      </tr>
      <tr>
          <td>「症狀緩解」</td>
          <td>&ldquo;bypass&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「解決根本問題」</td>
      </tr>
      <tr>
          <td>「先這樣處理」</td>
          <td>&ldquo;ignore for now&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「立即正確處理」</td>
      </tr>
      <tr>
          <td>「臨時解決方案」</td>
          <td>&ldquo;will fix later&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「永久性解決方案」</td>
      </tr>
  </tbody>
</table>
<h4 id="簡化妥協類">簡化妥協類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「更簡單的方法」</td>
          <td>&ldquo;simpler approach&rdquo;, &ldquo;simpler way&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「正確且完整的方法」</td>
      </tr>
      <tr>
          <td>「採用更簡單的方法」</td>
          <td>&ldquo;take the simpler approach&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「採用正確的方法」</td>
      </tr>
      <tr>
          <td>「簡化處理」</td>
          <td>&ldquo;simplify&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「完整處理」</td>
      </tr>
      <tr>
          <td>「簡單的處理方式」</td>
          <td>&ldquo;simpler method&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「專業的處理方式」</td>
      </tr>
  </tbody>
</table>
<h4 id="測試品質妥協類">測試品質妥協類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「簡化測試」</td>
          <td>&ldquo;simplify test&rdquo;, &ldquo;simplified test&rdquo;</td>
          <td>測試通過率鐵律</td>
          <td>「建立完整測試」</td>
      </tr>
      <tr>
          <td>「降低測試標準」</td>
          <td>&ldquo;lower test standard&rdquo;</td>
          <td>測試通過率鐵律</td>
          <td>「維持100%測試標準」</td>
      </tr>
      <tr>
          <td>「簡單測試就好」</td>
          <td>&ldquo;basic test only&rdquo;, &ldquo;simple test case&rdquo;</td>
          <td>測試通過率鐵律</td>
          <td>「全面測試覆蓋」</td>
      </tr>
      <tr>
          <td>「基本測試即可」</td>
          <td>&ldquo;minimal test&rdquo;</td>
          <td>測試通過率鐵律</td>
          <td>「完整測試套件」</td>
      </tr>
  </tbody>
</table>
<h4 id="發現問題但不解決類">發現問題但不解決類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「發現問題但不處理」</td>
          <td>&ldquo;ignore the issue&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「發現問題立即解決」</td>
      </tr>
      <tr>
          <td>「架構問題先不管」</td>
          <td>&ldquo;architecture debt later&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「架構問題立即修正」</td>
      </tr>
      <tr>
          <td>「只加個 TODO」</td>
          <td>&ldquo;just add todo&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「立即實作解決方案」</td>
      </tr>
      <tr>
          <td>「問題太多先跳過」</td>
          <td>&ldquo;too many issues skip&rdquo;</td>
          <td>永不放棄鐵律</td>
          <td>「逐一解決所有問題」</td>
      </tr>
  </tbody>
</table>
<h4 id="程式碼修改逃避類">程式碼修改逃避類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「註解掉」</td>
          <td>&ldquo;comment out&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「重構或移除」</td>
      </tr>
      <tr>
          <td>「停用功能」</td>
          <td>&ldquo;disable&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「修正後啟用」</td>
      </tr>
      <tr>
          <td>「暫時關閉」</td>
          <td>&ldquo;temporarily disable&rdquo;</td>
          <td>架構債務零容忍</td>
          <td>「立即修正並啟用」</td>
      </tr>
  </tbody>
</table>
<h4 id="模糊不精確詞彙類">模糊不精確詞彙類</h4>
<table>
  <thead>
      <tr>
          <th>中文違規詞彙</th>
          <th>英文違規詞彙</th>
          <th>違反原則</th>
          <th>正確替代</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「智能」</td>
          <td>&ldquo;smart&rdquo;, &ldquo;intelligent&rdquo;</td>
          <td>精確性原則</td>
          <td>「規則比對」「條件判斷」「算法處理」</td>
      </tr>
      <tr>
          <td>「自動」(無具體描述)</td>
          <td>&ldquo;auto&rdquo; (without details)</td>
          <td>精確性原則</td>
          <td>「Hook腳本執行」「定時任務觸發」</td>
      </tr>
      <tr>
          <td>「優化」(無具體指標)</td>
          <td>&ldquo;optimize&rdquo; (without metrics)</td>
          <td>精確性原則</td>
          <td>「減少記憶體使用50%」「提升載入速度2倍」</td>
      </tr>
  </tbody>
</table>
<h2 id="處理原則">處理原則</h2>
<h3 id="面對複雜問題">面對複雜問題</h3>
<p>複雜不是逃避的理由。我們的處理方式：</p>
<ol>
<li><strong>分解</strong>：任何複雜問題都能分解為可管理的部分</li>
<li><strong>排序</strong>：依影響和依賴關係決定處理順序</li>
<li><strong>執行</strong>：逐步解決，每步都有可驗證的成果</li>
</ol>
<h3 id="意外狀況的決策">意外狀況的決策</h3>
<p>遇到預期外的技術挑戰時：</p>
<h4 id="立即解決的情況">立即解決的情況</h4>
<ul>
<li>阻塞其他開發 → <strong>立即處理</strong></li>
<li>影響資料完整性 → <strong>立即處理</strong></li>
<li>安全漏洞 → <strong>立即處理</strong></li>
</ul>
<h4 id="可以延後的情況">可以延後的情況</h4>
<ul>
<li>效能優化（功能正常）→ <strong>可計劃延後</strong></li>
<li>UI 美化（體驗可用）→ <strong>可計劃延後</strong></li>
<li>邊緣案例（主流程正常）→ <strong>可計劃延後</strong></li>
</ul>
<h4 id="需要重新評估的情況">需要重新評估的情況</h4>
<ul>
<li>暴露設計缺陷 → <strong>停下來重新設計</strong></li>
<li>依賴關係錯誤 → <strong>調整架構</strong></li>
<li>需求理解偏差 → <strong>重新確認需求</strong></li>
</ul>
<h2 id="ai-回應前自檢機制詳解">AI 回應前自檢機制詳解</h2>
<h3 id="30秒強制檢查流程">30秒強制檢查流程</h3>
<p><strong>AI 在生成任何回應前都必須完整執行以下檢查</strong>：</p>
<h4 id="第一階段三大鐵律檢查">第一階段：三大鐵律檢查</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 測試通過率鐵律檢查
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   問：我的回應是否包含「測試失敗可接受」的思維？
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   問：是否想要跳過測試或降低測試標準？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   要求：必須堅持 100% 測試通過率
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">2. 永不放棄鐵律檢查
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   問：我是否想跳過、暫時處理、或延後任何問題？
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   問：是否在尋找「更簡單」的方法來迴避複雜性？
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   要求：必須面對並解決所有問題
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">3. 架構債務零容忍鐵律檢查
</span></span><span class="line"><span class="ln">12</span><span class="cl">   問：我是否發現架構問題卻想稍後處理？
</span></span><span class="line"><span class="ln">13</span><span class="cl">   問：是否想要使用臨時方案或權宜措施？
</span></span><span class="line"><span class="ln">14</span><span class="cl">   要求：必須立即修正架構問題</span></span></code></pre></div><h4 id="第二階段逃避詞彙檢查">第二階段：逃避詞彙檢查</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl">中文禁用詞彙掃描：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">□ 「太複雜」「先將就」「暫時性修正」「症狀緩解」
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">□ 「先這樣處理」「臨時解決方案」「回避」「不想處理」
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">□ 「更簡單的方法」「簡化處理」「簡化測試」
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">□ 「發現問題但不處理」「架構問題先不管」「只加個 TODO」
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">□ 「智能」「自動」<span class="o">(</span>無具體描述<span class="o">)</span>「優化」<span class="o">(</span>無具體指標<span class="o">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">英文禁用詞彙掃描：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">□ <span class="s2">&#34;workaround&#34;</span>, <span class="s2">&#34;bypass&#34;</span>, <span class="s2">&#34;hack&#34;</span>, <span class="s2">&#34;quick fix&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">□ <span class="s2">&#34;too complex&#34;</span>, <span class="s2">&#34;temporary fix&#34;</span>, <span class="s2">&#34;ignore for now&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">□ <span class="s2">&#34;simpler approach&#34;</span>, <span class="s2">&#34;simpler way&#34;</span>, <span class="s2">&#34;easier approach&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">□ <span class="s2">&#34;simplify test&#34;</span>, <span class="s2">&#34;basic test only&#34;</span>, <span class="s2">&#34;minimal test&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">□ <span class="s2">&#34;comment out&#34;</span>, <span class="s2">&#34;disable&#34;</span>, <span class="s2">&#34;just add todo&#34;</span></span></span></code></pre></div><h4 id="第三階段品質標準檢查">第三階段：品質標準檢查</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">要求：解決方案完整性檢查
</span></span><span class="line"><span class="ln">2</span><span class="cl">   我的解決方案是完整的，還是妥協的？
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">要求：根本問題檢查
</span></span><span class="line"><span class="ln">5</span><span class="cl">   我是否在迴避根本問題？
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">要求：專業標準檢查
</span></span><span class="line"><span class="ln">8</span><span class="cl">   我的回應體現專業工程師標準嗎？
</span></span><span class="line"><span class="ln">9</span><span class="cl">   6個月後我會為這個回應感到驕傲嗎？</span></span></code></pre></div><h3 id="檢查失敗立即修正流程">檢查失敗立即修正流程</h3>
<p><strong>如果任何檢查項目失敗</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 立即停止
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   停止當前回應生成，不發送任何內容
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">2. 重新分析
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   深入分析問題和解決方案
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   識別導致違規的根本原因
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">3. 重新構思
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   構思完全符合三大鐵律的回應
</span></span><span class="line"><span class="ln">10</span><span class="cl">   確保解決方案完整且專業
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">4. 再次檢查
</span></span><span class="line"><span class="ln">13</span><span class="cl">   重新執行完整的 30 秒檢查流程
</span></span><span class="line"><span class="ln">14</span><span class="cl">   直到通過所有檢查項目才能回應</span></span></code></pre></div><h3 id="關鍵情境標準處理">關鍵情境標準處理</h3>
<h4 id="測試失敗情境">測試失敗情境</h4>
<p><strong>觸發條件</strong>：任何測試失敗、通過率 &lt; 100%</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">強制執行流程：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">1. 立即停止所有其他工作
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">2. 啟動深度分析 agent (lavender-interface-designer)
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">3. 制定並執行完整解決方案
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">4. 驗證達到 100% 通過率
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">5. 記錄解決過程到工作日誌
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">嚴格禁止：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">禁止：跳過失敗的測試
</span></span><span class="line"><span class="ln">10</span><span class="cl">禁止：認為 92% 通過率「已經很好」
</span></span><span class="line"><span class="ln">11</span><span class="cl">禁止：先處理其他「更重要」的工作
</span></span><span class="line"><span class="ln">12</span><span class="cl">禁止：採用臨時修補方案</span></span></code></pre></div><h4 id="複雜問題情境">複雜問題情境</h4>
<p><strong>觸發條件</strong>：問題看起來複雜、不知道解法</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">強制執行流程：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">1. 承認複雜性，但拒絕放棄
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">2. 將複雜問題分解為可管理的子問題
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">3. 啟動相應的專業 agent
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">4. 按優先級逐一解決子問題
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">5. 記錄完整過程供未來參考
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">嚴格禁止：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">禁止：認為問題「太複雜」而放棄
</span></span><span class="line"><span class="ln">10</span><span class="cl">禁止：尋求「權宜方案」
</span></span><span class="line"><span class="ln">11</span><span class="cl">禁止：跳過問題處理其他工作
</span></span><span class="line"><span class="ln">12</span><span class="cl">禁止：採用「暫時性」解決方案</span></span></code></pre></div><h4 id="架構問題情境">架構問題情境</h4>
<p><strong>觸發條件</strong>：發現設計缺陷、技術債務、架構不一致</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">強制執行流程：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">1. 立即停止功能開發
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">2. 評估影響範圍
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">3. 啟動重構專家 (cinnamon-refactor-owl)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">4. 制定詳細修復計劃
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">5. 徹底修正後再繼續功能開發
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">嚴格禁止：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">禁止：認為架構問題「影響不大」
</span></span><span class="line"><span class="ln">10</span><span class="cl">禁止：想要「之後再處理」
</span></span><span class="line"><span class="ln">11</span><span class="cl">禁止：繼續在有問題的架構上開發
</span></span><span class="line"><span class="ln">12</span><span class="cl">禁止：採用簡單修補而非根本性解決</span></span></code></pre></div><h2 id="執行標準">執行標準</h2>
<h3 id="延後必須有記錄">延後必須有記錄</h3>
<p>每個延後決定都要：</p>
<ul>
<li>記錄在工作日誌</li>
<li>標註預計處理時間</li>
<li>說明延後的理由</li>
</ul>
<h3 id="定期檢視累積">定期檢視累積</h3>
<ul>
<li>Sprint 結束檢查延後項目</li>
<li>版本發布前清理技術債務</li>
<li>超過兩個版本的延後必須處理</li>
</ul>
<h3 id="品質底線不妥協">品質底線不妥協</h3>
<p>無論如何都不能延後的：</p>
<ul>
<li><strong>安全性</strong>：任何安全問題</li>
<li><strong>資料完整性</strong>：可能造成資料遺失或錯誤</li>
<li><strong>核心體驗</strong>：用戶主要使用路徑</li>
</ul>
<h2 id="判別範例">判別範例</h2>
<h3 id="正例正確的延後">正例：正確的延後</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// TODO: v0.2 版本加入批次處理
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// 目前 v0.1 實作單筆處理
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">processItem</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">return</span> <span class="nx">processSingle</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span> <span class="c1">// 完整功能，可延後優化
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>理由：功能完整、有明確版本規劃、不影響使用。</p>
<h3 id="反例錯誤的逃避">反例：錯誤的逃避</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// TODO: 錯誤處理太麻煩，先不做
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">riskyOperation</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">return</span> <span class="nx">doOperation</span><span class="p">();</span> <span class="c1">// 缺少錯誤處理，不可接受
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre></div><p>理由：核心責任缺失、沒有時程、影響穩定性。</p>
<h3 id="正例正確的最小實現">正例：正確的最小實現</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">class</span> <span class="nx">DataProcessor</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nx">process</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1">// v0.1: 同步處理
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">syncProcess</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="c1">// 預留介面給 v0.2 非同步處理
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>  <span class="kr">async</span> <span class="nx">processAsync</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;v0.2 功能&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>理由：當前版本完整可用、為未來預留空間、有清楚邊界。</p>
<h3 id="反例錯誤的妥協">反例：錯誤的妥協</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 測試太嚴格，先降低標準
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">test</span><span class="p">.</span><span class="nx">skip</span><span class="p">(</span><span class="s1">&#39;應該要處理邊界情況&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="c1">// 跳過困難的測試
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><p>理由：降低品質標準、逃避問題、累積風險。</p>
<h2 id="文化與態度">文化與態度</h2>
<h3 id="我們的信念">我們的信念</h3>
<ul>
<li><strong>沒有解決不了的問題</strong>，只有還沒找到的方法</li>
<li><strong>延後是策略</strong>，逃避是失職</li>
<li><strong>技術債務必須管理</strong>，不是忽視</li>
</ul>
<h3 id="我們的承諾">我們的承諾</h3>
<ul>
<li>誠實面對技術挑戰</li>
<li>為決定負責並追蹤</li>
<li>維持專案的長期健康</li>
</ul>
<h3 id="我們的標準">我們的標準</h3>
<ul>
<li>每個延後都有明確計劃</li>
<li>每個問題都有解決方案</li>
<li>每個決定都能說明理由</li>
</ul>
<h2 id="執行保障三層防護機制完整架構">執行保障：三層防護機制完整架構</h2>
<h3 id="完整防護體系">完整防護體系</h3>
<p>為了確保這些原則被確實執行，我們建立了三層互補的防護機制：</p>
<h4 id="第一層ai-內建自檢主要防線">第一層：AI 內建自檢（主要防線）</h4>
<ul>
<li><strong>執行時機</strong>：每次回應前自動執行</li>
<li><strong>檢查範圍</strong>：三大鐵律、逃避詞彙、品質標準</li>
<li><strong>處理方式</strong>：檢查失敗立即停止回應生成，重新構思</li>
<li><strong>特色</strong>：源頭預防，即時阻止</li>
</ul>
<h4 id="第二層hook-系統驗證輔助驗證">第二層：Hook 系統驗證（輔助驗證）</h4>
<ul>
<li><strong>執行時機</strong>：用戶輸入、檔案編輯、工具使用前後</li>
<li><strong>檢查範圍</strong>：ESLint 錯誤、技術債務、程式異味</li>
<li><strong>處理方式</strong>：自動記錄問題、啟動 agents 處理</li>
<li><strong>特色</strong>：持續監控，事後驗證</li>
</ul>
<h4 id="第三層修復模式最後防線">第三層：修復模式（最後防線）</h4>
<ul>
<li><strong>觸發條件</strong>：前兩層發現違規行為或品質問題</li>
<li><strong>處理方式</strong>：進入修復模式，完成修正後才能繼續</li>
<li><strong>特色</strong>：強制修正，確保問題得到解決</li>
</ul>
<h3 id="三層協作機制">三層協作機制</h3>
<h4 id="正常工作流程">正常工作流程</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">用戶輸入 → AI自檢 → 通過 → 生成回應 → Hook驗證 → 正常繼續</span></span></code></pre></div><h4 id="發現問題時">發現問題時</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">AI自檢失敗 → 重新構思 → 再次檢查 → 通過後回應
</span></span><span class="line"><span class="ln">2</span><span class="cl">Hook檢測問題 → 記錄追蹤 → 必要時啟動修復模式
</span></span><span class="line"><span class="ln">3</span><span class="cl">修復模式 → 停止開發 → 修正問題 → 驗證完成 → 繼續開發</span></span></code></pre></div><h3 id="關鍵優勢">關鍵優勢</h3>
<h4 id="多重保障">多重保障</h4>
<ul>
<li>AI 自檢確保源頭品質</li>
<li>Hook 系統提供持續監控</li>
<li>修復模式確保問題解決</li>
</ul>
<h4 id="精確判斷">精確判斷</h4>
<ul>
<li>區分計劃性延後和逃避行為</li>
<li>識別技術文檔中的範例程式碼</li>
<li>理解上下文和開發階段</li>
</ul>
<h4 id="自動化執行">自動化執行</h4>
<ul>
<li>無需人工干預</li>
<li>一致性執行標準</li>
<li>即時問題發現和處理</li>
</ul>
<h3 id="防護機制效果">防護機制效果</h3>
<p>這套三層防護機制確保：</p>
<ul>
<li><strong>原則被一致執行</strong>：沒有例外情況</li>
<li><strong>問題被及時發現</strong>：多個時間點檢查</li>
<li><strong>品質不因壓力妥協</strong>：自動化強制執行</li>
<li><strong>逃避行為被根本預防</strong>：從思維層面攔截</li>
</ul>
<h2 id="結論從根本預防逃避行為的完整方法論">結論：從根本預防逃避行為的完整方法論</h2>
<p>這套方法論代表了我們在 AI 協作品質控制上的重大突破。我們從最初的 Hook 系統發展到現在的三層防護機制，實現了從<strong>事後修正</strong>到<strong>源頭預防</strong>的根本性轉變。</p>
<h3 id="核心成就">核心成就</h3>
<h4 id="從反應式到預防式">從反應式到預防式</h4>
<ul>
<li><strong>過去</strong>：Hook 系統發現問題後修正</li>
<li><strong>現在</strong>：AI 自檢在問題產生前攔截</li>
<li><strong>效果</strong>：逃避行為從源頭被消除</li>
</ul>
<h4 id="從單一防護到多層保障">從單一防護到多層保障</h4>
<ul>
<li><strong>第一層</strong>：AI 內建自檢（30秒強制檢查）</li>
<li><strong>第二層</strong>：Hook 系統驗證（持續監控）</li>
<li><strong>第三層</strong>：修復模式（強制修正）</li>
<li><strong>效果</strong>：任何逃避行為都無法逃脫檢測</li>
</ul>
<h4 id="從人工監督到自動化執行">從人工監督到自動化執行</h4>
<ul>
<li><strong>自檢機制</strong>：每次回應前自動執行</li>
<li><strong>檢查標準</strong>：三大鐵律 + 違規詞彙 + 品質標準</li>
<li><strong>處理流程</strong>：檢查失敗立即重新構思</li>
<li><strong>效果</strong>：零人工干預的品質保證</li>
</ul>
<h3 id="方法論價值">方法論價值</h3>
<p>這不只是技術工具，更是<strong>工作哲學的具體實現</strong>：</p>
<h4 id="永不妥協的品質標準">永不妥協的品質標準</h4>
<ul>
<li>計劃性延後是正當策略</li>
<li>逃避性拖延絕不容忍</li>
<li>100% 測試通過率是最低標準</li>
<li>架構問題必須立即修正</li>
</ul>
<h4 id="思維模式">思維模式</h4>
<ul>
<li>面對複雜問題不逃避，而是分解解決</li>
<li>發現問題立即處理，不推遲到未來</li>
<li>追求根本性解決，拒絕症狀緩解</li>
<li>維持長期視角，拒絕短期妥協</li>
</ul>
<h4 id="可持續的開發實踐">可持續的開發實踐</h4>
<ul>
<li>技術債務得到控制</li>
<li>程式碼品質持續提升</li>
<li>團隊標準保持一致</li>
<li>專案健康度不斷改善</li>
</ul>
<h3 id="執行承諾">執行承諾</h3>
<h4 id="這是要求不是建議這是標準不是理想">這是要求，不是建議。這是標準，不是理想</h4>
<ul>
<li>任何違反這些原則的行為，無論來自人類還是 AI，都會被三層防護機制檢測並要求修正</li>
<li>AI 每次回應前都必須通過 30 秒自檢</li>
<li>Hook 系統持續監控所有開發活動，確保品質標準</li>
<li>修復模式在發現問題時強制執行，直到問題徹底解決</li>
</ul>
<p>這套三層防護機制目標是：</p>
<ul>
<li><strong>可信賴的 AI 協作關係</strong></li>
<li><strong>可持續的品質保證體系</strong></li>
<li><strong>可擴展的開發方法論</strong></li>
<li><strong>可複製的成功模式</strong></li>
</ul>
<p>我們將持續改進這套機制，讓它成為所有 AI 協作專案的標準基礎設施，確保每個專案都能維持最高的品質標準。</p>
<p>這是品質保證系統，確保 AI 協作始終維持最高執行標準，而非放任品質下滑。</p>
<hr>]]></content:encoded></item></channel></rss>