dev-playbooks-cn 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +466 -0
- package/bin/devbooks.js +987 -0
- package/package.json +43 -0
- package/skills/Skills/344/275/277/347/224/250/350/257/264/346/230/216.md +446 -0
- package/skills/Skill/345/274/200/345/217/221/346/214/207/345/215/227.md +248 -0
- package/skills/_shared/context-detection-template.md +315 -0
- package/skills/_shared/mcp-enhancement-template.md +144 -0
- package/skills/_shared/references//351/200/232/347/224/250/345/256/210/351/227/250/345/215/217/350/256/256.md +114 -0
- package/skills/_template/config-discovery-template.md +126 -0
- package/skills/devbooks-brownfield-bootstrap/SKILL.md +167 -0
- package/skills/devbooks-brownfield-bootstrap/references//344/273/243/347/240/201/345/257/274/350/210/252/347/255/226/347/225/245.md +203 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226.md +96 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226/346/217/220/347/244/272/350/257/215.md +115 -0
- package/skills/devbooks-brownfield-bootstrap/references//346/234/257/350/257/255/350/241/250/346/250/241/346/235/277.md +42 -0
- package/skills/devbooks-brownfield-bootstrap/scripts/cod-update.sh +357 -0
- package/skills/devbooks-brownfield-bootstrap/templates/project-profile-template.md +172 -0
- package/skills/devbooks-c4-map/SKILL.md +151 -0
- package/skills/devbooks-c4-map/references/C4/346/236/266/346/236/204/345/234/260/345/233/276/346/217/220/347/244/272/350/257/215.md +33 -0
- package/skills/devbooks-c4-map/references//345/210/206/345/261/202/347/272/246/346/235/237/346/243/200/346/237/245/346/270/205/345/215/225.md +185 -0
- package/skills/devbooks-code-review/SKILL.md +175 -0
- package/skills/devbooks-code-review/references/PR/346/250/241/346/235/277/344/270/216/346/214/207/345/215/227.md +321 -0
- package/skills/devbooks-code-review/references//344/273/243/347/240/201/350/257/204/345/256/241/346/217/220/347/244/272/350/257/215.md +100 -0
- package/skills/devbooks-code-review/references//345/235/217/345/221/263/351/201/223/351/200/237/346/237/245/350/241/250.md +495 -0
- package/skills/devbooks-code-review/references//350/265/204/346/272/220/347/256/241/347/220/206/345/256/241/346/237/245/346/270/205/345/215/225.md +311 -0
- package/skills/devbooks-coder/SKILL.md +219 -0
- package/skills/devbooks-coder/references//344/273/243/347/240/201/345/256/236/347/216/260/346/217/220/347/244/272/350/257/215.md +70 -0
- package/skills/devbooks-coder/references//344/275/216/351/243/216/351/231/251/346/224/271/345/212/250/346/212/200/346/234/257.md +275 -0
- package/skills/devbooks-coder/references//346/227/245/345/277/227/350/247/204/350/214/203.md +329 -0
- package/skills/devbooks-coder/references//347/274/226/347/240/201/351/243/216/346/240/274/347/273/206/345/210/231.md +351 -0
- package/skills/devbooks-coder/references//351/224/231/350/257/257/347/240/201/350/247/204/350/214/203.md +463 -0
- package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
- package/skills/devbooks-delivery-workflow/references//344/272/244/344/273/230/351/252/214/346/224/266/345/267/245/344/275/234/346/265/201.md +256 -0
- package/skills/devbooks-delivery-workflow/references//345/216/237/345/236/213-/347/224/237/344/272/247/345/217/214/350/275/250/346/250/241/345/274/217.md +168 -0
- package/skills/devbooks-delivery-workflow/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +133 -0
- package/skills/devbooks-delivery-workflow/scripts/ac-trace-check.sh +330 -0
- package/skills/devbooks-delivery-workflow/scripts/audit-scope.sh +262 -0
- package/skills/devbooks-delivery-workflow/scripts/change-check.sh +1040 -0
- package/skills/devbooks-delivery-workflow/scripts/change-codemod-scaffold.sh +135 -0
- package/skills/devbooks-delivery-workflow/scripts/change-evidence.sh +152 -0
- package/skills/devbooks-delivery-workflow/scripts/change-scaffold.sh +442 -0
- package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +136 -0
- package/skills/devbooks-delivery-workflow/scripts/constitution-check.sh +237 -0
- package/skills/devbooks-delivery-workflow/scripts/env-match-check.sh +128 -0
- package/skills/devbooks-delivery-workflow/scripts/fitness-check.sh +387 -0
- package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +519 -0
- package/skills/devbooks-delivery-workflow/scripts/handoff-check.sh +141 -0
- package/skills/devbooks-delivery-workflow/scripts/hygiene-check.sh +340 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-from-openspec.sh +385 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-to-v2-gates.sh +202 -0
- package/skills/devbooks-delivery-workflow/scripts/progress-dashboard.sh +319 -0
- package/skills/devbooks-delivery-workflow/scripts/prototype-promote.sh +341 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-preview.sh +203 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-promote.sh +118 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-rollback.sh +124 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-stage.sh +117 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-all.sh +78 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-npm-package.sh +123 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-openspec-free.sh +81 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-slash-commands.sh +146 -0
- package/skills/devbooks-delivery-workflow/templates/handoff.md +50 -0
- package/skills/devbooks-design-backport/SKILL.md +73 -0
- package/skills/devbooks-design-backport/references//345/233/236/345/206/231/350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +196 -0
- package/skills/devbooks-design-doc/SKILL.md +121 -0
- package/skills/devbooks-design-doc/references//345/276/256/346/234/215/345/212/241/350/256/276/350/256/241/346/270/205/345/215/225.md +149 -0
- package/skills/devbooks-design-doc/references//350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +189 -0
- package/skills/devbooks-design-doc/references//351/232/220/347/247/201/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +240 -0
- package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
- package/skills/devbooks-entropy-monitor/references//347/206/265/345/272/246/351/207/217/346/226/271/346/263/225/350/256/272.md +223 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-measure.sh +449 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-report.sh +303 -0
- package/skills/devbooks-entropy-monitor/templates/thresholds.json +99 -0
- package/skills/devbooks-federation/SKILL.md +264 -0
- package/skills/devbooks-federation/scripts/federation-check.sh +144 -0
- package/skills/devbooks-federation/templates/federation.yaml +89 -0
- package/skills/devbooks-impact-analysis/SKILL.md +135 -0
- package/skills/devbooks-impact-analysis/references//345/275/261/345/223/215/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.md +82 -0
- package/skills/devbooks-impact-analysis/scripts/graph-cache.sh +214 -0
- package/skills/devbooks-implementation-plan/SKILL.md +83 -0
- package/skills/devbooks-implementation-plan/references//347/274/226/347/240/201/350/256/241/345/210/222/346/217/220/347/244/272/350/257/215.md +99 -0
- package/skills/devbooks-index-bootstrap/SKILL.md +240 -0
- package/skills/devbooks-proposal-author/SKILL.md +83 -0
- package/skills/devbooks-proposal-author/references//346/217/220/346/241/210/346/222/260/345/206/231/346/217/220/347/244/272/350/257/215.md +66 -0
- package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
- package/skills/devbooks-proposal-challenger/references//344/274/246/347/220/206/344/270/216/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +176 -0
- package/skills/devbooks-proposal-challenger/references//346/217/220/346/241/210/350/264/250/347/226/221/346/217/220/347/244/272/350/257/215.md +57 -0
- package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/345/267/245/344/275/234/346/265/201.md +24 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/346/250/241/346/235/277.md +35 -0
- package/skills/devbooks-proposal-debate-workflow/scripts/proposal-debate-check.sh +102 -0
- package/skills/devbooks-proposal-judge/SKILL.md +78 -0
- package/skills/devbooks-proposal-judge/references//346/217/220/346/241/210/350/243/201/345/206/263/346/217/220/347/244/272/350/257/215.md +37 -0
- package/skills/devbooks-router/SKILL.md +346 -0
- package/skills/devbooks-spec-contract/SKILL.md +191 -0
- package/skills/devbooks-spec-contract/references/API/350/256/276/350/256/241/346/214/207/345/215/227.md +349 -0
- package/skills/devbooks-spec-contract/references//345/245/221/347/272/246/344/270/216/346/225/260/346/215/256/345/256/232/344/271/211/346/217/220/347/244/272/350/257/215.md +85 -0
- package/skills/devbooks-spec-contract/references//350/247/204/346/240/274/345/217/230/346/233/264/346/217/220/347/244/272/350/257/215.md +63 -0
- package/skills/devbooks-spec-contract/references//351/232/220/345/274/217/345/217/230/346/233/264/346/243/200/346/265/213/346/217/220/347/244/272/350/257/215.md +183 -0
- package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
- package/skills/devbooks-spec-gardener/SKILL.md +72 -0
- package/skills/devbooks-spec-gardener/references//350/247/204/346/240/274/345/233/255/344/270/201/346/217/220/347/244/272/350/257/215.md +41 -0
- package/skills/devbooks-test-owner/SKILL.md +172 -0
- package/skills/devbooks-test-owner/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +228 -0
- package/skills/devbooks-test-owner/references//345/274/202/346/255/245/347/263/273/347/273/237/346/265/213/350/257/225/347/255/226/347/225/245.md +316 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/344/273/243/347/240/201/346/217/220/347/244/272/350/257/215.md +208 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/345/210/206/345/261/202/347/255/226/347/225/245.md +281 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/351/251/261/345/212/250.md +394 -0
- package/skills/devbooks-test-owner/references//350/247/243/344/276/235/350/265/226/346/212/200/346/234/257/351/200/237/346/237/245/350/241/250.md +432 -0
- package/skills/devbooks-test-reviewer/SKILL.md +189 -0
- package/templates/.devbooks/config.yaml +88 -0
- package/templates/claude-commands/devbooks/apply.md +38 -0
- package/templates/claude-commands/devbooks/archive.md +33 -0
- package/templates/claude-commands/devbooks/backport.md +19 -0
- package/templates/claude-commands/devbooks/bootstrap.md +19 -0
- package/templates/claude-commands/devbooks/c4.md +19 -0
- package/templates/claude-commands/devbooks/challenger.md +19 -0
- package/templates/claude-commands/devbooks/code.md +19 -0
- package/templates/claude-commands/devbooks/debate.md +19 -0
- package/templates/claude-commands/devbooks/delivery.md +19 -0
- package/templates/claude-commands/devbooks/design.md +19 -0
- package/templates/claude-commands/devbooks/entropy.md +19 -0
- package/templates/claude-commands/devbooks/federation.md +19 -0
- package/templates/claude-commands/devbooks/gardener.md +19 -0
- package/templates/claude-commands/devbooks/impact.md +19 -0
- package/templates/claude-commands/devbooks/index.md +19 -0
- package/templates/claude-commands/devbooks/judge.md +19 -0
- package/templates/claude-commands/devbooks/plan.md +19 -0
- package/templates/claude-commands/devbooks/proposal.md +19 -0
- package/templates/claude-commands/devbooks/quick.md +42 -0
- package/templates/claude-commands/devbooks/review.md +19 -0
- package/templates/claude-commands/devbooks/router.md +19 -0
- package/templates/claude-commands/devbooks/spec.md +19 -0
- package/templates/claude-commands/devbooks/test-review.md +19 -0
- package/templates/claude-commands/devbooks/test.md +19 -0
- package/templates/dev-playbooks/README.md +458 -0
- package/templates/dev-playbooks/changes/.gitkeep +1 -0
- package/templates/dev-playbooks/constitution.md +116 -0
- package/templates/dev-playbooks/project.md +96 -0
- package/templates/dev-playbooks/scripts/.gitkeep +1 -0
- package/templates/dev-playbooks/specs/_meta/anti-patterns/.gitkeep +2 -0
- package/templates/dev-playbooks/specs/_meta/glossary.md +47 -0
- package/templates/dev-playbooks/specs/_meta/project-profile.md +79 -0
- package/templates/dev-playbooks/specs/architecture/fitness-rules.md +95 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# 低风险改动技术速查表
|
|
2
|
+
|
|
3
|
+
> 来源:《修改代码的艺术》(Working Effectively with Legacy Code) - Michael Feathers
|
|
4
|
+
> 适用角色:Coder(实现负责人)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 适用场景
|
|
9
|
+
|
|
10
|
+
当 Coder 需要在遗留代码中添加功能或修复 bug,但面临以下约束时:
|
|
11
|
+
- 时间压力大,无法大规模重构
|
|
12
|
+
- 测试覆盖不足,改动风险高
|
|
13
|
+
- 需要保证"行为保持"
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 核心原则
|
|
18
|
+
|
|
19
|
+
| 原则 | 说明 |
|
|
20
|
+
|------|------|
|
|
21
|
+
| **行为保持** | 任何改动必须保证原有功能不变 |
|
|
22
|
+
| **最小侵入** | 尽量不修改原有代码,而是在外围添加/包装 |
|
|
23
|
+
| **可测试优先** | 新代码必须可测试,即使旧代码不可测试 |
|
|
24
|
+
| **依靠编译器** | 改签名后让编译器报错来发现所有调用点 |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 一、Sprout Method(萌生方法)
|
|
29
|
+
|
|
30
|
+
### 定义
|
|
31
|
+
在需要添加新功能时,不修改原有方法,而是**新建一个方法**,然后在原方法中调用它。
|
|
32
|
+
|
|
33
|
+
### 适用场景
|
|
34
|
+
- 需要在某个方法中间添加逻辑
|
|
35
|
+
- 原方法过长或难以测试
|
|
36
|
+
- 新逻辑相对独立
|
|
37
|
+
|
|
38
|
+
### 操作步骤
|
|
39
|
+
1. 识别需要添加代码的位置
|
|
40
|
+
2. 将新代码提取为独立方法
|
|
41
|
+
3. 为新方法编写测试
|
|
42
|
+
4. 在原位置调用新方法
|
|
43
|
+
|
|
44
|
+
### 示例
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# 原代码(难以测试)
|
|
48
|
+
class OrderProcessor:
|
|
49
|
+
def process(self, order):
|
|
50
|
+
# 100行遗留代码...
|
|
51
|
+
total = self._calculate_total(order)
|
|
52
|
+
# 需要在这里添加折扣计算逻辑
|
|
53
|
+
self._save_order(order, total)
|
|
54
|
+
# 50行遗留代码...
|
|
55
|
+
|
|
56
|
+
# Sprout Method 重构后
|
|
57
|
+
class OrderProcessor:
|
|
58
|
+
def process(self, order):
|
|
59
|
+
# 100行遗留代码...(不动)
|
|
60
|
+
total = self._calculate_total(order)
|
|
61
|
+
discounted_total = self._apply_discount(order, total) # 新增调用
|
|
62
|
+
self._save_order(order, discounted_total) # 修改参数
|
|
63
|
+
# 50行遗留代码...(不动)
|
|
64
|
+
|
|
65
|
+
def _apply_discount(self, order, total): # 新方法,可独立测试
|
|
66
|
+
if order.customer.is_vip:
|
|
67
|
+
return total * 0.9
|
|
68
|
+
return total
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 优点
|
|
72
|
+
- 新代码 100% 可测试
|
|
73
|
+
- 对原代码改动最小(仅添加一行调用)
|
|
74
|
+
- 风险隔离:新逻辑的 bug 不会影响原有逻辑
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 二、Sprout Class(萌生类)
|
|
79
|
+
|
|
80
|
+
### 定义
|
|
81
|
+
当新功能需要多个方法或状态时,**新建一个类**来承载,然后在原代码中实例化并使用它。
|
|
82
|
+
|
|
83
|
+
### 适用场景
|
|
84
|
+
- 新功能复杂,需要多个方法
|
|
85
|
+
- 新功能需要维护状态
|
|
86
|
+
- 原类已经过大(>500 行)
|
|
87
|
+
|
|
88
|
+
### 操作步骤
|
|
89
|
+
1. 创建新类,实现新功能
|
|
90
|
+
2. 为新类编写完整测试
|
|
91
|
+
3. 在原代码中创建新类实例
|
|
92
|
+
4. 调用新类方法
|
|
93
|
+
|
|
94
|
+
### 示例
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# 新建独立类(完全可测试)
|
|
98
|
+
class DiscountCalculator:
|
|
99
|
+
def __init__(self, discount_rules: list[DiscountRule]):
|
|
100
|
+
self._rules = discount_rules
|
|
101
|
+
|
|
102
|
+
def calculate(self, order, original_total):
|
|
103
|
+
discount = 0
|
|
104
|
+
for rule in self._rules:
|
|
105
|
+
discount += rule.apply(order, original_total)
|
|
106
|
+
return original_total - discount
|
|
107
|
+
|
|
108
|
+
# 在原代码中使用
|
|
109
|
+
class OrderProcessor:
|
|
110
|
+
def __init__(self):
|
|
111
|
+
self._discount_calc = DiscountCalculator(self._load_rules())
|
|
112
|
+
|
|
113
|
+
def process(self, order):
|
|
114
|
+
# 遗留代码...
|
|
115
|
+
total = self._calculate_total(order)
|
|
116
|
+
discounted = self._discount_calc.calculate(order, total) # 使用新类
|
|
117
|
+
self._save_order(order, discounted)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 优点
|
|
121
|
+
- 新类完全独立,可单独测试
|
|
122
|
+
- 职责清晰,避免原类膨胀
|
|
123
|
+
- 未来可扩展(如添加更多折扣规则)
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 三、Wrap Method(包装方法)
|
|
128
|
+
|
|
129
|
+
### 定义
|
|
130
|
+
不修改原方法实现,而是**创建一个新方法包装原方法**,在包装方法中添加前置/后置逻辑。
|
|
131
|
+
|
|
132
|
+
### 适用场景
|
|
133
|
+
- 需要在方法执行前后添加逻辑(如日志、验证、缓存)
|
|
134
|
+
- 原方法签名不变,对调用方透明
|
|
135
|
+
- 遵循开闭原则
|
|
136
|
+
|
|
137
|
+
### 操作步骤
|
|
138
|
+
1. 将原方法重命名(如 `pay` → `_pay_impl`)
|
|
139
|
+
2. 创建同名新方法
|
|
140
|
+
3. 在新方法中调用原方法,添加前后逻辑
|
|
141
|
+
|
|
142
|
+
### 示例
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# 原代码
|
|
146
|
+
class PaymentService:
|
|
147
|
+
def pay(self, order, amount):
|
|
148
|
+
# 支付逻辑...
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
# Wrap Method 重构后
|
|
152
|
+
class PaymentService:
|
|
153
|
+
def pay(self, order, amount): # 外部调用不变
|
|
154
|
+
self._log_payment_start(order, amount) # 前置逻辑
|
|
155
|
+
result = self._pay_impl(order, amount) # 原逻辑
|
|
156
|
+
self._log_payment_end(order, result) # 后置逻辑
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
def _pay_impl(self, order, amount): # 原方法重命名
|
|
160
|
+
# 原支付逻辑(完全不变)
|
|
161
|
+
return result
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 优点
|
|
165
|
+
- 原逻辑完全不变
|
|
166
|
+
- 调用方无感知
|
|
167
|
+
- 前后置逻辑可独立测试
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 四、Wrap Class(包装类/装饰器)
|
|
172
|
+
|
|
173
|
+
### 定义
|
|
174
|
+
创建一个新类,**包装原类**,在调用原类方法前后添加逻辑(装饰器模式)。
|
|
175
|
+
|
|
176
|
+
### 适用场景
|
|
177
|
+
- 原类无法修改(如第三方库)
|
|
178
|
+
- 需要为整个类添加横切关注点(日志、缓存、权限)
|
|
179
|
+
- 想保持原类不变
|
|
180
|
+
|
|
181
|
+
### 操作步骤
|
|
182
|
+
1. 创建包装类,持有原类实例
|
|
183
|
+
2. 实现相同接口
|
|
184
|
+
3. 在方法中添加逻辑后委托给原类
|
|
185
|
+
|
|
186
|
+
### 示例
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# 原类(可能是第三方库,无法修改)
|
|
190
|
+
class LegacyPaymentGateway:
|
|
191
|
+
def process(self, payment):
|
|
192
|
+
# 复杂的遗留逻辑...
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
# Wrap Class
|
|
196
|
+
class AuditedPaymentGateway:
|
|
197
|
+
def __init__(self, gateway: LegacyPaymentGateway, audit_log: AuditLog):
|
|
198
|
+
self._gateway = gateway
|
|
199
|
+
self._audit = audit_log
|
|
200
|
+
|
|
201
|
+
def process(self, payment): # 相同接口
|
|
202
|
+
self._audit.log_start(payment)
|
|
203
|
+
try:
|
|
204
|
+
result = self._gateway.process(payment) # 委托原类
|
|
205
|
+
self._audit.log_success(payment, result)
|
|
206
|
+
return result
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self._audit.log_failure(payment, e)
|
|
209
|
+
raise
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 优点
|
|
213
|
+
- 原类完全不变
|
|
214
|
+
- 可组合多个包装(如:缓存 + 日志 + 重试)
|
|
215
|
+
- 符合开闭原则
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 五、决策流程图
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
需要添加/修改功能
|
|
223
|
+
│
|
|
224
|
+
▼
|
|
225
|
+
┌─────────────────────────┐
|
|
226
|
+
│ 新功能是否独立成块? │
|
|
227
|
+
└───────────┬─────────────┘
|
|
228
|
+
┌────┴────┐
|
|
229
|
+
▼ ▼
|
|
230
|
+
是 否
|
|
231
|
+
│ │
|
|
232
|
+
▼ ▼
|
|
233
|
+
┌──────────┐ ┌──────────────────┐
|
|
234
|
+
│ 需要状态? │ │ 在方法前后添加? │
|
|
235
|
+
└────┬─────┘ └────────┬─────────┘
|
|
236
|
+
┌──┴──┐ ┌───┴───┐
|
|
237
|
+
▼ ▼ ▼ ▼
|
|
238
|
+
是 否 是 否
|
|
239
|
+
│ │ │ │
|
|
240
|
+
▼ ▼ ▼ ▼
|
|
241
|
+
Sprout Sprout Wrap 在方法中间
|
|
242
|
+
Class Method Method 添加
|
|
243
|
+
或 │
|
|
244
|
+
Wrap ▼
|
|
245
|
+
Class Sprout
|
|
246
|
+
Method
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 六、与 Test Owner 的协作
|
|
252
|
+
|
|
253
|
+
| Coder 行为 | 需要 Test Owner 配合 |
|
|
254
|
+
|-----------|---------------------|
|
|
255
|
+
| 使用 Sprout Method/Class | Test Owner 为新方法/类编写测试 |
|
|
256
|
+
| 使用 Wrap Method/Class | Test Owner 验证包装后行为不变 |
|
|
257
|
+
| 需要解依赖才能测试 | 参考《解依赖技术速查表》 |
|
|
258
|
+
| 改动影响多个调用方 | 参考 Impact Analysis 的 Pinch Point |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 七、禁止行为
|
|
263
|
+
|
|
264
|
+
- **禁止**:直接修改遗留代码的核心逻辑(除非有充分测试覆盖)
|
|
265
|
+
- **禁止**:为了"顺手重构"而改动不相关代码
|
|
266
|
+
- **禁止**:删除看似无用的代码(可能有隐藏依赖)
|
|
267
|
+
- **禁止**:修改 `tests/**` 目录(需交还 Test Owner)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 参考资料
|
|
272
|
+
|
|
273
|
+
- 《修改代码的艺术》第 6-8 章
|
|
274
|
+
- 《解依赖技术速查表》
|
|
275
|
+
- dev-playbooks `devbooks-coder/SKILL.md`
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# 日志规范
|
|
2
|
+
|
|
3
|
+
借鉴 VS Code 的日志实践,本文档定义了应用程序日志的标准规范。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1) 日志级别
|
|
8
|
+
|
|
9
|
+
### 级别定义
|
|
10
|
+
|
|
11
|
+
| 级别 | 用途 | 示例 |
|
|
12
|
+
|------|------|------|
|
|
13
|
+
| `ERROR` | 需要立即关注的错误 | 数据库连接失败、外部 API 错误 |
|
|
14
|
+
| `WARN` | 潜在问题,但不影响主流程 | 配置缺失使用默认值、重试成功 |
|
|
15
|
+
| `INFO` | 关键业务流程节点 | 用户登录、订单创建、服务启动 |
|
|
16
|
+
| `DEBUG` | 开发调试信息 | 函数入参、中间状态 |
|
|
17
|
+
| `TRACE` | 详细追踪信息 | 每行数据处理、循环迭代 |
|
|
18
|
+
|
|
19
|
+
### 级别选择指南
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ERROR:系统无法正常工作
|
|
23
|
+
logger.error('Database connection failed', { error, retries: 3 });
|
|
24
|
+
|
|
25
|
+
// WARN:可恢复的问题
|
|
26
|
+
logger.warn('Cache miss, falling back to database', { key });
|
|
27
|
+
|
|
28
|
+
// INFO:业务里程碑
|
|
29
|
+
logger.info('User registered', { userId, email });
|
|
30
|
+
|
|
31
|
+
// DEBUG:开发时有用的信息
|
|
32
|
+
logger.debug('Processing request', { params, headers });
|
|
33
|
+
|
|
34
|
+
// TRACE:非常详细的追踪
|
|
35
|
+
logger.trace('Row processed', { rowIndex, data });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2) 日志格式
|
|
41
|
+
|
|
42
|
+
### 结构化日志
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// 推荐:结构化日志
|
|
46
|
+
logger.info('Order created', {
|
|
47
|
+
orderId: '12345',
|
|
48
|
+
userId: 'user-789',
|
|
49
|
+
amount: 99.99,
|
|
50
|
+
currency: 'USD',
|
|
51
|
+
items: 3
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 输出 JSON(便于解析)
|
|
55
|
+
{
|
|
56
|
+
"timestamp": "2024-01-15T10:30:00.000Z",
|
|
57
|
+
"level": "INFO",
|
|
58
|
+
"message": "Order created",
|
|
59
|
+
"orderId": "12345",
|
|
60
|
+
"userId": "user-789",
|
|
61
|
+
"amount": 99.99,
|
|
62
|
+
"currency": "USD",
|
|
63
|
+
"items": 3,
|
|
64
|
+
"service": "order-service",
|
|
65
|
+
"traceId": "abc-123-xyz"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 必须包含的字段
|
|
70
|
+
|
|
71
|
+
| 字段 | 说明 | 示例 |
|
|
72
|
+
|------|------|------|
|
|
73
|
+
| `timestamp` | ISO 8601 格式时间戳 | `2024-01-15T10:30:00.000Z` |
|
|
74
|
+
| `level` | 日志级别 | `INFO`, `ERROR` |
|
|
75
|
+
| `message` | 人类可读的消息 | `User logged in` |
|
|
76
|
+
| `service` | 服务名称 | `user-service` |
|
|
77
|
+
| `traceId` | 请求追踪 ID | `abc-123-xyz` |
|
|
78
|
+
|
|
79
|
+
### 可选字段
|
|
80
|
+
|
|
81
|
+
| 字段 | 说明 | 何时使用 |
|
|
82
|
+
|------|------|---------|
|
|
83
|
+
| `userId` | 用户标识 | 有用户上下文时 |
|
|
84
|
+
| `requestId` | 请求 ID | HTTP 请求处理 |
|
|
85
|
+
| `duration` | 耗时(毫秒) | 性能相关日志 |
|
|
86
|
+
| `error` | 错误详情 | ERROR 级别日志 |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 3) 日志内容规范
|
|
91
|
+
|
|
92
|
+
### 消息格式
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// 推荐:动词开头,描述发生了什么
|
|
96
|
+
logger.info('User logged in', { userId });
|
|
97
|
+
logger.info('Order created', { orderId });
|
|
98
|
+
logger.info('Payment processed', { paymentId, amount });
|
|
99
|
+
|
|
100
|
+
// 避免:模糊的消息
|
|
101
|
+
logger.info('Done'); // ❌ 什么 done?
|
|
102
|
+
logger.info('Error'); // ❌ 什么 error?
|
|
103
|
+
logger.info('Processing...'); // ❌ 处理什么?
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 上下文数据
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// 推荐:包含足够的上下文
|
|
110
|
+
logger.error('Failed to process payment', {
|
|
111
|
+
orderId: '12345',
|
|
112
|
+
paymentMethod: 'credit_card',
|
|
113
|
+
error: {
|
|
114
|
+
code: 'DECLINED',
|
|
115
|
+
message: 'Card declined by issuer'
|
|
116
|
+
},
|
|
117
|
+
retryCount: 2
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 避免:缺少上下文
|
|
121
|
+
logger.error('Payment failed'); // ❌ 哪个订单?什么原因?
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 敏感数据处理
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// 禁止:记录敏感数据
|
|
128
|
+
logger.info('User login', {
|
|
129
|
+
email: 'user@example.com',
|
|
130
|
+
password: '123456' // ❌ 绝对禁止!
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 正确:脱敏处理
|
|
134
|
+
logger.info('User login', {
|
|
135
|
+
email: maskEmail('user@example.com'), // u***@example.com
|
|
136
|
+
passwordProvided: true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// 脱敏工具函数
|
|
140
|
+
function maskEmail(email: string): string {
|
|
141
|
+
const [local, domain] = email.split('@');
|
|
142
|
+
return `${local[0]}***@${domain}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function maskCreditCard(card: string): string {
|
|
146
|
+
return `****-****-****-${card.slice(-4)}`;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**禁止记录的数据**:
|
|
151
|
+
|
|
152
|
+
| 类型 | 示例 |
|
|
153
|
+
|------|------|
|
|
154
|
+
| 密码 | password, secret, token |
|
|
155
|
+
| 凭证 | API key, access token |
|
|
156
|
+
| 个人信息 | 身份证号、银行卡号 |
|
|
157
|
+
| 敏感业务数据 | 完整信用卡号、CVV |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 4) 错误日志规范
|
|
162
|
+
|
|
163
|
+
### 错误信息结构
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
logger.error('Operation failed', {
|
|
167
|
+
operation: 'createOrder',
|
|
168
|
+
error: {
|
|
169
|
+
name: error.name,
|
|
170
|
+
message: error.message,
|
|
171
|
+
code: error.code,
|
|
172
|
+
stack: error.stack // 仅在非生产环境
|
|
173
|
+
},
|
|
174
|
+
context: {
|
|
175
|
+
userId: '12345',
|
|
176
|
+
input: sanitize(input) // 脱敏后的输入
|
|
177
|
+
},
|
|
178
|
+
recovery: 'Will retry in 5 seconds'
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 错误分类
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// 可恢复错误(WARN)
|
|
186
|
+
logger.warn('Temporary failure, retrying', {
|
|
187
|
+
operation: 'fetchData',
|
|
188
|
+
attempt: 2,
|
|
189
|
+
maxAttempts: 3
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 不可恢复错误(ERROR)
|
|
193
|
+
logger.error('Critical failure', {
|
|
194
|
+
operation: 'saveData',
|
|
195
|
+
error: error.message,
|
|
196
|
+
action: 'Manual intervention required'
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 5) 性能日志
|
|
203
|
+
|
|
204
|
+
### 耗时记录
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// 记录操作耗时
|
|
208
|
+
const start = performance.now();
|
|
209
|
+
await processOrder(order);
|
|
210
|
+
const duration = performance.now() - start;
|
|
211
|
+
|
|
212
|
+
logger.info('Order processed', {
|
|
213
|
+
orderId: order.id,
|
|
214
|
+
duration: Math.round(duration), // 毫秒
|
|
215
|
+
durationUnit: 'ms'
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// 超时警告
|
|
219
|
+
if (duration > 1000) {
|
|
220
|
+
logger.warn('Slow operation detected', {
|
|
221
|
+
operation: 'processOrder',
|
|
222
|
+
duration,
|
|
223
|
+
threshold: 1000
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 批量操作
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
logger.info('Batch processing completed', {
|
|
232
|
+
operation: 'importUsers',
|
|
233
|
+
total: 1000,
|
|
234
|
+
success: 985,
|
|
235
|
+
failed: 15,
|
|
236
|
+
duration: 5230,
|
|
237
|
+
avgPerItem: 5.23
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 6) 日志配置
|
|
244
|
+
|
|
245
|
+
### 环境配置
|
|
246
|
+
|
|
247
|
+
| 环境 | 默认级别 | 输出格式 | 堆栈跟踪 |
|
|
248
|
+
|------|---------|---------|---------|
|
|
249
|
+
| Development | DEBUG | 人类可读 | 完整 |
|
|
250
|
+
| Staging | INFO | JSON | 仅 ERROR |
|
|
251
|
+
| Production | INFO | JSON | 仅 ERROR |
|
|
252
|
+
|
|
253
|
+
### 配置示例
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// logger.config.ts
|
|
257
|
+
interface LoggerConfig {
|
|
258
|
+
level: 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
|
259
|
+
format: 'json' | 'pretty';
|
|
260
|
+
includeStack: boolean;
|
|
261
|
+
service: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const config: LoggerConfig = {
|
|
265
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
266
|
+
format: process.env.NODE_ENV === 'production' ? 'json' : 'pretty',
|
|
267
|
+
includeStack: process.env.NODE_ENV !== 'production',
|
|
268
|
+
service: process.env.SERVICE_NAME || 'app'
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 7) 日志库推荐
|
|
275
|
+
|
|
276
|
+
### Node.js
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// 推荐:pino(高性能)
|
|
280
|
+
import pino from 'pino';
|
|
281
|
+
|
|
282
|
+
const logger = pino({
|
|
283
|
+
level: 'info',
|
|
284
|
+
formatters: {
|
|
285
|
+
level: (label) => ({ level: label.toUpperCase() })
|
|
286
|
+
},
|
|
287
|
+
timestamp: () => `,"timestamp":"${new Date().toISOString()}"`
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// 或:winston(功能丰富)
|
|
291
|
+
import winston from 'winston';
|
|
292
|
+
|
|
293
|
+
const logger = winston.createLogger({
|
|
294
|
+
level: 'info',
|
|
295
|
+
format: winston.format.json(),
|
|
296
|
+
transports: [
|
|
297
|
+
new winston.transports.Console()
|
|
298
|
+
]
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 浏览器
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// 简单封装
|
|
306
|
+
const logger = {
|
|
307
|
+
error: (msg: string, data?: object) =>
|
|
308
|
+
console.error(JSON.stringify({ level: 'ERROR', message: msg, ...data })),
|
|
309
|
+
warn: (msg: string, data?: object) =>
|
|
310
|
+
console.warn(JSON.stringify({ level: 'WARN', message: msg, ...data })),
|
|
311
|
+
info: (msg: string, data?: object) =>
|
|
312
|
+
console.info(JSON.stringify({ level: 'INFO', message: msg, ...data })),
|
|
313
|
+
debug: (msg: string, data?: object) =>
|
|
314
|
+
console.debug(JSON.stringify({ level: 'DEBUG', message: msg, ...data }))
|
|
315
|
+
};
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 8) 检查清单
|
|
321
|
+
|
|
322
|
+
编写日志时确认:
|
|
323
|
+
|
|
324
|
+
- [ ] 级别是否正确?(ERROR/WARN/INFO/DEBUG)
|
|
325
|
+
- [ ] 消息是否清晰?(动词开头,描述事件)
|
|
326
|
+
- [ ] 上下文是否充分?(能定位问题)
|
|
327
|
+
- [ ] 敏感数据是否脱敏?
|
|
328
|
+
- [ ] 错误日志是否包含堆栈?
|
|
329
|
+
- [ ] 性能日志是否包含耗时?
|