Ticket 拆分標準方法論
方法論概述
為什麼需要 Ticket 拆分標準
問題背景:
在大型軟體開發專案中,我們經常面臨以下挑戰:
- God Ticket 問題:單一任務過於龐大,包含數十個檔案修改,跨越多個架構層級
- 主觀判斷困境:不同開發者對「任務大小」的理解不同,導致協作效率低
- 進度追蹤困難:任務粒度不一致,難以準確評估完成度
- 風險控制失衡:大任務失敗影響大,小任務過碎增加管理成本
傳統拆分方法的問題:
- 經驗導向:依賴個人經驗判斷,缺乏客觀標準
- 時間估算:受個人能力和環境影響,難以標準化
- 模糊描述:「不要太大」「保持適中」等描述無法執行
- 後驗調整:任務執行後才發現過大,增加返工成本
核心目標
- 標準化任務拆分:提供統一的拆分標準,消除主觀判斷
- 控制任務複雜度:確保每個 Ticket 在可控範圍內
- 提升協作效率:明確的任務邊界,減少溝通成本
- 及早風險管控:設計階段就識別過大任務,降低執行風險
與其他方法論的關係
本方法論的定位:
1Ticket 設計派工方法論 (主方法論)
2├── Ticket 拆分標準方法論 ← 你正在閱讀
3│ └── 提供量化拆分標準和決策樹
4├── Code Smell 品質閘門檢測方法論
5│ └── 提供設計階段的品質檢測
6├── Ticket 生命週期管理方法論
7│ └── 提供 Ticket 執行流程管理
8└── 即時 Review 機制方法論
9 └── 提供執行過程中的 Review 機制第一章:量化指標體系
1.1 指標 1:職責數量(Responsibilities) 最優先
定義:Ticket 需要完成的獨立職責數量。
為什麼職責是第一指標:
- 最客觀:不受個人能力影響
- 最穩定:不受環境和參考資料影響
- 最易溝通:PM 和工程師都能理解
- 最精確:直接對應業務需求
職責的精確定義
什麼算一個職責:
一個職責 = 一個明確可驗證的功能點或邊界條件
識別方式:
- 每個「需要實作的功能點」算一個職責
- 每個「需要驗證的邊界條件」算一個職責
- 每個「需要處理的錯誤情境」算一個職責
範例說明:
1範例任務:實作書籍評分功能
2
3職責識別:
41. 定義 Rating Value Object(數值範圍驗證) ← 職責 1
52. 定義 Rating Entity(包含評分和評論) ← 職責 2
63. 實作 IRatingRepository 介面 ← 職責 3
74. 實作評分儲存邏輯 ← 職責 4
85. 處理無效評分錯誤 ← 職責 5
96. 處理資料庫錯誤 ← 職責 6
10
11總計:6 個職責 → 超過 5 個,必須拆分職責數量標準
| 等級 | 職責數量 | 判定 | 說明 |
|---|---|---|---|
| 簡單 Ticket | 1 個 | 理想 | 單一職責,最易管理 |
| 中等 Ticket | 2-3 個 | 可接受 | 少數相關職責,可控範圍 |
| 複雜 Ticket | 3-5 個 | 需檢查 | 多職責,建議拆分 |
| 必須拆分 | > 5 個 | 禁止 | 範圍失控,必須拆分 |
強制規則:
- 超過 5 個職責 = 必須拆分,無例外
- 3-5 個職責 = 評估是否可拆分,優先拆分
- 1-2 個職責 = 理想狀態,鼓勵維持
職責識別實例
實例 1:簡單 Ticket(1 職責)
1任務:定義 SelectionManager 介面方法簽名
2
3職責分析:
4職責 1:定義 toggleSelection、clearSelection、getSelectedIds 三個方法簽名
5
6總計:1 個職責
7判定:簡單 Ticket實例 2:中等 Ticket(2-3 職責)
1任務:實作 SelectionManager 基礎功能
2
3職責分析:
4職責 1:實作 toggleSelection 方法
5職責 2:實作 clearSelection 方法
6職責 3:實作 ChangeNotifier 通知機制
7
8總計:3 個職責
9判定:中等 Ticket(可接受)實例 3:複雜 Ticket(3-5 職責)- 建議拆分
1任務:實作完整的 BookRepository
2
3職責分析:
4職責 1:實作 getBookByIsbn CRUD 方法
5職責 2:實作 saveBook CRUD 方法
6職責 3:實作 Data Mapper 轉換
7職責 4:實作錯誤處理
8職責 5:實作 Cache 管理
9
10總計:5 個職責
11判定:複雜 Ticket(建議拆分為 2-3 個 Ticket)實例 4:必須拆分(> 5 職責)
1任務:實作書籍評分完整功能(包含 UI、UseCase、Repository)
2
3職責分析:
4職責 1:定義 Rating Value Object
5職責 2:定義 Rating Entity
6職責 3:建立 RatingWidget UI
7職責 4:實作 RateBookUseCase
8職責 5:定義 IRatingRepository
9職責 6:實作 SQLiteRatingRepository
10職責 7:處理各種異常情境
11職責 8:撰寫完整測試
12
13總計:8 個職責
14判定:God Ticket 必須拆分拆分建議:
1拆分為 4 個 Ticket:
2
3- Ticket 1: 定義 Rating Domain 模型(職責 1, 2)
4- Ticket 2: 實作 RatingRepository(職責 5, 6)
5- Ticket 3: 實作 RateBookUseCase(職責 4, 7)
6- Ticket 4: 實作 RatingWidget UI(職責 3, 8)1.2 指標 2:程式碼行數(Lines of Code)
定義:Ticket 涉及的程式碼修改行數(新增 + 修改 + 刪除)。
為什麼需要行數指標:
- 可量化:使用
git diff --stat精確統計 - 可驗證:執行後可驗證預估準確性
- 風險指標:行數越多,出錯風險越高
行數統計標準
測量方式:
1# 統計修改行數
2git diff --stat
3
4# 範例輸出
5lib/domain/entities/book.dart | 25 +++++++++++++
6lib/domain/repositories/i_repo.dart | 15 ++++++--
7test/unit/domain/book_test.dart | 40 ++++++++++++++++++++
83 files changed, 78 insertions(+), 2 deletions(-)
9
10# 計算方式
11總行數 = 新增行數 + 修改行數 + 刪除行數
12 = 25 + 15 + 40 = 80 行計算規則:
- 包含新增行數
- 包含修改行數
- 包含刪除行數
- 不包含空行(pure whitespace changes)
- 不包含純註解行(comment-only changes)
行數標準
| 等級 | 行數範圍 | 判定 | 說明 |
|---|---|---|---|
| 簡單 Ticket | < 30 行 | 理想 | Interface 定義、簡單 Value Object |
| 中等 Ticket | 30-70 行 | 可接受 | 中等實作、含測試 |
| 複雜 Ticket | 70-100 行 | 需檢查 | 複雜實作、多測試案例 |
| 必須拆分 | > 100 行 | 禁止 | 範圍過大,必須拆分 |
強制規則:
- 超過 100 行 = 必須拆分
- 70-100 行 = 評估是否可拆分
- < 70 行 = 可接受範圍
行數估算實例
實例 1:簡單 Ticket(< 30 行)
1// 任務:定義 IBookRepository 介面
2// lib/domain/repositories/i_book_repository.dart
3
4abstract class IBookRepository {
5 /// 根據 ISBN 取得書籍
6 Future<Book?> getBookByIsbn(String isbn);
7
8 /// 儲存書籍
9 Future<void> saveBook(Book book);
10
11 /// 刪除書籍
12 Future<void> deleteBook(String isbn);
13}
14
15// 預估行數:~20 行(含註解)
16// 判定:簡單 Ticket實例 2:中等 Ticket(30-70 行)
1// 任務:實作 Rating Value Object
2// lib/domain/value_objects/rating.dart + test
3
4// rating.dart (~40 行)
5class Rating {
6 final int value;
7
8 Rating(this.value) {
9 if (value < 1 || value > 5) {
10 throw ValidationException.invalidRating(value);
11 }
12 }
13
14 // ... 其他方法(equals, hashCode, toString)
15}
16
17// rating_test.dart (~25 行)
18void main() {
19 test('建立有效評分', () { ... });
20 test('評分過低拋出異常', () { ... });
21 test('評分過高拋出異常', () { ... });
22}
23
24// 總計:~65 行
25// 判定:中等 Ticket實例 3:複雜 Ticket(70-100 行)- 建議拆分
1// 任務:實作完整的 BookRepository
2// lib/infrastructure/repositories/sqlite_book_repository.dart + test
3
4// repository.dart (~80 行)
5class SQLiteBookRepository implements IBookRepository {
6 // ... 建構子和欄位定義
7
8 @override
9 Future<Book?> getBookByIsbn(String isbn) async {
10 // ... SQL 查詢邏輯(~15 行)
11 // ... Data Mapper 轉換(~10 行)
12 // ... 錯誤處理(~5 行)
13 }
14
15 @override
16 Future<void> saveBook(Book book) async {
17 // ... SQL 插入/更新邏輯(~20 行)
18 // ... Data Mapper 轉換(~10 行)
19 // ... 錯誤處理(~5 行)
20 }
21
22 @override
23 Future<void> deleteBook(String isbn) async {
24 // ... SQL 刪除邏輯(~10 行)
25 // ... 錯誤處理(~5 行)
26 }
27}
28
29// repository_test.dart (~80 行)
30// ... 8 個測試案例
31
32// 總計:~160 行
33// 判定:超過 100 行,必須拆分拆分建議:
1拆分為 3 個 Ticket:
2
3- Ticket 1: 定義 IBookRepository 介面(~20 行)
4- Ticket 2: 實作 getBookByIsbn + 測試(~60 行)
5- Ticket 3: 實作 saveBook + deleteBook + 測試(~80 行)1.3 指標 3:涉及檔案數(Files)
定義:Ticket 需要修改的檔案數量(不含測試檔案)。
為什麼需要檔案數指標:
- 架構層級指標:檔案數反映任務的架構範圍
- 風險管控:修改越多檔案,影響範圍越大
- 測試複雜度:檔案數越多,測試整合越複雜
檔案統計標準
測量方式:
1# 統計修改檔案數
2git diff --name-only | grep -v '_test\.dart$' | wc -l
3
4# 範例輸出
5lib/domain/entities/book.dart
6lib/domain/repositories/i_book_repository.dart
7lib/infrastructure/repositories/sqlite_book_repository.dart
8
9# 結果:3 個檔案(不含測試)計算規則:
- 包含新建檔案
- 包含修改檔案
- 包含刪除檔案
- 不包含測試檔案(測試檔案另計為「測試數量」指標)
- 不包含配置檔案(如 pubspec.yaml, analysis_options.yaml)
檔案數標準
| 等級 | 檔案數量 | 判定 | 說明 |
|---|---|---|---|
| 簡單 Ticket | 1 個 | 理想 | 單一檔案修改 |
| 中等 Ticket | 2-3 個 | 可接受 | 相關檔案修改 |
| 複雜 Ticket | 3-5 個 | 需檢查 | 多檔案修改,建議拆分 |
| 必須拆分 | > 5 個 | 禁止 | 範圍過大,必須拆分 |
強制規則:
- 超過 5 個檔案 = 必須拆分
- 3-5 個檔案 = 評估是否可拆分
- 1-2 個檔案 = 理想狀態
檔案數實例
實例 1:簡單 Ticket(1 個檔案)
1任務:建立 Rating Value Object
2
3涉及檔案:
4lib/domain/value_objects/rating.dart
5
6總計:1 個檔案
7判定:簡單 Ticket實例 2:中等 Ticket(2-3 個檔案)
1任務:更新 Book Entity 增加評分欄位
2
3涉及檔案:
4lib/domain/entities/book.dart (修改)
5lib/domain/value_objects/rating.dart (新增)
6
7總計:2 個檔案
8判定:中等 Ticket實例 3:複雜 Ticket(3-5 個檔案)- 建議拆分
1任務:實作完整的書籍評分功能
2
3涉及檔案:
4lib/domain/entities/rating.dart
5lib/domain/entities/book.dart
6lib/domain/repositories/i_rating_repository.dart
7lib/infrastructure/repositories/sqlite_rating_repository.dart
8lib/infrastructure/mappers/rating_mapper.dart
9
10總計:5 個檔案
11判定:複雜 Ticket(建議拆分)實例 4:必須拆分(> 5 個檔案)
1任務:實作評分功能(含 UI、UseCase、Repository)
2
3涉及檔案:
4lib/presentation/widgets/rating_widget.dart
5lib/presentation/controllers/rating_controller.dart
6lib/application/use_cases/rate_book_use_case.dart
7lib/domain/entities/rating.dart
8lib/domain/entities/book.dart
9lib/domain/repositories/i_rating_repository.dart
10lib/infrastructure/repositories/sqlite_rating_repository.dart
11lib/infrastructure/mappers/rating_mapper.dart
12
13總計:8 個檔案
14判定:God Ticket,必須拆分拆分建議:
1按 Clean Architecture 分層拆分為 4 個 Ticket:
2
3- Ticket 1: Domain 層(檔案 4, 5, 6) - 3 個檔案
4- Ticket 2: Infrastructure 層(檔案 7, 8) - 2 個檔案
5- Ticket 3: Application 層(檔案 3) - 1 個檔案
6- Ticket 4: Presentation 層(檔案 1, 2) - 2 個檔案1.4 指標 4:測試用例數(Tests)
定義:Ticket 對應的測試用例數量。
為什麼需要測試數量指標:
- 品質保證指標:測試數量反映功能複雜度
- 執行時間預估:測試數量影響 TDD 循環時間
- 維護成本:過多測試增加維護負擔
測試統計標準
測量方式:
1// 計算測試方法數
2void main() {
3 group('Rating Value Object', () {
4 test('建立有效評分', () { ... }); // 測試 1
5 test('評分過低拋出異常', () { ... }); // 測試 2
6 test('評分過高拋出異常', () { ... }); // 測試 3
7 test('相同評分視為相等', () { ... }); // 測試 4
8 });
9}
10
11// 總計:4 個測試計算規則:
- 每個
test('...', () {...})算一個測試 - 每個
testWidgets('...', () {...})算一個測試 - 包含單元測試和整合測試
- 不包含
group()本身(只是測試組織)
測試數量標準
| 等級 | 測試數量 | 判定 | 說明 |
|---|---|---|---|
| 簡單 Ticket | 1-3 個 | 理想 | 基本功能測試 |
| 中等 Ticket | 3-6 個 | 可接受 | 含邊界和異常測試 |
| 複雜 Ticket | 6-10 個 | 需檢查 | 複雜邏輯,多測試案例 |
| 必須拆分 | > 10 個 | 禁止 | 測試過多,必須拆分 |
強制規則:
- 超過 10 個測試 = 必須拆分
- 6-10 個測試 = 評估是否可拆分
- 1-6 個測試 = 可接受範圍
測試數量實例
實例 1:簡單 Ticket(1-3 個測試)
1// 任務:定義 Rating Value Object
2
3void main() {
4 group('Rating Value Object', () {
5 test('建立有效評分', () {
6 final rating = Rating(4);
7 expect(rating.value, 4);
8 });
9
10 test('評分過低拋出異常', () {
11 expect(() => Rating(0), throwsA(isA<ValidationException>()));
12 });
13
14 test('評分過高拋出異常', () {
15 expect(() => Rating(6), throwsA(isA<ValidationException>()));
16 });
17 });
18}
19
20// 總計:3 個測試
21// 判定:簡單 Ticket實例 2:中等 Ticket(3-6 個測試)
1// 任務:實作 BookRepository.getBookByIsbn
2
3void main() {
4 group('BookRepository.getBookByIsbn', () {
5 test('成功取得存在的書籍', () { ... }); // 測試 1
6 test('書籍不存在回傳 null', () { ... }); // 測試 2
7 test('無效 ISBN 拋出 ValidationException', () { ... }); // 測試 3
8 test('資料庫錯誤拋出 StorageException', () { ... }); // 測試 4
9 test('資料庫連線失敗拋出 NetworkException', () { ... }); // 測試 5
10 });
11}
12
13// 總計:5 個測試
14// 判定:中等 Ticket實例 3:複雜 Ticket(6-10 個測試)- 建議拆分
1// 任務:實作完整的 BookRepository CRUD
2
3void main() {
4 group('BookRepository CRUD', () {
5 // getBookByIsbn 測試
6 test('取得存在的書籍', () { ... }); // 測試 1
7 test('書籍不存在回傳 null', () { ... }); // 測試 2
8
9 // saveBook 測試
10 test('新增書籍成功', () { ... }); // 測試 3
11 test('更新書籍成功', () { ... }); // 測試 4
12 test('儲存時資料庫錯誤', () { ... }); // 測試 5
13
14 // deleteBook 測試
15 test('刪除存在的書籍', () { ... }); // 測試 6
16 test('刪除不存在的書籍', () { ... }); // 測試 7
17 test('刪除時資料庫錯誤', () { ... }); // 測試 8
18
19 // Data Mapper 測試
20 test('Entity 轉 DTO 正確', () { ... }); // 測試 9
21 test('DTO 轉 Entity 正確', () { ... }); // 測試 10
22 });
23}
24
25// 總計:10 個測試
26// 判定:複雜 Ticket(達上限,建議拆分)拆分建議:
1拆分為 3 個 Ticket:
2
3- Ticket 1: getBookByIsbn + 測試(2 個測試)
4- Ticket 2: saveBook + 測試(3 個測試)
5- Ticket 3: deleteBook + Mapper + 測試(5 個測試)1.5 指標整合評估方法
整合評估原則
最高等級原則:
- 取 4 個指標中「最高的複雜度等級」作為最終評估結果
- 任一指標達到「必須拆分」,則整個 Ticket 必須拆分
評估公式:
1Ticket 複雜度 = max(職責複雜度, 行數複雜度, 檔案數複雜度, 測試數複雜度)
2
3where 複雜度等級:
4 簡單 = 1
5 中等 = 2
6 複雜 = 3
7 必須拆分 = 4整合評估實例
實例 1:所有指標都是簡單 → 簡單 Ticket
1任務:定義 Rating Value Object
2
3指標評估:
4
5- 職責數量:1 個職責 → 簡單
6- 程式碼行數:25 行 → 簡單
7- 涉及檔案:1 個檔案 → 簡單
8- 測試用例:3 個測試 → 簡單
9
10最終判定:簡單 Ticket(最理想狀態)實例 2:有一個指標是中等 → 中等 Ticket
1任務:實作 Rating Value Object
2
3指標評估:
4
5- 職責數量:2 個職責(建立、驗證)→ 中等
6- 程式碼行數:45 行 → 中等
7- 涉及檔案:1 個檔案 → 簡單
8- 測試用例:5 個測試 → 中等
9
10最終判定:中等 Ticket(可接受)實例 3:有一個指標是複雜 → 複雜 Ticket(建議拆分)
1任務:實作 BookRepository.getBookByIsbn
2
3指標評估:
4
5- 職責數量:3 個職責(查詢、轉換、異常處理)→ 複雜
6- 程式碼行數:65 行 → 中等
7- 涉及檔案:2 個檔案 → 中等
8- 測試用例:6 個測試 → 複雜
9
10最終判定:複雜 Ticket(建議拆分)實例 4:有任一指標超標 → 必須拆分
1任務:實作完整的書籍評分功能
2
3指標評估:
4
5- 職責數量:8 個職責 → 必須拆分
6- 程式碼行數:180 行 → 必須拆分
7- 涉及檔案:7 個檔案 → 必須拆分
8- 測試用例:15 個測試 → 必須拆分
9
10最終判定:God Ticket 必須立即拆分評估決策流程
1步驟 1:計算 4 個指標
2 ↓
3步驟 2:取最高複雜度等級
4 ↓
5 ├─ 簡單 → 可直接建立 Ticket
6 ├─ 中等 → 可直接建立 Ticket(可選:評估是否拆分為更小 Ticket)
7 ├─ 複雜 → 強烈建議拆分(可選:無法拆分時勉強接受)
8 └─ 必須拆分 → 阻止建立,必須先拆分再重新評估評估檢查清單:
1□ 已計算 4 個指標的值
2□ 已確定每個指標的複雜度等級
3□ 已取最高複雜度等級作為最終判定
4□ 如為「必須拆分」,已執行拆分並重新評估
5□ 如為「複雜」,已評估是否可拆分為更小 Ticket第二章:複雜度評估方法
2.1 複雜度等級定義
4 級複雜度體系:
| 等級 | 職責 | 行數 | 檔案 | 測試 | 描述 | 處理方式 |
|---|---|---|---|---|---|---|
| Level 1: 簡單 | 1 個 | < 30 行 | 1 個 | 1-3 個 | 單一職責,單一檔案 | 直接建立 Ticket |
| Level 2: 中等 | 2-3 個 | 30-70 行 | 2-3 個 | 3-6 個 | 少數相關職責,少數檔案 | 直接建立 Ticket |
| Level 3: 複雜 | 3-5 個 | 70-100 行 | 3-5 個 | 6-10 個 | 多職責,多檔案 | 建議拆分 |
| Level 4: 超標 | > 5 個 | > 100 行 | > 5 個 | > 10 個 | 範圍失控 | 必須拆分 |
複雜度特徵:
Level 1: 簡單
- 特徵: 最小可交付單元,職責明確
- 適用: Interface 定義、單一 Value Object、單一方法實作
- 優點: 風險低、易測試、易 Review
- 預估時間: 5-20 分鐘
Level 2: 中等
- 特徵: 少數相關職責,內聚性高
- 適用: 含業務邏輯的 Entity、基礎 Repository 方法
- 注意: 確保職責相關性,避免職責分散
- 預估時間: 20-40 分鐘
Level 3: 複雜
- 特徵: 多職責或跨檔案,整合性高
- 適用: 完整 UseCase、Repository CRUD、複雜業務邏輯
- 風險: 測試複雜、Review 困難、易出錯
- 預估時間: 40-60 分鐘
- 建議: 優先評估是否可拆分為 Level 1-2
Level 4: 超標
- 特徵: 任一指標超標,範圍失控
- 問題: God Ticket、高風險、難以管理
- 處理: 必須拆分,無例外
- 禁止: 禁止建立此等級 Ticket
2.2 評估流程
3 步驟評估流程:
1步驟 1: 初步評估(基於任務描述)
2 ↓
3 計算 4 個指標的預估值
4 ↓
5步驟 2: 複雜度確認(取最高等級)
6 ↓
7 取 4 個指標中最高的複雜度等級
8 ↓
9步驟 3: 拆分決策(基於等級決定)
10 ↓
11 ├─ Level 1-2 → 可直接建立 Ticket
12 ├─ Level 3 → 評估是否可拆分
13 └─ Level 4 → 必須拆分步驟 1:初步評估
目標: 快速估算 4 個指標的值
評估依據:
- 任務描述(What to do)
- 驗收條件(Acceptance Criteria)
- 預期步驟(Steps)
評估方法:
1範例任務:實作 Book Entity
2
3步驟 1-1:估算職責數量
4- 分析任務描述,列出所有需要完成的功能點
5- 功能點 1:定義 Entity 欄位
6- 功能點 2:實作 equals/hashCode
7- 功能點 3:實作 toString
8- 功能點 4:撰寫測試
9→ 預估:4 個職責
10
11步驟 1-2:估算程式碼行數
12- 根據類似任務經驗估算
13- Entity 定義:~30 行
14- equals/hashCode:~15 行
15- toString:~5 行
16- 測試:~40 行
17→ 預估:~90 行
18
19步驟 1-3:估算檔案數量
20- 列出需要建立/修改的檔案
21- lib/domain/entities/book.dart(新增)
22- test/unit/domain/entities/book_test.dart(新增)
23→ 預估:2 個檔案(1 個生產 + 1 個測試)
24
25步驟 1-4:估算測試數量
26- 根據驗收條件估算測試案例
27- 測試 Entity 建立
28- 測試 equals(相等/不等)
29- 測試 hashCode
30- 測試 toString
31→ 預估:5 個測試步驟 2:複雜度確認
目標: 確定最終複雜度等級
確認方法:
1對應 4 個指標到複雜度等級:
2
3職責數量:4 個 → Level 3(複雜)
4程式碼行數:90 行 → Level 3(複雜)
5檔案數量:2 個(1 生產 + 1 測試)→ Level 2(中等)
6測試數量:5 個 → Level 2(中等)
7
8取最高等級:Level 3(複雜)確認檢查清單:
1□ 已計算所有 4 個指標
2□ 已對應每個指標到複雜度等級
3□ 已確定最高複雜度等級
4□ 已記錄評估依據步驟 3:拆分決策
目標: 決定是否拆分以及如何拆分
決策規則:
1Level 1-2 → 可直接建立 Ticket
2 ├─ 無需拆分
3 └─ 直接進入 Phase 2(測試設計)
4
5Level 3 → 評估是否可拆分
6 ├─ 可拆分為 Level 1-2 → 執行拆分
7 └─ 無法拆分 → 勉強接受,加強 Review
8
9Level 4 → 必須拆分
10 ├─ 阻止建立 Ticket
11 ├─ 執行拆分(使用第三章拆分策略)
12 └─ 重新評估每個拆分後的子 TicketLevel 3 拆分評估:
1範例:實作 Book Entity(Level 3)
2
3拆分評估:
41. 是否可拆分為更小 Ticket?
5 → 是,可拆分為兩個 Ticket
6
72. 如何拆分?
8 Ticket A: 定義 Book Entity 欄位和基礎方法
9 - 職責:定義欄位 + equals/hashCode
10 - 行數:~45 行
11 - 檔案:1 個
12 - 測試:3 個
13 → Level 2(中等)
14
15 Ticket B: 補充 Book Entity 完整功能
16 - 職責:toString + 完整測試
17 - 行數:~45 行
18 - 檔案:1 個(修改)
19 - 測試:2 個
20 → Level 1(簡單)
21
223. 拆分後依賴關係?
23 → Ticket B 依賴 Ticket A(B 在 A 完成後執行)
24
254. 拆分價值評估?
26 → 降低風險:兩個 Level 1-2 比一個 Level 3 更易管理
27 → 易於 Review:分兩次 Review,每次範圍更小
28 → 成本:增加一個 Ticket,但風險降低值得
29
30結論:建議拆分Level 4 拆分處理:
1範例:實作完整書籍評分功能(Level 4)
2
3當前狀態:
4
5- 職責:8 個 → Level 4
6- 行數:180 行 → Level 4
7- 檔案:7 個 → Level 4
8- 測試:15 個 → Level 4
9
10拆分決策:必須拆分(無選項)
11
12拆分方式:使用「基於 Clean Architecture 分層拆分策略」(詳見第三章)2.3 評估決策樹
完整決策流程圖:
1[開始評估]
2 ↓
3[計算 4 個指標]
4 ↓
5[取最高複雜度等級]
6 ↓
7 ├─ Level 1(簡單)
8 │ ↓
9 │ [可直接建立 Ticket]
10 │ ↓
11 │ [進入 Phase 2:測試設計]
12 │
13 ├─ Level 2(中等)
14 │ ↓
15 │ [可直接建立 Ticket]
16 │ ↓
17 │ [(可選)評估是否拆分為更小 Ticket]
18 │ ↓
19 │ [進入 Phase 2:測試設計]
20 │
21 ├─ Level 3(複雜)
22 │ ↓
23 │ [評估是否可拆分]
24 │ ↓
25 │ ├─ 可拆分?
26 │ │ ├─ Yes → [執行拆分]
27 │ │ │ ↓
28 │ │ │ [重新評估子 Ticket]
29 │ │ │ ↓
30 │ │ │ [確保所有子 Ticket 為 Level 1-2]
31 │ │ │
32 │ │ └─ No → [勉強接受]
33 │ │ ↓
34 │ │ [標記為高風險 Ticket]
35 │ │ ↓
36 │ │ [加強 Review 機制]
37 │ │ ↓
38 │ │ [進入 Phase 2]
39 │
40 └─ Level 4(超標)
41 ↓
42 [禁止建立 Ticket]
43 ↓
44 [阻止進入 Phase 2]
45 ↓
46 [必須拆分(使用第三章策略)]
47 ↓
48 [重新評估所有子 Ticket]
49 ↓
50 [確保所有子 Ticket ≤ Level 3]決策節點詳細說明:
節點 1:Level 3 拆分評估
1問題:此 Ticket 是否可拆分為更小 Ticket?
2
3評估準則:
41. 職責是否可分離?
5 - 是否包含多個獨立功能點?
6 - 是否可按照 Clean Architecture 分層拆分?
7
82. 拆分後是否降低複雜度?
9 - 拆分後每個子 Ticket 是否 ≤ Level 2?
10 - 是否減少單一 Ticket 的風險?
11
123. 拆分成本是否合理?
13 - 增加的管理成本 vs 降低的風險
14 - 是否需要額外的整合測試?
15
16決策:
17
18- 滿足 1 且 2 → 建議拆分
19- 不滿足 1 或 2,但 3 成本高 → 勉強接受 Level 3節點 2:Level 4 強制拆分
1Level 4 無需評估,必須拆分
2
3拆分方法(按優先順序):
41. 優先:按 Clean Architecture 分層拆分(詳見第三章 3.1-3.4)
52. 次之:按職責拆分(每個職責獨立 Ticket)
63. 最後:按檔案拆分(每個檔案獨立 Ticket)
7
8拆分要求:
9
10- 所有子 Ticket 必須 ≤ Level 3
11- 建議所有子 Ticket ≤ Level 2
12- 理想所有子 Ticket = Level 1
13
14拆分後驗證:
15□ 所有子 Ticket 都已重新評估
16□ 所有子 Ticket 都 ≤ Level 3
17□ 子 Ticket 依賴關係明確
18□ 子 Ticket 總和涵蓋原始 Ticket 所有功能2.4 複雜度評估實例
完整評估案例:
案例 1:簡單 Ticket 評估
1任務:定義 IBookRepository 介面
2
3步驟 1:初步評估
4- 職責數量:1 個(定義介面方法簽名)
5- 程式碼行數:~20 行
6- 涉及檔案:1 個(i_book_repository.dart)
7- 測試數量:0 個(Interface 不需測試)
8
9步驟 2:複雜度確認
10- 職責:1 個 → Level 1
11- 行數:20 行 → Level 1
12- 檔案:1 個 → Level 1
13- 測試:0 個 → Level 1
14→ 最高等級:Level 1
15
16步驟 3:拆分決策
17- Level 1 → 可直接建立 Ticket
18- 無需拆分
19- 進入 Phase 2案例 2:中等 Ticket 評估
1任務:實作 Rating Value Object
2
3步驟 1:初步評估
4- 職責數量:2 個(建立 + 驗證)
5- 程式碼行數:~50 行(含測試)
6- 涉及檔案:1 個(rating.dart)
7- 測試數量:5 個
8
9步驟 2:複雜度確認
10- 職責:2 個 → Level 2
11- 行數:50 行 → Level 2
12- 檔案:1 個 → Level 1
13- 測試:5 個 → Level 2
14→ 最高等級:Level 2
15
16步驟 3:拆分決策
17- Level 2 → 可直接建立 Ticket
18- 評估:可選拆分,但不必要(職責內聚性高)
19- 進入 Phase 2案例 3:複雜 Ticket 評估與拆分
1任務:實作 BookRepository CRUD
2
3步驟 1:初步評估
4- 職責數量:5 個(get + save + delete + mapper + 錯誤處理)
5- 程式碼行數:~160 行(含測試)
6- 涉及檔案:2 個(repository.dart + mapper.dart)
7- 測試數量:10 個
8
9步驟 2:複雜度確認
10- 職責:5 個 → Level 3
11- 行數:160 行 → Level 4(超過 100 行)
12- 檔案:2 個 → Level 2
13- 測試:10 個 → Level 3
14→ 最高等級:Level 4(行數超標)
15
16步驟 3:拆分決策
17- Level 4 → 必須拆分
18- 拆分方式:按 CRUD 方法拆分
19
20 Ticket A: 實作 getBookByIsbn
21 - 職責:2 個(查詢 + 轉換)
22 - 行數:~50 行
23 - 檔案:2 個
24 - 測試:3 個
25 → Level 2
26
27 Ticket B: 實作 saveBook
28 - 職責:2 個(儲存 + 錯誤處理)
29 - 行數:~60 行
30 - 檔案:1 個(修改 repository.dart)
31 - 測試:4 個
32 → Level 2
33
34 Ticket C: 實作 deleteBook
35 - 職責:2 個(刪除 + 錯誤處理)
36 - 行數:~50 行
37 - 檔案:1 個(修改 repository.dart)
38 - 測試:3 個
39 → Level 2
40
41結果:拆分為 3 個 Level 2 Ticket案例 4:God Ticket 評估與拆分
1任務:實作完整書籍評分功能(UI + UseCase + Repository)
2
3步驟 1:初步評估
4- 職責數量:8 個(UI + Controller + UseCase + Entity + Repository + Mapper + 測試 + 整合)
5- 程式碼行數:~300 行
6- 涉及檔案:8 個(跨 4 個架構層級)
7- 測試數量:20 個
8
9步驟 2:複雜度確認
10- 職責:8 個 → Level 4
11- 行數:300 行 → Level 4
12- 檔案:8 個 → Level 4
13- 測試:20 個 → Level 4
14→ 最高等級:Level 4(所有指標都超標)
15
16步驟 3:拆分決策
17- Level 4 → 必須拆分(God Ticket)
18- 拆分方式:使用 Clean Architecture 分層拆分策略
19
20 Ticket 1: Domain 層實作(Layer 5)
21 - Rating Entity + Rating Value Object
22 - 職責:2 個
23 - 行數:~60 行
24 - 檔案:2 個
25 - 測試:6 個
26 → Level 2
27
28 Ticket 2: Repository 層實作(Layer 4-5 Interface + Infra)
29 - IRatingRepository + SQLiteRatingRepository + Mapper
30 - 職責:3 個
31 - 行數:~90 行
32 - 檔案:3 個
33 - 測試:8 個
34 → Level 3(可接受)
35
36 Ticket 3: UseCase 層實作(Layer 3)
37 - RateBookUseCase
38 - 職責:2 個
39 - 行數:~60 行
40 - 檔案:1 個
41 - 測試:4 個
42 → Level 2
43
44 Ticket 4: Presentation 層實作(Layer 1-2)
45 - RatingWidget + RatingController
46 - 職責:2 個
47 - 行數:~90 行
48 - 檔案:2 個
49 - 測試:4 個(Widget 測試)
50 → Level 2
51
52結果:拆分為 4 個 Ticket(3 個 Level 2 + 1 個 Level 3)
53依賴順序:Ticket 1 → Ticket 2 → Ticket 3 → Ticket 4第三章:Clean Architecture 分層拆分策略
為什麼需要基於架構分層拆分
架構分層拆分的核心價值:
單層修改原則(Single Layer Modification Principle)
- 每個 Ticket 專注於單一架構層級
- 降低跨層依賴帶來的複雜度
- 提升程式碼審查效率
依賴方向一致性
- 遵循 Clean Architecture 依賴規則(內層不依賴外層)
- 避免循環依賴
- 確保架構穩定性
測試可獨立性
- 每層有明確的測試策略
- 可獨立測試不依賴其他層
- 簡化 Mock 和 Stub
本章內容結構:
13.1 Clean Architecture 五層架構回顧
2 └── 快速回顧五層架構和依賴規則
33.2 四種標準拆分策略
4 ├── 策略 1: Interface 定義 Ticket
5 ├── 策略 2: 具體實作 Ticket
6 ├── 策略 3: 測試驗證 Ticket
7 └── 策略 4: 整合連接 Ticket
83.3 分層拆分決策指引
9 └── 如何選擇合適的拆分策略
103.4 分層拆分實務案例
11 └── 完整的書籍評分功能拆分範例3.1 Clean Architecture 五層架構回顧
五層架構定義(引用自 Clean Architecture 實作方法論):
1Layer 1 (UI - 最外層)
2├── 職責: 使用者介面元件
3├── 路徑: lib/presentation/widgets/, lib/presentation/pages/
4├── 依賴: Layer 2 (Behavior)
5└── 不依賴: Layer 3-5
6
7Layer 2 (Behavior)
8├── 職責: UI 行為控制(State Management)
9├── 路徑: lib/presentation/controllers/, lib/presentation/providers/
10├── 依賴: Layer 3 (UseCase)
11└── 不依賴: Layer 1, 4-5
12
13Layer 3 (UseCase)
14├── 職責: 業務用例協調
15├── 路徑: lib/application/use_cases/, lib/application/services/
16├── 依賴: Layer 4-5 (Domain)
17└── 不依賴: Layer 1-2
18
19Layer 4 (Domain Events/Interfaces)
20├── 職責: 領域事件和介面定義
21├── 路徑: lib/domain/events/, lib/domain/repositories/ (介面)
22├── 依賴: Layer 5 (Domain Implementation)
23└── 不依賴: Layer 1-3
24
25Layer 5 (Domain Implementation - 最內層)
26├── 職責: 領域模型實作和基礎設施
27├── 路徑: lib/domain/entities/, lib/domain/value_objects/, lib/infrastructure/
28├── 依賴: 無(核心層)
29└── 不依賴: 任何層依賴規則(Dependency Rule):
1外層 → 內層 允許
2內層 → 外層 禁止
3
4範例:
5
6- Layer 2 (Behavior) → Layer 3 (UseCase)
7- Layer 3 (UseCase) → Layer 2 (Behavior)單層修改原則:
- 理想: 每個 Ticket 只修改單一層級
- 可接受: Ticket 修改相鄰兩層(如 Interface + Implementation)
- 禁止: Ticket 跨越超過 2 層(如 UI → Domain 直接跨越)
3.2 四種標準拆分策略
策略 1:Interface 定義 Ticket
定義: 定義一個介面及其輸入輸出契約。
適用層級: 主要用於 Layer 4 (Domain Interfaces)
職責範圍:
- 定義 Interface 簽名
- 定義輸入參數類型
- 定義回傳類型
- 撰寫文檔註解(含業務需求編號)
禁止包含:
- 具體實作邏輯
- 資料庫操作
- 業務邏輯
Ticket 範本:
1## Ticket #NNN: 定義 {Interface 名稱} 介面
2
3### 業務需求
4[引用業務需求編號,如 REQ-001]
5
6### 目標
7建立 `{Interface 名稱}` 介面,定義 {業務功能} 的契約
8
9### 步驟
101. 在 `lib/domain/repositories/` 建立 `{interface_file}.dart`
112. 定義 `{method1}` 方法簽名
123. 定義 `{method2}` 方法簽名
134. 撰寫文檔註解(含需求編號)
14
15### 驗收條件
16- [ ] Interface 檔案建立在正確位置
17- [ ] 所有方法簽名完整且明確
18- [ ] 輸入輸出類型定義清楚
19- [ ] 包含完整的文檔註解(含需求編號)
20- [ ] dart analyze 0 錯誤實務範例:
1## Ticket #101: 定義 IBookRepository 介面
2
3### 業務需求
4REQ-LIB-001: 書籍資料存取功能
5
6### 目標
7建立 `IBookRepository` 介面,定義書籍資料存取的契約
8
9### 步驟
101. 在 `lib/domain/repositories/` 建立 `i_book_repository.dart`
112. 定義 `getBookByIsbn` 方法簽名
12 ```dart
13 /// [REQ-LIB-001.1] 根據 ISBN 查詢書籍
14 ///
15 /// 參數:
16 /// - isbn: 書籍 ISBN 編號
17 ///
18 /// 回傳:
19 /// - Book 物件(存在)或 null(不存在)
20 Future<Book?> getBookByIsbn(String isbn);- 定義
saveBook方法簽名 - 定義
deleteBook方法簽名 - 撰寫文檔註解
驗收條件
- Interface 檔案建立在
lib/domain/repositories/ - 3 個方法簽名完整且明確
- 輸入輸出類型定義清楚
- 包含完整的文檔註解(含需求編號)
- dart analyze 0 錯誤
指標評估
- 職責: 1 個(定義介面)
- 行數: ~25 行
- 檔案: 1 個
- 測試: 0 個(Interface 不需單元測試)
→ Level 1(簡單)
1
2---
3
4#### 策略 2:具體實作 Ticket
5
6**定義**: 實作一個類別的核心邏輯。
7
8**適用層級**:
9- Layer 5 (Domain Implementation): Entity, Value Object
10- Layer 5 (Infrastructure): Repository 實作, Service 實作
11
12**職責範圍**:
13- 實作類別邏輯
14- 實現介面方法
15- 處理異常
16- 撰寫單元測試
17
18**禁止包含**:
19- UI 元件
20- 跨層整合(如直接呼叫 UseCase)
21- 測試以外的其他層修改
22
23**Ticket 範本**:
24
25```markdown
26## Ticket #NNN: 實作 {類別名稱}
27
28### 業務需求
29[引用業務需求編號]
30
31### 目標
32實作 `{類別名稱}`,提供 {業務功能} 的具體實現
33
34### 依賴 Ticket
35- Ticket #XXX: 定義 {Interface 名稱} 介面(必須先完成)
36
37### 步驟
381. 建立 `{類別名稱}` 類別(實作 `{Interface}`)
392. 實作 `{method1}` 方法
403. 實作 `{method2}` 方法
414. 處理異常情況
425. 撰寫單元測試(正常流程 + 異常處理)
436. 確保所有測試通過
44
45### 驗收條件
46- [ ] 實作所有 Interface 方法
47- [ ] 異常處理完整
48- [ ] 單元測試 100% 通過
49- [ ] 測試覆蓋正常流程和異常處理
50- [ ] dart analyze 0 錯誤實務範例:
1## Ticket #102: 實作 SQLiteBookRepository
2
3### 業務需求
4REQ-LIB-001: 書籍資料存取功能
5
6### 目標
7實作 `SQLiteBookRepository`,提供書籍資料的 SQLite 儲存
8
9### 依賴 Ticket
10- Ticket #101: 定義 IBookRepository 介面(必須先完成)
11
12### 步驟
131. 在 `lib/infrastructure/repositories/` 建立 `sqlite_book_repository.dart`
142. 實作 `getBookByIsbn` 方法
15 - SQL 查詢邏輯
16 - Data Mapper 轉換(DTO → Entity)
17 - 錯誤處理(Database Exception)
183. 實作 `saveBook` 方法
19 - SQL 插入/更新邏輯
20 - Data Mapper 轉換(Entity → DTO)
21 - 錯誤處理
224. 實作 `deleteBook` 方法
235. 撰寫單元測試
24 - 正常流程:CRUD 操作成功
25 - 異常處理:資料庫錯誤、資料不存在
266. 確保所有測試通過
27
28### 驗收條件
29- [ ] 實作所有 IBookRepository 方法
30- [ ] 異常處理完整(DatabaseException, ValidationException)
31- [ ] 單元測試 100% 通過(至少 6 個測試案例)
32- [ ] dart analyze 0 錯誤
33
34### 指標評估
35- 職責: 3 個(CRUD 實作、Mapper、異常處理)
36- 行數: ~80 行(含測試)
37- 檔案: 1 個
38- 測試: 6 個
39→ Level 2(中等)策略 3:測試驗證 Ticket
定義: 撰寫一組相關的測試用例,補強現有實作的測試覆蓋率。
適用時機:
- 現有實作缺乏完整測試
- 需要補充邊界測試和異常測試
- TDD 紅綠燈循環中的「紅燈」階段
職責範圍:
- 撰寫單元測試
- 覆蓋正常流程
- 覆蓋邊界條件
- 覆蓋異常處理
- 確保測試通過
禁止包含:
- 修改生產程式碼(除非是修正測試發現的 Bug)
- 新增功能
- 重構(測試 Ticket 專注於驗證,不做重構)
Ticket 範本:
1## Ticket #NNN: 撰寫 {功能} 測試
2
3### 業務需求
4[引用業務需求編號]
5
6### 目標
7撰寫 `{類別名稱}` 的完整測試用例,確保 {業務功能} 正確性
8
9### 依賴 Ticket
10- Ticket #XXX: 實作 {類別名稱}(必須先完成)
11
12### 步驟
131. 建立測試檔案 `{class}_test.dart`
142. 撰寫正常流程測試
153. 撰寫邊界條件測試
164. 撰寫異常處理測試
175. 確保所有測試通過
18
19### 驗收條件
20- [ ] 測試檔案建立在正確位置
21- [ ] 至少 N 個測試用例
22- [ ] 覆蓋正常流程、邊界條件和異常處理
23- [ ] 所有測試 100% 通過實務範例:
1## Ticket #103: 撰寫 BookRepository 整合測試
2
3### 業務需求
4REQ-LIB-001: 書籍資料存取功能
5
6### 目標
7撰寫 `BookRepository` 的整合測試,驗證資料庫操作正確性
8
9### 依賴 Ticket
10- Ticket #102: 實作 SQLiteBookRepository(必須先完成)
11
12### 步驟
131. 建立測試檔案 `test/integration/repositories/book_repository_test.dart`
142. 撰寫 `getBookByIsbn` 測試
15 - 測試 1: 成功取得存在的書籍
16 - 測試 2: 書籍不存在回傳 null
17 - 測試 3: 無效 ISBN 拋出異常
183. 撰寫 `saveBook` 測試
19 - 測試 4: 新增書籍成功
20 - 測試 5: 更新現有書籍成功
214. 撰寫 `deleteBook` 測試
22 - 測試 6: 刪除存在的書籍
23 - 測試 7: 刪除不存在的書籍無異常
245. 確保所有測試通過
25
26### 驗收條件
27- [ ] 測試檔案建立在 `test/integration/repositories/`
28- [ ] 至少 7 個整合測試案例
29- [ ] 覆蓋正常流程和異常處理
30- [ ] 所有測試 100% 通過
31
32### 指標評估
33- 職責: 1 個(撰寫測試)
34- 行數: ~60 行(純測試)
35- 檔案: 1 個(測試檔案)
36- 測試: 7 個
37→ Level 2(中等)策略 4:整合連接 Ticket
定義: 連接兩個模組並驗證整合,實現端到端流程。
適用時機:
- 需要連接 Layer 3 (UseCase) 和 Layer 5 (Repository)
- 需要連接 Layer 2 (Controller) 和 Layer 3 (UseCase)
- 完成分層實作後的整合階段
職責範圍:
- 連接 Use Case 和 Repository
- 實作依賴注入
- 撰寫整合測試
- 驗證端到端流程
禁止包含:
- 修改核心業務邏輯(應在具體實作 Ticket 完成)
- 跨越超過 2 層的整合
- UI 實作(應獨立為 Presentation 層 Ticket)
Ticket 範本:
1## Ticket #NNN: 整合 {UseCase} 到 {Repository}
2
3### 業務需求
4[引用業務需求編號]
5
6### 目標
7將 `{Repository}` 整合到 `{UseCase}`,實現 {業務功能} 完整流程
8
9### 依賴 Ticket
10- Ticket #XXX: 定義 {Interface}(必須先完成)
11- Ticket #YYY: 實作 {Repository}(必須先完成)
12
13### 步驟
141. 修改 `{UseCase}` 注入 `{Interface}`
152. 在 `execute` 方法中呼叫 Repository 方法
163. 處理 Repository 異常
174. 撰寫整合測試驗證端到端流程
185. 確保所有測試通過
19
20### 驗收條件
21- [ ] `{UseCase}` 正確注入 `{Interface}`
22- [ ] 端到端流程正常運作
23- [ ] 整合測試 100% 通過
24- [ ] 異常處理完整實務範例:
1## Ticket #104: 整合 BookRepository 到 GetBookUseCase
2
3### 業務需求
4REQ-LIB-001: 書籍查詢功能
5
6### 目標
7將 `BookRepository` 整合到 `GetBookUseCase`,實現完整書籍查詢流程
8
9### 依賴 Ticket
10- Ticket #101: 定義 IBookRepository 介面(必須先完成)
11- Ticket #102: 實作 SQLiteBookRepository(必須先完成)
12
13### 步驟
141. 修改 `GetBookInteractor` 注入 `IBookRepository`
15 ```dart
16 class GetBookInteractor implements GetBookUseCase {
17 final IBookRepository _repository;
18
19 GetBookInteractor(this._repository);
20
21 @override
22 Future<Book?> execute(String isbn) async {
23 // 呼叫 Repository
24 }
25 }- 在
execute方法中呼叫repository.getBookByIsbn - 處理 Repository 異常(ValidationException, StorageException)
- 撰寫整合測試
- 測試 1: 成功查詢書籍
- 測試 2: 書籍不存在
- 測試 3: Repository 異常處理
- 確保所有測試通過
驗收條件(整合應用案例)
-
GetBookInteractor正確注入IBookRepository - 端到端流程正常運作
- 整合測試 100% 通過(至少 3 個測試案例)
- 異常處理完整
指標評估(整合應用案例)
- 職責: 2 個(注入、整合)
- 行數: ~50 行(含測試)
- 檔案: 1 個(修改 UseCase)
- 測試: 3 個
→ Level 2(中等)
1
2---
3
4### 3.3 分層拆分決策指引
5
6**決策流程圖**:
7
8```text
9[分析 Ticket 涉及的架構層級]
10 ↓
11 ├─ 單層修改?
12 │ ├─ Yes → 選擇對應策略
13 │ │ ├─ Layer 4 Interface → 策略 1: Interface 定義
14 │ │ ├─ Layer 5 Implementation → 策略 2: 具體實作
15 │ │ └─ 補充測試 → 策略 3: 測試驗證
16 │ │
17 │ └─ No → 跨層修改?
18 │ ├─ 相鄰兩層整合 → 策略 4: 整合連接
19 │ └─ 跨越超過 2 層 → 必須拆分為多個 Ticket決策準則:
準則 1: 優先單層修改
1問題: Ticket 是否只修改單一架構層級?
2
3- Yes → 選擇策略 1-3(Interface / 實作 / 測試)
4- No → 評估是否可拆分為多個單層 Ticket準則 2: 相鄰層整合可接受
1問題: 是否為相鄰兩層的整合?
2
3範例:
4
5- Layer 3 (UseCase) + Layer 4-5 (Repository) 可接受
6- Layer 2 (Controller) + Layer 3 (UseCase) 可接受
7- Layer 1 (UI) + Layer 5 (Domain) 禁止(跨越太多層)
8
9決策:
10
11- 相鄰兩層整合 → 策略 4: 整合連接
12- 跨越超過 2 層 → 必須拆分為多個 Ticket準則 3: Interface 先行
1原則: Interface 定義必須先於實作
2
3範例拆分順序:
41. Ticket A: 定義 IBookRepository(策略 1)
52. Ticket B: 實作 SQLiteBookRepository(策略 2)
63. Ticket C: 整合到 GetBookUseCase(策略 4)
7
8依賴關係: Ticket C 依賴 B,B 依賴 A準則 4: 測試獨立或整合
1問題: 測試應該獨立 Ticket 還是整合到實作 Ticket?
2
3決策:
4
5- 實作 Ticket < Level 2 → 整合測試到實作 Ticket
6- 實作 Ticket = Level 2-3 → 可選擇獨立測試 Ticket
7- 測試案例 > 10 個 → 必須獨立測試 Ticket(策略 3)3.4 分層拆分實務案例
完整案例:書籍評分功能實作
原始 God Ticket 分析
1原始任務: 實作完整書籍評分功能(UI + UseCase + Repository)
2
3指標評估:
4- 職責: 8 個
5- 行數: ~300 行
6- 檔案: 8 個
7- 測試: 20 個
8→ Level 4(所有指標都超標)必須拆分分層拆分方案
拆分為 6 個 Ticket(按 Clean Architecture 分層):
Ticket 1: 定義 Rating Domain 模型(Layer 5 - Domain Implementation)
1## Ticket #201: 定義 Rating Value Object
2
3### 層級: Layer 5 (Domain Implementation)
4### 策略: 策略 2(具體實作)
5
6### 業務需求
7REQ-RATING-001: 書籍評分功能
8
9### 目標
10建立 `Rating` Value Object,封裝評分規則(1-5 分)
11
12### 步驟
131. 建立 `lib/domain/value_objects/rating.dart`
142. 實作 Rating 類別
15 - 建構子驗證(1-5 分)
16 - equals / hashCode
17 - toString
183. 撰寫單元測試(正常、邊界、異常)
194. 確保測試通過
20
21### 驗收條件
22- [ ] Rating 類別實作完整
23- [ ] 驗證邏輯正確(1-5 分)
24- [ ] 單元測試 100% 通過(至少 5 個測試)
25- [ ] dart analyze 0 錯誤
26
27### 指標評估
28- 職責: 1 個
29- 行數: ~50 行
30- 檔案: 1 個
31- 測試: 5 個
32→ Level 2(中等)Ticket 2: 定義 Rating Entity(Layer 5 - Domain Implementation)
1## Ticket #202: 定義 Rating Entity
2
3### 層級: Layer 5 (Domain Implementation)
4### 策略: 策略 2(具體實作)
5### 依賴: Ticket #201(Rating Value Object)
6
7### 業務需求
8REQ-RATING-001: 書籍評分功能
9
10### 目標
11建立 `Rating` Entity,包含評分和評論資訊
12
13### 步驟
141. 建立 `lib/domain/entities/rating.dart`
152. 實作 Rating Entity
16 - 欄位: ratingValue (Rating VO), comment (String), userId, bookIsbn
17 - equals / hashCode
183. 撰寫單元測試
194. 確保測試通過
20
21### 驗收條件
22- [ ] Rating Entity 實作完整
23- [ ] 使用 Rating Value Object
24- [ ] 單元測試 100% 通過(至少 3 個測試)
25- [ ] dart analyze 0 錯誤
26
27### 指標評估
28- 職責: 1 個
29- 行數: ~40 行
30- 檔案: 1 個
31- 測試: 3 個
32→ Level 1(簡單)Ticket 3: 定義 IRatingRepository 介面(Layer 4 - Domain Interfaces)
1## Ticket #203: 定義 IRatingRepository 介面
2
3### 層級: Layer 4 (Domain Interfaces)
4### 策略: 策略 1(Interface 定義)
5
6### 業務需求
7REQ-RATING-001: 書籍評分功能
8
9### 目標
10建立 `IRatingRepository` 介面,定義評分資料存取契約
11
12### 步驟
131. 建立 `lib/domain/repositories/i_rating_repository.dart`
142. 定義 `saveRating` 方法簽名
153. 定義 `getRatingsByBookIsbn` 方法簽名
164. 撰寫文檔註解
17
18### 驗收條件
19- [ ] Interface 檔案建立在正確位置
20- [ ] 2 個方法簽名完整
21- [ ] 文檔註解包含需求編號
22- [ ] dart analyze 0 錯誤
23
24### 指標評估
25- 職責: 1 個
26- 行數: ~20 行
27- 檔案: 1 個
28- 測試: 0 個
29→ Level 1(簡單)Ticket 4: 實作 SQLiteRatingRepository(Layer 5 - Infrastructure)
1## Ticket #204: 實作 SQLiteRatingRepository
2
3### 層級: Layer 5 (Infrastructure)
4### 策略: 策略 2(具體實作)
5### 依賴: Ticket #202, #203
6
7### 業務需求
8REQ-RATING-001: 書籍評分功能
9
10### 目標
11實作 `SQLiteRatingRepository`,提供評分資料的 SQLite 儲存
12
13### 步驟
141. 建立 `lib/infrastructure/repositories/sqlite_rating_repository.dart`
152. 實作 `saveRating` 方法
16 - SQL 插入邏輯
17 - Data Mapper 轉換
18 - 錯誤處理
193. 實作 `getRatingsByBookIsbn` 方法
204. 撰寫單元測試(CRUD + 異常)
215. 確保測試通過
22
23### 驗收條件
24- [ ] 實作所有 IRatingRepository 方法
25- [ ] 異常處理完整
26- [ ] 單元測試 100% 通過(至少 6 個測試)
27- [ ] dart analyze 0 錯誤
28
29### 指標評估
30- 職責: 3 個(CRUD、Mapper、異常)
31- 行數: ~90 行
32- 檔案: 1 個
33- 測試: 6 個
34→ Level 3(複雜)可接受Ticket 5: 實作 RateBookUseCase(Layer 3 - UseCase)
1## Ticket #205: 實作 RateBookUseCase
2
3### 層級: Layer 3 (UseCase)
4### 策略: 策略 4(整合連接)
5### 依賴: Ticket #203, #204
6
7### 業務需求
8REQ-RATING-001: 書籍評分功能
9
10### 目標
11實作 `RateBookUseCase`,協調書籍評分業務流程
12
13### 步驟
141. 建立 `lib/application/use_cases/rate_book_use_case.dart`
152. 注入 `IRatingRepository`
163. 實作 `execute` 方法
17 - 建立 Rating Entity
18 - 呼叫 Repository 儲存
19 - 處理異常
204. 撰寫整合測試
215. 確保測試通過
22
23### 驗收條件
24- [ ] UseCase 正確注入 IRatingRepository
25- [ ] 業務流程完整
26- [ ] 整合測試 100% 通過(至少 4 個測試)
27- [ ] 異常處理完整
28- [ ] dart analyze 0 錯誤
29
30### 指標評估
31- 職責: 2 個(協調、整合)
32- 行數: ~60 行
33- 檔案: 1 個
34- 測試: 4 個
35→ Level 2(中等)Ticket 6: 實作 RatingWidget UI(Layer 1-2 - Presentation)
1## Ticket #206: 實作 RatingWidget 和 RatingController
2
3### 層級: Layer 1 (UI) + Layer 2 (Behavior)
4### 策略: 策略 2 + 策略 4(實作 + 整合)
5### 依賴: Ticket #205
6
7### 業務需求
8REQ-RATING-001: 書籍評分功能
9
10### 目標
11實作評分 UI 元件和行為控制
12
13### 步驟
141. 建立 `lib/presentation/widgets/rating_widget.dart`
152. 實作 RatingWidget(5 星評分 UI)
163. 建立 `lib/presentation/controllers/rating_controller.dart`
174. 實作 RatingController
18 - 注入 RateBookUseCase
19 - 呼叫 UseCase 評分
20 - 狀態管理
215. 撰寫 Widget 測試
226. 確保測試通過
23
24### 驗收條件
25- [ ] RatingWidget UI 正確顯示
26- [ ] RatingController 正確整合 UseCase
27- [ ] Widget 測試 100% 通過(至少 4 個測試)
28- [ ] dart analyze 0 錯誤
29
30### 指標評估
31- 職責: 2 個(UI、Controller)
32- 行數: ~80 行
33- 檔案: 2 個
34- 測試: 4 個
35→ Level 2(中等)拆分結果總結
拆分前後對比:
| 項目 | 拆分前(God Ticket) | 拆分後(6 個 Ticket) |
|---|---|---|
| 職責數量 | 8 個 | 平均 1.7 個 |
| 程式碼行數 | ~300 行 | 平均 57 行 |
| 檔案數量 | 8 個 | 平均 1.2 個 |
| 測試數量 | 20 個 | 平均 4 個 |
| 複雜度等級 | Level 4 | 5 個 Level 1-2 1 個 Level 3 |
拆分效益:
- 風險降低: 從 1 個高風險任務 → 6 個低風險任務
- 並行開發: 可 2-3 人同時開發不同層級
- 易於 Review: 每個 PR 範圍小,Review 時間縮短
- 依賴明確: 清楚的 Ticket 依賴順序
- 測試獨立: 每層可獨立測試,Mock 簡單
執行順序:
1Phase 1(可並行):
2├─ Ticket #201: Rating Value Object
3└─ Ticket #202: Rating Entity (依賴 #201)
4
5Phase 2(可並行):
6├─ Ticket #203: IRatingRepository Interface
7└─ Ticket #204: SQLiteRatingRepository (依賴 #202, #203)
8
9Phase 3:
10└─ Ticket #205: RateBookUseCase (依賴 #204)
11
12Phase 4:
13└─ Ticket #206: RatingWidget UI (依賴 #205)第四章:Ticket 大小標準與範例
4.1 簡單 Ticket 標準與範例(Level 1)
Level 1 特徵:
- 職責:1 個明確職責
- 行數:< 30 行
- 檔案:1 個
- 測試:1-3 個
適用場景:
- Interface 定義
- 單一 Value Object
- 單一方法實作
- 簡單配置修改
範例 1:定義 IBookRepository 介面
1## Ticket #1: 定義 IBookRepository 介面
2
3### 層級:Layer 4 (Domain Interfaces)
4
5### 業務需求
6REQ-LIB-001: 書籍資料存取功能
7
8### 目標
9建立 `IBookRepository` 介面,定義書籍資料存取的契約
10
11### 步驟
121. 在 `lib/domain/repositories/` 建立 `i_book_repository.dart`
132. 定義 `getBookByIsbn(String isbn)` 方法簽名
143. 定義 `saveBook(Book book)` 方法簽名
154. 定義 `deleteBook(String isbn)` 方法簽名
165. 撰寫文檔註解(含需求編號)
17
18### 驗收條件
19- [ ] Interface 檔案建立在 `lib/domain/repositories/`
20- [ ] 3 個方法簽名完整且明確
21- [ ] 輸入輸出類型定義清楚
22- [ ] 文檔註解包含需求編號 REQ-LIB-001
23- [ ] dart analyze 0 錯誤
24
25### 指標評估
26- 職責數量:1 個(定義介面契約)
27- 程式碼行數:~20 行
28- 涉及檔案:1 個
29- 測試用例:0 個(Interface 不需單元測試)
30→ Level 1(簡單)
31
32### 預估時間
3310-15 分鐘範例 2:建立 Rating Value Object
1## Ticket #2: 建立 Rating Value Object
2
3### 層級:Layer 5 (Domain Implementation)
4
5### 業務需求
6REQ-RATING-001: 書籍評分功能
7
8### 目標
9建立 `Rating` Value Object,封裝評分規則(1-5 分)
10
11### 步驟
121. 在 `lib/domain/value_objects/` 建立 `rating.dart`
132. 實作 Rating 類別
14 - 建構子驗證(1-5 分範圍)
15 - equals / hashCode
16 - toString
173. 撰寫單元測試
18 - 測試 1:建立有效評分
19 - 測試 2:評分過低拋出異常
20 - 測試 3:評分過高拋出異常
214. 確保所有測試通過
22
23### 驗收條件
24- [ ] Rating 類別實作完整
25- [ ] 驗證邏輯正確(1-5 分)
26- [ ] equals / hashCode / toString 實作正確
27- [ ] 單元測試 100% 通過(至少 3 個測試)
28- [ ] dart analyze 0 錯誤
29
30### 指標評估
31- 職責數量:1 個(實作 Value Object)
32- 程式碼行數:~25 行(含測試)
33- 涉及檔案:1 個
34- 測試用例:3 個
35→ Level 1(簡單)
36
37### 預估時間
3815-20 分鐘4.2 中等 Ticket 標準與範例(Level 2)
Level 2 特徵:
- 職責:2-3 個相關職責
- 行數:30-70 行
- 檔案:2-3 個
- 測試:3-6 個
適用場景:
- 含業務邏輯的 Entity
- 基礎 Repository 方法
- 簡單 UseCase
- 單一功能的 Controller
範例 3:實作 Book Entity
1## Ticket #3: 實作 Book Entity
2
3### 層級:Layer 5 (Domain Implementation)
4
5### 業務需求
6REQ-LIB-001: 書籍資料模型
7
8### 目標
9建立 `Book` Entity,封裝書籍核心資料
10
11### 步驟
121. 在 `lib/domain/entities/` 建立 `book.dart`
132. 定義 Entity 欄位
14 - isbn (String, required)
15 - title (String, required)
16 - author (String, required)
17 - publishDate (DateTime, optional)
183. 實作 equals / hashCode(基於 isbn)
194. 實作 toString
205. 撰寫單元測試
21 - 測試 1:建立有效 Book
22 - 測試 2:equals 正確比較(相同 ISBN)
23 - 測試 3:equals 正確比較(不同 ISBN)
24 - 測試 4:hashCode 一致性
256. 確保測試通過
26
27### 驗收條件
28- [ ] Book Entity 欄位定義完整
29- [ ] equals / hashCode 實作正確(基於 isbn)
30- [ ] toString 回傳清楚的字串表示
31- [ ] 單元測試 100% 通過(至少 4 個測試)
32- [ ] dart analyze 0 錯誤
33
34### 指標評估
35- 職責數量:3 個(欄位定義、equals/hashCode、toString)
36- 程式碼行數:~50 行(含測試)
37- 涉及檔案:1 個
38- 測試用例:4 個
39→ Level 2(中等)
40
41### 預估時間
4225-35 分鐘範例 4:實作 GetBookUseCase
1## Ticket #4: 實作 GetBookUseCase
2
3### 層級:Layer 3 (UseCase)
4
5### 業務需求
6REQ-LIB-001: 查詢書籍功能
7
8### 目標
9實作 `GetBookUseCase`,協調書籍查詢業務流程
10
11### 依賴 Ticket
12- Ticket #1: 定義 IBookRepository 介面(必須先完成)
13
14### 步驟
151. 在 `lib/application/use_cases/` 建立 `get_book_use_case.dart`
162. 定義 `GetBookUseCase` 介面
173. 實作 `GetBookInteractor`
18 - 注入 `IBookRepository`
19 - 實作 `execute(String isbn)` 方法
20 - 處理 Repository 異常
214. 撰寫單元測試
22 - 測試 1:成功查詢書籍
23 - 測試 2:書籍不存在回傳 null
24 - 測試 3:無效 ISBN 拋出 ValidationException
25 - 測試 4:Repository 異常正確處理
265. 確保測試通過
27
28### 驗收條件
29- [ ] GetBookInteractor 正確注入 IBookRepository
30- [ ] execute 方法實作正確
31- [ ] 異常處理完整
32- [ ] 單元測試 100% 通過(至少 4 個測試)
33- [ ] dart analyze 0 錯誤
34
35### 指標評估
36- 職責數量:2 個(業務協調、異常處理)
37- 程式碼行數:~55 行(含測試)
38- 涉及檔案:1 個
39- 測試用例:4 個
40→ Level 2(中等)
41
42### 預估時間
4330-40 分鐘4.3 複雜 Ticket 標準與範例(Level 3)
Level 3 特徵:
- 職責:3-5 個相關職責
- 行數:70-100 行
- 檔案:3-5 個
- 測試:6-10 個
適用場景:
- 完整 Repository CRUD
- 複雜 UseCase(含多重驗證)
- 複雜業務邏輯實作
注意:Level 3 Ticket 建議優先評估是否可拆分為更小 Ticket
範例 5:實作 BookRepository CRUD(建議拆分)
1## Ticket #5: 實作 SQLiteBookRepository CRUD
2
3### 層級:Layer 5 (Infrastructure)
4
5### 業務需求
6REQ-LIB-001: 書籍資料存取功能
7
8### 目標
9實作 `SQLiteBookRepository`,提供完整的 CRUD 操作
10
11### 依賴 Ticket
12- Ticket #1: 定義 IBookRepository 介面(必須先完成)
13
14### 複雜度警告
15此 Ticket 為 Level 3(複雜),建議拆分為 3 個 Level 2 Ticket:
16
17- Ticket A: 實作 getBookByIsbn + 測試
18- Ticket B: 實作 saveBook + 測試
19- Ticket C: 實作 deleteBook + Data Mapper + 測試
20
21### 步驟(如不拆分)
221. 在 `lib/infrastructure/repositories/` 建立 `sqlite_book_repository.dart`
232. 在 `lib/infrastructure/mappers/` 建立 `book_mapper.dart`
243. 實作 `getBookByIsbn` 方法
25 - SQL 查詢邏輯
26 - Data Mapper 轉換(DTO → Entity)
27 - 錯誤處理
284. 實作 `saveBook` 方法
29 - SQL 插入/更新邏輯
30 - Data Mapper 轉換(Entity → DTO)
31 - 錯誤處理
325. 實作 `deleteBook` 方法
336. 撰寫完整測試
34 - getBookByIsbn: 成功、不存在、異常(3 個測試)
35 - saveBook: 新增、更新、異常(3 個測試)
36 - deleteBook: 成功、不存在、異常(3 個測試)
377. 確保所有測試通過
38
39### 驗收條件
40- [ ] 實作所有 IBookRepository 方法
41- [ ] Data Mapper 轉換正確
42- [ ] 異常處理完整
43- [ ] 單元測試 100% 通過(至少 9 個測試)
44- [ ] dart analyze 0 錯誤
45
46### 指標評估
47- 職責數量:5 個(get、save、delete、mapper、異常處理)
48- 程式碼行數:~160 行(含測試)
49- 涉及檔案:2 個
50- 測試用例:9 個
51→ Level 3(複雜)建議拆分
52
53### 預估時間
5460-90 分鐘
55
56### 建議拆分方案
57拆分為 3 個 Ticket(詳見第五章決策樹)4.4 必須拆分標準(Level 4)
Level 4 特徵:
- 職責:> 5 個
- 行數:> 100 行
- 檔案:> 5 個
- 測試:> 10 個
處理方式:
- 禁止建立 Level 4 Ticket
- 必須拆分為多個 Level 1-2 Ticket
- 拆分後可接受少數 Level 3 Ticket
範例 6:God Ticket 檢測與拆分
1## 禁止:實作完整書籍評分功能
2
3### 原始任務描述
4實作書籍評分功能,包含 UI、Controller、UseCase、Repository
5
6### God Ticket 檢測結果
7- 職責數量:8 個 → Level 4
8- 程式碼行數:~300 行 → Level 4
9- 涉及檔案:8 個 → Level 4
10- 測試用例:20 個 → Level 4
11→ **所有指標都超標,必須拆分**
12
13### 拆分決策
14使用「Clean Architecture 分層拆分策略」(詳見第三章 3.4)
15
16拆分為 6 個 Ticket:
171. Ticket #201: Rating Value Object(Level 2)
182. Ticket #202: Rating Entity(Level 1)
193. Ticket #203: IRatingRepository Interface(Level 1)
204. Ticket #204: SQLiteRatingRepository(Level 3)
215. Ticket #205: RateBookUseCase(Level 2)
226. Ticket #206: RatingWidget UI(Level 2)
23
24結果:5 個 Level 1-2 + 1 個 Level 3 全部可接受4.5 Ticket 大小對照表
快速參考表:
| Level | 職責 | 行數 | 檔案 | 測試 | 預估時間 | 範例 |
|---|---|---|---|---|---|---|
| 1 簡單 | 1 | <30 | 1 | 1-3 | 5-20分鐘 | Interface 定義、簡單 VO |
| 2 中等 | 2-3 | 30-70 | 2-3 | 3-6 | 20-40分鐘 | Entity、基礎 UseCase |
| 3 複雜 | 3-5 | 70-100 | 3-5 | 6-10 | 40-90分鐘 | 完整 Repository CRUD |
| 4 超標 | >5 | >100 | >5 | >10 | N/A | 禁止建立 |
決策建議:
- Level 1-2:直接建立 Ticket
- Level 3:優先評估是否可拆分
- Level 4:必須拆分,無例外
第五章:拆分決策樹
5.1 決策樹總覽
完整決策流程:
1[Ticket 設計階段]
2 ↓
3[計算 4 個量化指標]
4 ↓
5[取最高複雜度等級]
6 ↓
7 ├─ Level 1-2
8 │ ↓
9 │ [直接建立 Ticket]
10 │ ↓
11 │ [進入 Phase 2(測試設計)]
12 │
13 ├─ Level 3
14 │ ↓
15 │ [拆分評估決策]
16 │ ↓
17 │ ├─ 可拆分?
18 │ │ ├─ Yes → [執行拆分策略] → [重新評估]
19 │ │ └─ No → [勉強接受] → [標記高風險]
20 │ ↓
21 │ [進入 Phase 2]
22 │
23 └─ Level 4
24 ↓
25 [阻止建立 Ticket]
26 ↓
27 [執行強制拆分]
28 ↓
29 [選擇拆分策略]
30 ↓
31 ├─ 優先:按架構分層拆分
32 ├─ 次之:按職責拆分
33 └─ 最後:按檔案拆分
34 ↓
35 [重新評估所有子 Ticket]
36 ↓
37 [確保所有子 Ticket ≤ Level 3]
38 ↓
39 [可建立 Ticket]5.2 Level 3 拆分評估決策
決策問題:此 Level 3 Ticket 是否應該拆分?
評估準則:
準則 1:職責可分離性
1問題:職責是否可獨立分離為多個 Ticket?
2
3評估方法:
41. 列出所有職責
52. 檢查職責之間的依賴關係
63. 判斷是否可獨立完成
7
8範例:
9原始任務:實作 BookRepository CRUD
10職責分析:
11
12- 職責 1:getBookByIsbn → 可獨立
13- 職責 2:saveBook → 可獨立(依賴職責 1 的測試模式)
14- 職責 3:deleteBook → 可獨立
15- 職責 4:Data Mapper → 可整合到職責 1-3
16- 職責 5:異常處理 → 可整合到職責 1-3
17
18結論:可拆分為 3 個 Ticket(每個職責對應 1 個 Ticket)準則 2:拆分後複雜度降低
1問題:拆分後每個子 Ticket 是否 ≤ Level 2?
2
3評估方法:
4對每個拆分後的子 Ticket 重新計算 4 個指標
5
6範例:
7原始 Ticket(Level 3):
8
9- 職責:5 個
10- 行數:160 行
11- 檔案:2 個
12- 測試:9 個
13
14拆分後 Ticket A:getBookByIsbn
15- 職責:2 個(查詢 + 轉換)
16- 行數:50 行
17- 檔案:2 個
18- 測試:3 個
19→ Level 2
20
21拆分後 Ticket B:saveBook
22- 職責:2 個(儲存 + 異常處理)
23- 行數:60 行
24- 檔案:1 個(修改)
25- 測試:3 個
26→ Level 2
27
28拆分後 Ticket C:deleteBook
29- 職責:2 個(刪除 + 異常處理)
30- 行數:50 行
31- 檔案:1 個(修改)
32- 測試:3 個
33→ Level 2
34
35結論:拆分成功,所有子 Ticket 都降為 Level 2準則 3:拆分成本合理性
1問題:拆分帶來的管理成本是否合理?
2
3成本考量:
4
5- 收益:風險降低、易於 Review、可並行開發
6- 成本:增加 Ticket 數量、需要管理依賴關係
7
8決策:
9
10- 拆分收益 > 拆分成本 → 建議拆分
11- 拆分收益 ≈ 拆分成本 → 可選擇
12- 拆分收益 < 拆分成本 → 不建議拆分
13
14範例:
15原始 Ticket:Level 3(複雜 Repository CRUD)
16拆分收益:
17+ 風險降低:1 個高風險 → 3 個低風險
18+ Review 效率:每次 Review 50-60 行 vs 160 行
19+ 可並行:3 個 Ticket 可依序快速開發
20
21拆分成本:
22
23- 管理成本:需要管理 3 個 Ticket 依賴
24- 整合測試:需要額外的整合測試(但本來就需要)
25
26結論:拆分收益 > 拆分成本,建議拆分5.3 Level 4 強制拆分策略
Level 4 無需評估,必須拆分
拆分策略優先順序
策略 1:按 Clean Architecture 分層拆分(優先)
1適用情況:
2
3- Ticket 跨越多個架構層級(Layer 1-5)
4- 檔案分布在不同層級目錄
5- 涉及 UI、UseCase、Repository 等多層
6
7拆分方法:
81. 按照 Layer 1 → Layer 5 順序分組檔案
92. 每個 Layer 建立獨立 Ticket
103. 相鄰兩層可合併為單一 Ticket(如 Interface + Implementation)
11
12範例(書籍評分功能):
13原始:8 個檔案,跨越 4 層
14拆分後:
15
16- Ticket 1: Layer 5 Domain(Rating VO + Entity)
17- Ticket 2: Layer 5 + 4 Repository(Interface + Impl)
18- Ticket 3: Layer 3 UseCase
19- Ticket 4: Layer 1-2 Presentation(UI + Controller)
20
21結果:4 個 Level 1-2 Ticket策略 2:按職責拆分(次之)
1適用情況:
2
3- 所有檔案在同一層級,但職責過多
4- 單一 Repository 包含過多 CRUD 方法
5- 單一 UseCase 包含過多業務邏輯
6
7拆分方法:
81. 列出所有職責
92. 每個獨立職責建立 Ticket
103. 相關職責可合併(最多 2-3 個職責)
11
12範例(BookRepository CRUD):
13原始:5 個職責(get + save + delete + mapper + error)
14拆分後:
15
16- Ticket A: getBookByIsbn + mapper + error(2-3 職責)
17- Ticket B: saveBook + error(2 職責)
18- Ticket C: deleteBook + error(2 職責)
19
20結果:3 個 Level 2 Ticket策略 3:按檔案拆分(最後)
1適用情況:
2
3- 前兩種策略都無法適用
4- 檔案之間相對獨立
5- 每個檔案本身就是一個完整單元
6
7拆分方法:
81. 每個檔案建立獨立 Ticket
92. 相關檔案可合併(最多 2-3 個檔案)
10
11範例:
12原始:7 個檔案
13拆分後:
14
15- Ticket 1: file1.dart + file2.dart(相關)
16- Ticket 2: file3.dart
17- Ticket 3: file4.dart + file5.dart(相關)
18- Ticket 4: file6.dart + file7.dart(相關)
19
20結果:4 個 Ticket5.4 拆分決策檢查清單
Level 3 拆分評估檢查清單:
1□ 已列出所有職責
2□ 已評估職責可分離性
3□ 已計算拆分後每個子 Ticket 的指標
4□ 已確認所有子 Ticket ≤ Level 2
5□ 已評估拆分成本 vs 收益
6□ 已確定是否拆分的最終決策Level 4 強制拆分檢查清單:
1□ 已識別 Ticket 為 Level 4(任一指標超標)
2□ 已選擇拆分策略(分層 / 職責 / 檔案)
3□ 已執行拆分(列出所有子 Ticket)
4□ 已重新評估所有子 Ticket
5□ 已確認所有子 Ticket ≤ Level 3
6□ 已標記子 Ticket 依賴關係
7□ 已確認子 Ticket 總和涵蓋原始功能第六章:Ticket 拆分檢查清單
6.1 拆分前檢查清單
階段 1:需求理解
1□ 已閱讀完整的業務需求
2□ 已理解 Ticket 的業務目標
3□ 已確認 Ticket 的驗收條件
4□ 已識別所有需要完成的功能點
5□ 已確認與其他 Ticket 的依賴關係階段 2:指標計算
1□ 已列出所有職責(功能點 + 邊界條件 + 異常處理)
2□ 已估算程式碼行數(參考類似任務)
3□ 已列出所有涉及檔案(含新建和修改)
4□ 已估算測試用例數(正常 + 邊界 + 異常)
5□ 已取最高複雜度等級作為最終評估階段 3:複雜度確認
1□ 已確定 Ticket 的複雜度等級(Level 1-4)
2□ 如為 Level 4,已阻止建立並準備拆分
3□ 如為 Level 3,已評估是否拆分
4□ 如為 Level 1-2,已確認可直接建立6.2 拆分過程檢查清單
階段 4:拆分策略選擇
1□ 已分析 Ticket 涉及的架構層級
2□ 已確定拆分策略(分層 / 職責 / 檔案)
3□ 已列出所有拆分後的子 Ticket
4□ 已為每個子 Ticket 撰寫初步描述階段 5:子 Ticket 設計
1□ 每個子 Ticket 都有明確的目標
2□ 每個子 Ticket 都有清楚的步驟
3□ 每個子 Ticket 都有具體的驗收條件
4□ 每個子 Ticket 都標記了層級([Layer X])
5□ 每個子 Ticket 都標記了依賴關係階段 6:依賴關係確認
1□ 已識別所有子 Ticket 之間的依賴
2□ 已確保依賴方向符合 Clean Architecture 規則
3□ 已標記依賴順序(Phase 1 → Phase 2 → ...)
4□ 已確認可並行執行的 Ticket6.3 拆分後驗證清單
階段 7:指標重新評估
1□ 已重新計算每個子 Ticket 的 4 個指標
2□ 已確認所有子 Ticket ≤ Level 3
3□ 已確認大多數子 Ticket ≤ Level 2
4□ 如有 Level 3 子 Ticket,已評估合理性階段 8:完整性驗證
1□ 所有子 Ticket 功能總和 = 原始 Ticket 功能
2□ 沒有遺漏任何功能點
3□ 沒有重複的功能實作
4□ 所有檔案都被包含在某個子 Ticket 中
5□ 所有測試案例都被包含階段 9:品質檢查
1□ 每個子 Ticket 都有業務需求引用
2□ 每個子 Ticket 都有指標評估
3□ 每個子 Ticket 都有預估時間
4□ 每個子 Ticket 的職責明確且不重疊
5□ 每個子 Ticket 的標題包含 [Layer X] 標籤6.4 特殊情況檢查清單
情況 1:無法拆分的 Level 3 Ticket
1□ 已明確記錄為何無法拆分
2□ 已標記為「高風險 Ticket」
3□ 已安排額外的 Code Review
4□ 已增加測試覆蓋率要求(> 90%)
5□ 已準備更長的開發時間情況 2:跨層整合 Ticket
1□ 已確認只涉及相鄰兩層(如 Layer 3 + Layer 4-5)
2□ 已確認符合依賴規則(外層 → 內層)
3□ 已明確標記為「整合 Ticket」
4□ 已包含整合測試驗收條件情況 3:測試獨立 Ticket
1□ 已確認生產程式碼已完成(依賴 Ticket)
2□ Ticket 只包含測試程式碼
3□ 已列出所有測試案例(正常 + 邊界 + 異常)
4□ 已確認測試數量在合理範圍(< 10 個)第七章:實務案例與最佳實踐
7.1 完整案例:書籍搜尋功能
業務需求:
1REQ-SEARCH-001: 實作書籍搜尋功能
2- 使用者可輸入關鍵字搜尋書籍
3- 支援書名、作者、ISBN 搜尋
4- 顯示搜尋結果列表初步評估:識別為 God Ticket
1原始任務分析:
2實作完整書籍搜尋功能(包含 UI、Controller、UseCase、Repository)
3
4指標計算:
5
6- 職責數量:10 個
7 1. SearchBar UI 元件
8 2. SearchResultList UI 元件
9 3. SearchController 狀態管理
10 4. SearchBookUseCase 業務協調
11 5. Repository 查詢方法(書名搜尋)
12 6. Repository 查詢方法(作者搜尋)
13 7. Repository 查詢方法(ISBN 搜尋)
14 8. 錯誤處理
15 9. 載入狀態處理
16 10. 空結果處理
17
18- 程式碼行數:~400 行
19- 涉及檔案:10 個
20- 測試用例:25 個
21
22複雜度評估:
23
24- 職責:10 個 → Level 4
25- 行數:400 行 → Level 4
26- 檔案:10 個 → Level 4
27- 測試:25 個 → Level 4
28
29結論:God Ticket 必須拆分拆分策略:Clean Architecture 分層拆分
第一步:按層級分組檔案
1Layer 5 (Domain + Infrastructure):
2- SearchQuery Value Object
3- IBookRepository.searchByTitle()
4- IBookRepository.searchByAuthor()
5- IBookRepository.searchByIsbn()
6- SQLiteBookRepository 查詢實作
7
8Layer 3 (UseCase):
9- SearchBookUseCase
10
11Layer 2 (Behavior):
12- SearchController
13
14Layer 1 (UI):
15- SearchBar Widget
16- SearchResultList Widget第二步:設計拆分後的 Ticket
Ticket 1: 定義 SearchQuery Value Object(Layer 5)
1## Ticket #301: 定義 SearchQuery Value Object
2
3### 層級:Layer 5 (Domain Implementation)
4### 策略:策略 2(具體實作)
5
6### 業務需求
7REQ-SEARCH-001: 書籍搜尋功能
8
9### 目標
10建立 `SearchQuery` Value Object,封裝搜尋條件驗證
11
12### 步驟
131. 建立 `lib/domain/value_objects/search_query.dart`
142. 實作 SearchQuery 類別
15 - 驗證查詢字串長度(最少 2 個字元)
16 - trim() 處理空白
17 - equals / hashCode
183. 撰寫單元測試
194. 確保測試通過
20
21### 驗收條件
22- [ ] SearchQuery 類別實作完整
23- [ ] 驗證邏輯正確(最少 2 字元)
24- [ ] 單元測試 100% 通過(至少 4 個測試)
25- [ ] dart analyze 0 錯誤
26
27### 指標評估
28- 職責:1 個
29- 行數:~30 行
30- 檔案:1 個
31- 測試:4 個
32→ Level 1(簡單)
33
34### 預估時間
3515-20 分鐘Ticket 2: 擴充 IBookRepository 查詢方法(Layer 4)
1## Ticket #302: 定義 IBookRepository 搜尋方法
2
3### 層級:Layer 4 (Domain Interfaces)
4### 策略:策略 1(Interface 定義)
5
6### 業務需求
7REQ-SEARCH-001: 書籍搜尋功能
8
9### 目標
10在 `IBookRepository` 介面新增搜尋方法簽名
11
12### 步驟
131. 修改 `lib/domain/repositories/i_book_repository.dart`
142. 新增 `searchByTitle(String query)` 方法簽名
153. 新增 `searchByAuthor(String query)` 方法簽名
164. 新增 `searchByIsbn(String query)` 方法簽名
175. 撰寫文檔註解
18
19### 驗收條件
20- [ ] 3 個搜尋方法簽名完整
21- [ ] 回傳類型為 `Future<List<Book>>`
22- [ ] 文檔註解包含需求編號
23- [ ] dart analyze 0 錯誤
24
25### 指標評估
26- 職責:1 個(定義介面)
27- 行數:~15 行
28- 檔案:1 個(修改)
29- 測試:0 個
30→ Level 1(簡單)
31
32### 預估時間
3310 分鐘Ticket 3: 實作 BookRepository 搜尋方法(Layer 5)
1## Ticket #303: 實作 SQLiteBookRepository 搜尋
2
3### 層級:Layer 5 (Infrastructure)
4### 策略:策略 2(具體實作)
5### 依賴:Ticket #301, #302
6
7### 業務需求
8REQ-SEARCH-001: 書籍搜尋功能
9
10### 目標
11實作 `SQLiteBookRepository` 的 3 個搜尋方法
12
13### 步驟
141. 修改 `lib/infrastructure/repositories/sqlite_book_repository.dart`
152. 實作 `searchByTitle` 方法
16 - SQL LIKE 查詢
17 - Data Mapper 轉換
18 - 錯誤處理
193. 實作 `searchByAuthor` 方法
204. 實作 `searchByIsbn` 方法
215. 撰寫單元測試(每個方法 2-3 個測試)
226. 確保測試通過
23
24### 驗收條件
25- [ ] 3 個搜尋方法實作完整
26- [ ] SQL 查詢使用 LIKE 模糊搜尋
27- [ ] 異常處理完整
28- [ ] 單元測試 100% 通過(至少 8 個測試)
29- [ ] dart analyze 0 錯誤
30
31### 指標評估
32- 職責:4 個(3 個搜尋方法 + 異常處理)
33- 行數:~95 行
34- 檔案:1 個(修改)
35- 測試:8 個
36→ Level 3(複雜)可接受
37(註:3 個搜尋方法邏輯相似,整合實作較有效率)
38
39### 預估時間
4060-75 分鐘Ticket 4: 實作 SearchBookUseCase(Layer 3)
1## Ticket #304: 實作 SearchBookUseCase
2
3### 層級:Layer 3 (UseCase)
4### 策略:策略 4(整合連接)
5### 依賴:Ticket #302, #303
6
7### 業務需求
8REQ-SEARCH-001: 書籍搜尋功能
9
10### 目標
11實作 `SearchBookUseCase`,協調搜尋業務流程
12
13### 步驟
141. 建立 `lib/application/use_cases/search_book_use_case.dart`
152. 注入 `IBookRepository`
163. 實作 `execute(SearchQuery query, SearchType type)` 方法
17 - 根據 SearchType 呼叫對應的 Repository 方法
18 - 處理空結果
19 - 處理異常
204. 撰寫整合測試
215. 確保測試通過
22
23### 驗收條件
24- [ ] UseCase 正確注入 IBookRepository
25- [ ] 支援 3 種搜尋類型(Title / Author / ISBN)
26- [ ] 整合測試 100% 通過(至少 5 個測試)
27- [ ] 異常處理完整
28- [ ] dart analyze 0 錯誤
29
30### 指標評估
31- 職責:2 個(業務協調、異常處理)
32- 行數:~65 行
33- 檔案:1 個
34- 測試:5 個
35→ Level 2(中等)
36
37### 預估時間
3835-45 分鐘Ticket 5: 實作 SearchController(Layer 2)
1## Ticket #305: 實作 SearchController
2
3### 層級:Layer 2 (Behavior)
4### 策略:策略 4(整合連接)
5### 依賴:Ticket #304
6
7### 業務需求
8REQ-SEARCH-001: 書籍搜尋功能
9
10### 目標
11實作 `SearchController`,管理搜尋狀態
12
13### 步驟
141. 建立 `lib/presentation/controllers/search_controller.dart`
152. 注入 `SearchBookUseCase`
163. 實作狀態管理
17 - isLoading(載入中)
18 - searchResults(搜尋結果)
19 - errorMessage(錯誤訊息)
204. 實作 `search(String query, SearchType type)` 方法
215. 撰寫單元測試
226. 確保測試通過
23
24### 驗收條件
25- [ ] SearchController 正確注入 UseCase
26- [ ] 狀態管理正確(loading / success / error)
27- [ ] 單元測試 100% 通過(至少 5 個測試)
28- [ ] dart analyze 0 錯誤
29
30### 指標評估
31- 職責:2 個(狀態管理、UseCase 整合)
32- 行數:~70 行
33- 檔案:1 個
34- 測試:5 個
35→ Level 2(中等)
36
37### 預估時間
3840 分鐘Ticket 6: 實作搜尋 UI 元件(Layer 1)
1## Ticket #306: 實作 SearchBar 和 SearchResultList
2
3### 層級:Layer 1 (UI)
4### 策略:策略 2(具體實作)
5### 依賴:Ticket #305
6
7### 業務需求
8REQ-SEARCH-001: 書籍搜尋功能
9
10### 目標
11實作搜尋介面的 UI 元件
12
13### 步驟
141. 建立 `lib/presentation/widgets/search_bar.dart`
152. 實作 SearchBar Widget
16 - TextField 輸入框
17 - SearchType 選擇器
18 - 搜尋按鈕
193. 建立 `lib/presentation/widgets/search_result_list.dart`
204. 實作 SearchResultList Widget
21 - 顯示搜尋結果
22 - 載入中狀態
23 - 空結果提示
24 - 錯誤提示
255. 撰寫 Widget 測試
266. 確保測試通過
27
28### 驗收條件
29- [ ] SearchBar UI 正確顯示和互動
30- [ ] SearchResultList 正確顯示各種狀態
31- [ ] Widget 測試 100% 通過(至少 6 個測試)
32- [ ] dart analyze 0 錯誤
33
34### 指標評估
35- 職責:2 個(SearchBar、SearchResultList)
36- 行數:~90 行
37- 檔案:2 個
38- 測試:6 個
39→ Level 2(中等)
40
41### 預估時間
4250-60 分鐘拆分結果總結
拆分前後對比:
| 項目 | 拆分前(God Ticket) | 拆分後(6 個 Ticket) |
|---|---|---|
| 職責數量 | 10 個 | 平均 1.8 個 |
| 程式碼行數 | ~400 行 | 平均 61 行 |
| 檔案數量 | 10 個 | 平均 1.2 個 |
| 測試數量 | 25 個 | 平均 4.7 個 |
| 複雜度等級 | Level 4 | 5 個 Level 1-2 1 個 Level 3 |
| 預估總時間 | N/A(無法估算) | 210-250 分鐘 |
拆分效益分析:
風險大幅降低:
- 從 1 個無法管理的 God Ticket → 6 個可控 Ticket
- 單一 Ticket 失敗不影響其他 Ticket
支援並行開發:
- Phase 1: Ticket #301, #302 可並行
- Phase 2: Ticket #303 執行
- Phase 3: Ticket #304 執行
- Phase 4: Ticket #305, #306 可部分並行
Review 效率提升:
- 每次 Review 平均 61 行(vs 400 行)
- Review 時間縮短 85%
測試獨立性:
- 每層可獨立測試
- Mock 依賴簡單明確
7.2 常見錯誤與解決方案
錯誤 1:過度拆分
問題描述:
1將 Level 2(中等)Ticket 拆分為多個 Level 1 Ticket,
2反而增加管理成本且沒有實質收益。
3
4範例:
5原始 Ticket(Level 2):實作 Rating Value Object
6- 職責:2 個(建立 + 驗證)
7- 行數:50 行
8- 測試:5 個
9
10過度拆分為 2 個 Ticket:
11
12- Ticket A:實作 Rating 建構子(Level 1,25 行)
13- Ticket B:實作 Rating 驗證邏輯(Level 1,25 行)
14
15問題:
16
17- 兩個 Ticket 高度耦合,必須連續執行
18- 增加了額外的管理成本
19- 沒有實質的風險降低解決方案:
1保持原始 Level 2 Ticket 不拆分
2
3判斷準則:
4
5- Level 1-2 Ticket 通常不需要拆分
6- 只有 Level 3-4 才需要評估拆分
7- 拆分後的子 Ticket 應該相對獨立錯誤 2:拆分後依賴過於複雜
問題描述:
1拆分方式導致依賴關係過於複雜,
2難以理解和管理執行順序。
3
4範例:
5拆分為 8 個 Ticket,依賴關係如下:
6Ticket 1 → Ticket 2 → Ticket 4
7Ticket 1 → Ticket 3 → Ticket 5
8Ticket 2 → Ticket 6
9Ticket 3 → Ticket 6
10Ticket 5 → Ticket 7
11Ticket 6 → Ticket 7 → Ticket 8
12
13問題:
14
15- 依賴網絡複雜,難以追蹤
16- 執行順序不明確
17- 容易遺漏依賴解決方案:
1使用線性或樹狀依賴結構
2
3優化後的依賴關係:
4Phase 1(並行):
5├─ Ticket 1, Ticket 2
6
7Phase 2(並行):
8├─ Ticket 3, Ticket 4(依賴 Phase 1)
9
10Phase 3:
11└─ Ticket 5(依賴 Phase 2)
12
13Phase 4:
14└─ Ticket 6(依賴 Phase 3)
15
16判斷準則:
17
18- 優先使用 Phase 分組(線性依賴)
19- 同 Phase 內的 Ticket 可並行
20- 避免跨 Phase 的複雜依賴錯誤 3:拆分邊界不清晰
問題描述:
1拆分後的子 Ticket 職責重疊或邊界模糊,
2導致實作時不知道該在哪個 Ticket 完成。
3
4範例:
5原始:實作完整 BookRepository
6拆分後:
7
8- Ticket A:實作 getBookByIsbn
9- Ticket B:實作 Data Mapper
10- Ticket C:實作異常處理
11
12問題:
13
14- Ticket A 需要 Data Mapper(依賴 Ticket B)
15- Ticket A 需要異常處理(依賴 Ticket C)
16- 但 Ticket A 應該先完成?
17- 邊界模糊,無法獨立完成解決方案:
1確保每個子 Ticket 可獨立完成
2
3正確拆分:
4
5- Ticket A:實作 getBookByIsbn(含 Mapper + 異常處理)
6- Ticket B:實作 saveBook(含 Mapper + 異常處理)
7- Ticket C:實作 deleteBook(含異常處理)
8
9判斷準則:
10
11- 每個子 Ticket 應該是「最小可交付單元」
12- 避免將共用邏輯獨立為 Ticket
13- 共用邏輯應整合到使用它的 Ticket 中7.3 拆分最佳實踐
實踐 1:優先選擇架構分層拆分
原則:
1當 Ticket 跨越多個架構層級時,
2優先按照 Clean Architecture 分層拆分。
3
4優點:
5依賴方向清晰(內層不依賴外層)
6測試策略明確(每層有標準測試方法)
7可並行開發(不同層可由不同開發者負責)
8符合單一職責原則範例:
1原始任務:實作書籍評分功能(跨越 4 層)
2
3錯誤:按功能模組拆分
4- Ticket A:評分輸入功能(UI + Controller + UseCase)
5- Ticket B:評分儲存功能(Repository + Mapper)
6
7問題:
8
9- Ticket A 跨越 3 層,依賴關係複雜
10- 測試困難(需要 Mock 多層)
11
12正確:按架構分層拆分
13- Ticket 1:Rating Domain 模型(Layer 5)
14- Ticket 2:IRatingRepository + Impl(Layer 4-5)
15- Ticket 3:RateBookUseCase(Layer 3)
16- Ticket 4:RatingController + Widget(Layer 1-2)
17
18優點:
19
20- 每個 Ticket 職責單一
21- 依賴方向清楚
22- 測試獨立簡單實踐 2:保持線性依賴順序
原則:
1優先設計線性或樹狀依賴結構,
2避免複雜的網狀依賴關係。
3
4建議結構:
5線性依賴:Ticket 1 → Ticket 2 → Ticket 3 → Ticket 4
6樹狀依賴:
7 Ticket 1
8 ├─ Ticket 2
9 │ └─ Ticket 4
10 └─ Ticket 3
11 └─ Ticket 5
12
13避免結構:
14網狀依賴:多個交叉依賴,難以追蹤實施方法:
1步驟 1:識別「核心 Ticket」
2- Domain 層 Ticket 通常是核心
3- 其他層依賴 Domain 層
4
5步驟 2:按照 Clean Architecture 依賴方向排序
6- Layer 5 → Layer 4 → Layer 3 → Layer 2 → Layer 1
7
8步驟 3:標記 Phase
9- Phase 1:Layer 5(Domain)
10- Phase 2:Layer 4-5(Repository)
11- Phase 3:Layer 3(UseCase)
12- Phase 4:Layer 1-2(Presentation)實踐 3:每個 Ticket 包含完整測試
原則:
1每個 Ticket 應該包含對應的測試,
2不要將測試獨立為單獨 Ticket(除非測試數量 > 10)。
3
4優點:
5TDD 紅綠燈循環完整
6每個 Ticket 交付時就是可測試的
7避免「實作完成但測試缺失」的情況實施方法:
1在 Ticket 驗收條件中明確測試要求:
2
3範例:
4## Ticket #X: 實作 Rating Value Object
5
6### 驗收條件
7- [ ] Rating 類別實作完整
8- [ ] 單元測試 100% 通過(至少 3 個測試)
9 - 測試 1:建立有效評分
10 - 測試 2:評分過低拋出異常
11 - 測試 3:評分過高拋出異常
12- [ ] 測試覆蓋率 > 90%
13- [ ] dart analyze 0 錯誤
14
15例外情況(測試獨立 Ticket):
16
17- 測試用例 > 10 個(測試複雜度 Level 4)
18- 補充現有程式碼的測試(非新功能)
19- 整合測試(跨多層驗證)實踐 4:明確標記層級和策略
原則:
1每個 Ticket 標題應包含:
21. [Layer X] 標籤(標示架構層級)
32. 策略類型(Interface / 實作 / 整合)
4
5格式:
6## Ticket #NNN: [Layer X] {動詞} {目標}
7
8範例:
9好的標題:
10
11- [Layer 4] 定義 IBookRepository 介面
12- [Layer 5] 實作 SQLiteBookRepository
13- [Layer 3] 實作 GetBookUseCase
14
15不好的標題:
16
17- 書籍資料存取功能(沒有層級、目標不明確)
18- 實作 Repository(沒有層級、範圍不清楚)實施方法:
1在 Ticket 範本中包含:
2
3### 層級:Layer X (層級名稱)
4### 策略:策略 N(策略名稱)
5
6範例:
7## Ticket #101: [Layer 4] 定義 IBookRepository 介面
8
9### 層級:Layer 4 (Domain Interfaces)
10### 策略:策略 1(Interface 定義)
11
12### 目標
13建立 `IBookRepository` 介面,定義書籍資料存取的契約
14
15這樣可以:
16快速識別 Ticket 的架構位置
17理解 Ticket 的目的(定義 / 實作 / 整合)
18追蹤依賴關係(內層 → 外層)附錄 A:術語表
| 術語 | 英文 | 定義 | 範例 |
|---|---|---|---|
| 職責 | Responsibility | Ticket 需要完成的獨立功能點或邊界條件 | 定義介面、實作方法、處理異常 |
| 複雜度等級 | Complexity Level | 基於 4 個量化指標計算的任務複雜度(Level 1-4) | Level 2(中等) |
| God Ticket | God Ticket | 範圍過大、無法管理的任務(Level 4) | 包含 8 個檔案、300 行程式碼的任務 |
| 單層修改原則 | Single Layer Modification | 每個 Ticket 應只修改單一架構層級 | Ticket 只修改 Domain 層 |
| 依賴規則 | Dependency Rule | Clean Architecture 的依賴方向規則(外層→內層) | UseCase 依賴 Repository Interface |
| 最小可交付單元 | Minimum Deliverable Unit | 可獨立完成、測試、交付的最小功能單元 | 實作單一 Value Object |
| 指標整合評估 | Integrated Indicator Assessment | 取 4 個指標中最高的複雜度等級 | max(Level 2, Level 3, Level 1, Level 2) = Level 3 |
| 拆分策略 | Splitting Strategy | 將大任務拆分為小任務的標準方法 | 按架構分層拆分 |
| Phase | Phase | 依賴順序的執行階段 | Phase 1(Domain 層)→ Phase 2(Repository 層) |
附錄 B:拆分決策模板
模板用途:用於記錄 Ticket 拆分的決策過程
1# Ticket 拆分決策記錄
2
3## 基本資訊
4- **原始任務**:[任務描述]
5- **業務需求**:[需求編號]
6- **評估日期**:YYYY-MM-DD
7- **評估人員**:[姓名]
8
9## 指標評估
10
11### 職責數量
12- **職責列表**:
13 1. [職責 1]
14 2. [職責 2]
15 ...
16- **總計**:X 個
17- **等級**:Level X
18
19### 程式碼行數
20- **預估行數**:X 行
21- **依據**:[參考類似任務或經驗估算]
22- **等級**:Level X
23
24### 涉及檔案
25- **檔案列表**:
26 1. [檔案路徑 1]
27 2. [檔案路徑 2]
28 ...
29- **總計**:X 個
30- **等級**:Level X
31
32### 測試用例數
33- **測試列表**:
34 1. [測試案例 1]
35 2. [測試案例 2]
36 ...
37- **總計**:X 個
38- **等級**:Level X
39
40## 複雜度結論
41- **最高等級**:Level X
42- **最終判定**:[簡單 / 中等 / 複雜 / 必須拆分]
43
44## 拆分決策
45
46### 決策結果
47- [ ] 不拆分(Level 1-2)
48- [ ] 評估拆分(Level 3)
49- [ ] 強制拆分(Level 4)
50
51### 拆分原因(如適用)
52[說明為什麼需要拆分]
53
54### 拆分策略(如適用)
55- [ ] 按架構分層拆分(優先)
56- [ ] 按職責拆分(次之)
57- [ ] 按檔案拆分(最後)
58
59## 拆分方案(如適用)
60
61### 子 Ticket 列表
621. **Ticket A**:[標題]
63 - 層級:Layer X
64 - 指標:職責 X、行數 X、檔案 X、測試 X
65 - 等級:Level X
66
672. **Ticket B**:[標題]
68 - 層級:Layer X
69 - 指標:職責 X、行數 X、檔案 X、測試 X
70 - 等級:Level X
71
72### 依賴關係
73```text
74[依賴關係圖或順序說明]執行順序
- Phase 1:[Ticket A, Ticket B]
- Phase 2:[Ticket C]
- …
驗證清單
拆分前檢查
- 已計算所有 4 個指標
- 已確定複雜度等級
- 已確定是否拆分
拆分後檢查(如適用)
- 所有子 Ticket 已設計
- 所有子 Ticket ≤ Level 3
- 依賴關係明確
- 功能完整性確認
備註
[其他需要記錄的資訊]
1
2---
3
4## 附錄 C:與其他方法論的整合指引
5
6### C.1 與 TDD 四階段方法論整合
7
8**整合點**:
9
10```markdown
11本方法論位於 TDD Phase 1(設計階段)
12
13TDD 流程整合:
14Phase 1: Ticket 設計
15 ↓
16 使用本方法論評估 Ticket 複雜度
17 ↓
18 如為 Level 3-4,執行拆分
19 ↓
20 所有 Ticket 確認為 Level 1-2
21 ↓
22Phase 2: 測試設計(sage-test-architect)
23 ↓
24Phase 3: 實作(pepper → parsley)
25 ↓
26Phase 4: 重構(cinnamon-refactor-owl)協作方式:
- Phase 1 完成時,所有 Ticket 都應該 ≤ Level 3
- 理想狀態:所有 Ticket 都是 Level 1-2
- 如有 Level 3 Ticket,應標記為「需要額外 Review」
C.2 與層級隔離派工方法論整合
整合點:
1本方法論提供「拆分標準」
2層級隔離派工方法論提供「派工策略」
3
4協作流程:
51. 使用本方法論拆分 Ticket(按架構分層)
62. 使用層級隔離派工方法論決定執行順序
7 - 優先派發內層 Ticket(Layer 5)
8 - 逐步派發外層 Ticket(Layer 1)
93. 確保依賴順序符合 Clean Architecture 規則範例:
1拆分結果(本方法論):
2
3- Ticket 1: Layer 5 Domain
4- Ticket 2: Layer 4-5 Repository
5- Ticket 3: Layer 3 UseCase
6- Ticket 4: Layer 1-2 Presentation
7
8派工順序(層級隔離派工方法論):
9Week 1:
10 ├─ 派發 Ticket 1(Layer 5)給 Developer A
11 ├─ Ticket 1 完成後,派發 Ticket 2(Layer 4-5)給 Developer A
12
13Week 2:
14 ├─ Ticket 2 完成後,派發 Ticket 3(Layer 3)給 Developer B
15 └─ 可同時派發 Ticket 4(Layer 1-2)給 Developer C(部分並行)C.3 與 Code Smell 品質閘門檢測方法論整合
整合點:
1本方法論在 Ticket 設計階段執行(Design Time)
2Code Smell 品質閘門在 Ticket 執行後檢測(Runtime)
3
4協作流程:
5設計階段(本方法論):
6 ├─ 計算 4 個指標
7 ├─ 評估複雜度等級
8 ├─ 決定是否拆分
9 └─ 確保 Ticket ≤ Level 3
10
11執行階段(Code Smell 檢測):
12 ├─ C1 檢測:God Ticket(檔案數、層級跨度)
13 ├─ C2 檢測:Incomplete Ticket(缺失元素)
14 └─ C3 檢測:Ambiguous Responsibility(職責不明)
15
16如果 Code Smell 檢測失敗:
17 ├─ C1 失敗 → 返回本方法論重新拆分
18 ├─ C2 失敗 → 補充缺失元素
19 └─ C3 失敗 → 明確職責定義防範措施:
1如果在設計階段使用本方法論:
2C1 God Ticket 失敗率應該降至 0%
3C3 Ambiguous Responsibility 失敗率應該降至接近 0%
4C2 Incomplete Ticket 仍需要 Code Smell 檢測補強C.4 與 Clean Architecture 實作方法論整合
整合點:
1本方法論的「架構分層拆分策略」(第三章)
2完全基於 Clean Architecture 實作方法論的五層架構。
3
4依賴關係:
5本方法論 → 依賴 Clean Architecture 實作方法論
6- 採用相同的五層架構定義
7- 遵循相同的依賴規則
8- 使用相同的檔案路徑規範參考章節對應:
1本方法論第三章「Clean Architecture 分層拆分策略」
2↓ 引用自
3Clean Architecture 實作方法論:
4
5- 第一章:五層架構定義
6- 第二章:依賴規則
7- 第三章:檔案組織規範總結
方法論核心價值
本方法論提供了量化、客觀、可執行的 Ticket 拆分標準,解決了傳統拆分方法的主觀性和不一致性問題。
快速開始指引
第一步:評估 Ticket 複雜度
11. 計算 4 個指標(職責、行數、檔案、測試)
22. 取最高複雜度等級
33. 判斷是否需要拆分第二步:執行拆分(如需要)
11. 選擇拆分策略(優先:架構分層)
22. 設計子 Ticket
33. 重新評估每個子 Ticket
44. 確認所有子 Ticket ≤ Level 3第三步:驗證和記錄
11. 使用檢查清單驗證(第六章)
22. 記錄決策過程(附錄 B 模板)
33. 進入 TDD Phase 2(測試設計)相關方法論引用
完整的 Ticket 設計派工流程請參考:
- Ticket 設計派工方法論 - 主方法論
- Clean Architecture 實作方法論 - 架構基礎
- 層級隔離派工方法論 - 派工策略
- TDD 四階段方法論 - 開發流程