<?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>Ticket系統 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/ticket%E7%B3%BB%E7%B5%B1/</link><description>Recent content in Ticket系統 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/ticket%E7%B3%BB%E7%B5%B1/index.xml" rel="self" type="application/rss+xml"/><item><title>Atomic Ticket 方法論 - 最小可追蹤任務單位設計</title><link>https://tarrragon.github.io/blog/record/atomic-ticket-%E6%96%B9%E6%B3%95%E8%AB%96-%E6%9C%80%E5%B0%8F%E5%8F%AF%E8%BF%BD%E8%B9%A4%E4%BB%BB%E5%8B%99%E5%96%AE%E4%BD%8D%E8%A8%AD%E8%A8%88/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/atomic-ticket-%E6%96%B9%E6%B3%95%E8%AB%96-%E6%9C%80%E5%B0%8F%E5%8F%AF%E8%BF%BD%E8%B9%A4%E4%BB%BB%E5%8B%99%E5%96%AE%E4%BD%8D%E8%A8%AD%E8%A8%88/</guid><description>&lt;p>Ticket 越開越大，驗收條件越來越模糊，code review 要重新推理背景——這是我們曾長期面對的問題。根源很簡單：我們沒有定義「一個 Ticket 應該是什麼」。&lt;/p></description><content:encoded><![CDATA[<p>Ticket 越開越大，驗收條件越來越模糊，code review 要重新推理背景——這是我們曾長期面對的問題。根源很簡單：我們沒有定義「一個 Ticket 應該是什麼」。</p>
<h2 id="從一個問題出發">從一個問題出發</h2>
<p>試想這樣一張 Ticket：「實作 ISBNScannerService 的 15 個測試」。</p>
<p>15 個不同的測試目標，掃描啟動、停止、ISBN-10 驗證、ISBN-13 驗證、離線快取、批次模式……每一項失敗都讓整張 Ticket 無法完成。負責人要同時記住 15 個目標才能判斷自己有沒有做完。</p>
<p>這不是一張 Ticket，是一份工作清單。</p>
<h3 id="一張-ticket最少應該是什麼">一張 Ticket，最少應該是什麼？</h3>
<p>答案：<strong>一個動詞 + 一個目標</strong>。</p>
<hr>
<h2 id="什麼是-atomic-ticket">什麼是 Atomic Ticket</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">Atomic Ticket = 動詞 + 單一目標</span></span></code></pre></div><p>核心特徵：</p>
<ul>
<li><strong>單一職責</strong>：只有一個修改原因</li>
<li><strong>獨立驗收</strong>：不需要等待其他任務</li>
<li><strong>不可再拆分</strong>：硬拆會產生循環依賴，表示這些部分本來就是一體的</li>
</ul>
<hr>
<h2 id="四個原子性檢查">四個原子性檢查</h2>
<h3 id="語義檢查">語義檢查</h3>
<p>「這個 Ticket 能用動詞加單一目標描述嗎？」</p>
<p>符合：「實作 startScan() 方法」、「修復 ISBN 驗證邏輯」。</p>
<p>不符合：描述裡有「和」或「並」——「實作掃描功能和離線支援」通常是兩張 Ticket 擠在一起。</p>
<h3 id="修改原因檢查">修改原因檢查</h3>
<p>「只有一個原因會導致這個 Ticket 需要修改嗎？」</p>
<p>「實作 startScan()」只受掃描 API 規格影響，單一原因。「實作掃描功能和離線支援」則是 API 變更或儲存格式變更都會觸發修改——兩個原因，應拆成兩張。</p>
<h3 id="驗收條件一致性">驗收條件一致性</h3>
<p>「所有驗收條件都指向同一個目標嗎？」</p>
<p>如果驗收條件同時涵蓋 startScan()、stopScan() 和離線快取，其實在同時驗收三件事，應該拆開。</p>
<h3 id="依賴獨立性檢查">依賴獨立性檢查</h3>
<p>「如果拆成兩張，它們之間有循環依賴嗎？」</p>
<p>「實作掃描啟動邏輯」和「實作掃描狀態管理」互相依賴，硬拆反而讓兩張都無法獨立完成——這種情況維持為單一 Ticket 才對。</p>
<p>反過來，「實作 startScan()」和「實作 stopScan()」是單向依賴，可以安全拆分。</p>
<hr>
<h2 id="什麼不是判斷原子性的依據">什麼不是判斷原子性的依據</h2>
<p>常見的錯誤標準：時間、程式碼行數、檔案數量、測試數量。</p>
<p>這些都是結果，不是原因。一個單一職責的任務可能只需要 10 行，也可能跨越多個檔案改 200 行。判斷的唯一依據是職責是否單一。</p>
<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">開發 (IMP) → 測試 (TST) → 調整 (ADJ)
</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">           分析 (ANA) → 調整 (ADJ)</span></span></code></pre></div><p>開發完成不代表測試通過，測試完成可能衍生調整。把這三種行為混在同一張 Ticket，就失去了「問題在哪個環節發生」的追蹤能力。</p>
<p>完整的追蹤鏈讓我們能回答：「這個修復是從哪一次測試失敗衍生的？那次測試又是為了驗證哪個開發任務？」</p>
<h3 id="ticket-類型定義">Ticket 類型定義</h3>
<p>七種類型區分不同性質的任務：</p>
<ul>
<li>IMP（Implementation）：開發新功能</li>
<li>TST（Testing）：執行測試驗證</li>
<li>ADJ（Adjustment）：調整或修復問題</li>
<li>RES（Research）：探索未知領域</li>
<li>ANA（Analysis）：理解現狀和問題</li>
<li>INV（Investigation）：深入追蹤問題根因</li>
<li>DOC（Documentation）：記錄和傳承經驗</li>
</ul>
<hr>
<h2 id="ticket-關聯追蹤">Ticket 關聯追蹤</h2>
<p>每張 Ticket 記錄三個關聯欄位：</p>
<ul>
<li><code>source_ticket</code>：觸發此 Ticket 的來源</li>
<li><code>spawned_tickets</code>：此 Ticket 衍生的後續 Ticket</li>
<li><code>dispatch_reason</code>：派發原因和交接理由</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">開發 Ticket (IMP) 完成
</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">    v
</span></span><span class="line"><span class="ln">5</span><span class="cl">測試 Ticket (TST)
</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">    v
</span></span><span class="line"><span class="ln">9</span><span class="cl">調整 Ticket (ADJ)，dispatch_reason: &#34;UC-01 測試失敗，需修復 ImportService&#34;</span></span></code></pre></div><hr>
<h2 id="ticket-id-命名規範">Ticket 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">根任務：{Version}-W{Wave}-{Seq}
</span></span><span class="line"><span class="ln">2</span><span class="cl">子任務：{根ID}.{n}[.{n}...]</span></span></code></pre></div><p>Wave 代表執行批次的依賴層級：W1 無依賴可並行，W2 依賴 W1，以此類推。</p>
<p><code>0.15.16-W1-001</code> 是 v0.15.16 的 Wave 1 第一個任務。<code>0.31.0-W3-002.1</code> 是 0.31.0-W3-002 的第一個子任務。單看 ID 就能判斷它在版本工作流中的位置。</p>
<hr>
<h2 id="5w1h-驅動的欄位設計">5W1H 驅動的欄位設計</h2>
<p>每張 Ticket 的 YAML 欄位對應 5W1H：</p>
<ul>
<li>Who：負責人與各 Phase 的歷史負責人</li>
<li>What：任務目標（動詞加單一目標）</li>
<li>When：觸發時機</li>
<li>Where：架構層級與影響的檔案清單</li>
<li>Why：需求依據</li>
<li>How：任務類型與實作策略</li>
</ul>
<p>欄位設計讓 Ticket 本身就能完整描述任務全貌，交接時不需要額外解釋背景。</p>
<hr>
<h2 id="拆分範例">拆分範例</h2>
<h3 id="把功能清單拆成原子任務">把功能清單拆成原子任務</h3>
<p>原始需求：「實作 ISBNScannerService 的 15 個測試」</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">0.15.16-W1-001: 實作 startScan() 方法
</span></span><span class="line"><span class="ln">2</span><span class="cl">0.15.16-W1-002: 實作 stopScan() 方法
</span></span><span class="line"><span class="ln">3</span><span class="cl">0.15.16-W1-003: 實作 validateIsbn10() 驗證邏輯
</span></span><span class="line"><span class="ln">4</span><span class="cl">0.15.16-W1-004: 實作 validateIsbn13() 驗證邏輯
</span></span><span class="line"><span class="ln">5</span><span class="cl">0.15.16-W2-005: 實作離線掃描支援（依賴 W1）
</span></span><span class="line"><span class="ln">6</span><span class="cl">0.15.16-W2-006: 實作批次掃描模式（依賴 W1）</span></span></code></pre></div><p>W1 任務可並行，W2 等 W1 完成後進行。</p>
<h3 id="知道什麼時候不該拆">知道什麼時候不該拆</h3>
<p>需求：「實作 BookRepository.save() 方法」</p>
<p>有人可能拆成：Ticket A 實作簽名、Ticket B 實作邏輯、Ticket C 實作驗證。</p>
<p>這是錯的。save() 的簽名、邏輯、驗證三者循環依賴，硬拆後每張都無法獨立完成。正確做法是維持為單一 Ticket。</p>
<hr>
<h2 id="建立-ticket-前的四個問題">建立 Ticket 前的四個問題</h2>
<ol>
<li>能用「動詞 + 單一目標」描述嗎？</li>
<li>只有一個修改原因嗎？</li>
<li>所有驗收條件都指向同一個目標嗎？</li>
<li>如果拆開，不會產生循環依賴嗎？</li>
</ol>
<p>常見的違反模式：「實作 X 和 Y」（兩個目標）、「修復所有 X 測試」（多個目標）、「重構 X 並優化 Y」（兩個行動）、「建立 X 的完整功能」（目標模糊）。</p>]]></content:encoded></item><item><title>驗收條件方法論 - 確保任務完整性的系統化驗收標準</title><link>https://tarrragon.github.io/blog/record/%E9%A9%97%E6%94%B6%E6%A2%9D%E4%BB%B6%E6%96%B9%E6%B3%95%E8%AB%96-%E7%A2%BA%E4%BF%9D%E4%BB%BB%E5%8B%99%E5%AE%8C%E6%95%B4%E6%80%A7%E7%9A%84%E7%B3%BB%E7%B5%B1%E5%8C%96%E9%A9%97%E6%94%B6%E6%A8%99%E6%BA%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E9%A9%97%E6%94%B6%E6%A2%9D%E4%BB%B6%E6%96%B9%E6%B3%95%E8%AB%96-%E7%A2%BA%E4%BF%9D%E4%BB%BB%E5%8B%99%E5%AE%8C%E6%95%B4%E6%80%A7%E7%9A%84%E7%B3%BB%E7%B5%B1%E5%8C%96%E9%A9%97%E6%94%B6%E6%A8%99%E6%BA%96/</guid><description>&lt;p>有一天，一個開發任務進入了完成狀態。所有人都說「做完了」。但一週後我們才發現，「做完了」對每個人的意義不同。有人以為只要主功能可以跑，有人以為還需要測試全綠，有人以為文件也要同步更新。這件事讓我們開始認真想：一個任務究竟在什麼條件下才算「完成」？&lt;/p></description><content:encoded><![CDATA[<p>有一天，一個開發任務進入了完成狀態。所有人都說「做完了」。但一週後我們才發現，「做完了」對每個人的意義不同。有人以為只要主功能可以跑，有人以為還需要測試全綠，有人以為文件也要同步更新。這件事讓我們開始認真想：一個任務究竟在什麼條件下才算「完成」？</p>
<p>驗收條件（Acceptance Criteria）這個詞不陌生，但在實際流程中，它往往淪為形式：「功能實作完成」、「測試通過」、「文件更新」——三條模糊的描述，沒有人能在執行後說出「這確實通過了」的具體理由。</p>
<hr>
<h2 id="驗收條件是一份契約">驗收條件是一份契約</h2>
<p>核心定義：驗收條件是用戶和執行者之間的明確約定，雙方都能清楚判斷任務是否完成。</p>
<p>關鍵在「雙方都能清楚判斷」。不是執行者自己說「差不多了」，而是在撰寫驗收條件的當下，就預先定義好「怎樣算完成」的標準。</p>
<p>這延伸出 4V 原則：</p>
<p><strong>可驗證（Verifiable）</strong>：每一條都要有具體的確認方法。執行什麼命令？看什麼輸出？要寫明。</p>
<p><strong>可量化（Quantifiable）</strong>：「所有測試通過」不夠，要說「77/77 個測試通過」。量化讓驗收可以被計算，不是被感覺。</p>
<p><strong>可追溯（Traceable）</strong>：每一條都應該引用來源——哪個設計文件的哪一行、哪個規格的哪個章節。來源讓驗收有根據。</p>
<p><strong>可記錄（Documented）</strong>：驗收結果必須能寫入文件，留下狀態標記。三個月後翻開 Ticket，能看到每一條是否通過、通過的證據是什麼。</p>
<hr>
<h2 id="四種情境四種確認方式">四種情境，四種確認方式</h2>
<p>不同性質的驗收項目需要不同的確認方式，混在一起只會讓「怎麼確認」這件事也變模糊。</p>
<p><strong>功能驗收</strong>：程式功能是否正確實作。確認方式是執行命令、看輸出是否符合預期。</p>
<p><strong>流程驗收</strong>：開發流程品質關卡——測試全綠、靜態分析無錯誤。確認方式是執行測試套件和工具輸出狀態。</p>
<p><strong>文件驗收</strong>：文件是否正確更新。確認方式是檢查特定檔案存在、特定內容在正確位置。</p>
<p><strong>建議驗收</strong>：Ticket 執行過程中收到的建議，每一條都需要有明確的處置（採納、拒絕或延後），不能讓建議懸在半空中。</p>
<hr>
<h2 id="證據不是感覺">證據，不是感覺</h2>
<p>每一條驗收項目通過後，要留下對應的紀錄：命令輸出留命令和輸出文字、測試通過留「77/77 tests passed」、靜態分析留「dart analyze: 0 issues」。</p>
<p>這看起來繁瑣，但意義在於：三個月後翻開這個 Ticket 的人，不需要重跑所有確認，就能看到當初怎麼通過的。</p>
<hr>
<h2 id="模糊詞彙是驗收條件的天敵">模糊詞彙是驗收條件的天敵</h2>
<p>幾個最常出現的危險詞彙：</p>
<p>「完成」沒有說完成的標準是什麼。換成「某子命令可執行並顯示預期的樹狀結構」。</p>
<p>「正常」沒有定義什麼叫正常。換成「輸入無效 ID 時顯示特定錯誤訊息」。</p>
<p>「適當」最危險，因為每個人的感受不同。引用具體標準文件，說「符合品質基線文件的測試覆蓋率要求」。</p>
<p>「符合規範」不說是哪個規範。要把規範文件寫出來。</p>
<hr>
<h2 id="最低成本陷阱一個真實的故事">最低成本陷阱：一個真實的故事</h2>
<p>我們曾遇到這樣的驗收條件：「SKILL.md 只列已實作指令，或實作缺失指令」。</p>
<p>這個「或」讓執行者面對兩個選項：刪除文件裡還沒實作的指令描述（省力），或去實作缺失的功能（耗時）。在沒有明確業務意圖的情況下，執行者幾乎必然選第一個。</p>
<p>結果是技術上正確、業務上損失慘重——那些「未實作的指令」其實是規劃中的功能，刪掉等於丟失了產品藍圖。</p>
<p>從那以後我們立下一條規則：驗收條件不可以用「或」連接不同方案，不可以讓執行者自行選擇最低成本的方向。</p>
<p>正確做法是在撰寫驗收條件前先確認業務意圖：這個任務要解決什麼問題？根本原因是什麼？理想結果是什麼？什麼結果不可接受？回答這四個問題後，方向才能確定，而且只有一個方向。</p>
<hr>
<h2 id="業務意圖的三個宣告">業務意圖的三個宣告</h2>
<p>每個 Ticket 的驗收條件要包含三個業務層面的說明：業務目標（要達成什麼）、期望結果（完成後應該是什麼狀態）、禁止結果（完成後不應該是什麼狀態）。</p>
<p>「禁止結果」最常被忽略，但它往往是防止最低成本陷阱最有效的護欄。明確寫下「不可損失規劃中的功能」，執行者就沒辦法假裝「刪除文件」是合理的選擇。</p>
<p>類似地，當文件和實作不一致時，不能用「修正不一致」帶過。要先判斷這個不一致的性質：規劃中的功能（保留並建立實作 Ticket）、錯誤記錄（移除）、過時功能（標記 deprecated）、重命名移動（更新引用）——四種情況對應四種處理方式，一條模糊的驗收條件無法涵蓋。</p>
<hr>
<h2 id="格式的選擇">格式的選擇</h2>
<p>三種格式對應不同複雜度：</p>
<p>簡單任務（五條以下）用清單，粗體標記項目，括號標記來源，後面說明確認方法。</p>
<p>中型任務用表格，有編號、項目描述、來源引用、確認方法、完成狀態，按功能/流程/文件分類。</p>
<p>複雜任務用分類格式，每個類別獨立表格，加上驗收摘要區段：「功能驗收 0/N 完成」、「流程驗收 0/N 完成」。</p>
<p>選格式的關鍵是任務是否跨越多個類別。只要同時涵蓋功能實作、測試品質、文件更新，就應該用分類格式。</p>
<hr>
<h2 id="結語">結語</h2>
<p>驗收條件的本質是「事先定義完成的樣子」。這個過程強迫我們在任務開始前就把終點想清楚，而不是結束後才爭論「算不算完成」。</p>
<p>我們從這套方法論得到最大的收穫是一種心態的轉變：當你開始認真設計驗收條件，你其實是在認真思考這個任務究竟要達成什麼。</p>]]></content:encoded></item></channel></rss>