ethan-skill 1.7.0 → 1.8.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/README.md +83 -24
- package/dist/skills/15-git-workflow.d.ts +3 -0
- package/dist/skills/15-git-workflow.d.ts.map +1 -0
- package/dist/skills/15-git-workflow.js +288 -0
- package/dist/skills/15-git-workflow.js.map +1 -0
- package/dist/skills/16-unit-testing.d.ts +3 -0
- package/dist/skills/16-unit-testing.d.ts.map +1 -0
- package/dist/skills/16-unit-testing.js +298 -0
- package/dist/skills/16-unit-testing.js.map +1 -0
- package/dist/skills/17-system-design.d.ts +3 -0
- package/dist/skills/17-system-design.d.ts.map +1 -0
- package/dist/skills/17-system-design.js +294 -0
- package/dist/skills/17-system-design.js.map +1 -0
- package/dist/skills/18-database-optimize.d.ts +3 -0
- package/dist/skills/18-database-optimize.d.ts.map +1 -0
- package/dist/skills/18-database-optimize.js +294 -0
- package/dist/skills/18-database-optimize.js.map +1 -0
- package/dist/skills/19-docker.d.ts +3 -0
- package/dist/skills/19-docker.d.ts.map +1 -0
- package/dist/skills/19-docker.js +360 -0
- package/dist/skills/19-docker.js.map +1 -0
- package/dist/skills/20-cicd.d.ts +3 -0
- package/dist/skills/20-cicd.d.ts.map +1 -0
- package/dist/skills/20-cicd.js +364 -0
- package/dist/skills/20-cicd.js.map +1 -0
- package/dist/skills/21-performance.d.ts +3 -0
- package/dist/skills/21-performance.d.ts.map +1 -0
- package/dist/skills/21-performance.js +139 -0
- package/dist/skills/21-performance.js.map +1 -0
- package/dist/skills/22-refactoring.d.ts +3 -0
- package/dist/skills/22-refactoring.d.ts.map +1 -0
- package/dist/skills/22-refactoring.js +235 -0
- package/dist/skills/22-refactoring.js.map +1 -0
- package/dist/skills/23-observability.d.ts +3 -0
- package/dist/skills/23-observability.d.ts.map +1 -0
- package/dist/skills/23-observability.js +266 -0
- package/dist/skills/23-observability.js.map +1 -0
- package/dist/skills/24-design-patterns.d.ts +3 -0
- package/dist/skills/24-design-patterns.d.ts.map +1 -0
- package/dist/skills/24-design-patterns.js +258 -0
- package/dist/skills/24-design-patterns.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +41 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/skills.test.js +3 -3
- package/dist/skills/skills.test.js.map +1 -1
- package/dist/templates/templates.test.js +2 -3
- package/dist/templates/templates.test.js.map +1 -1
- package/package.json +1 -1
- package/rules/claude-code/CLAUDE.md +2410 -3
- package/rules/cline/.clinerules +2262 -2
- package/rules/codebuddy/CODEBUDDY.md +2361 -2
- package/rules/continue/.continuerules +2262 -2
- package/rules/copilot/copilot-instructions.md +2331 -2
- package/rules/cursor/.cursorrules +2399 -2
- package/rules/cursor/smart-flow.mdc +2399 -2
- package/rules/jetbrains/smart-flow.md +2331 -2
- package/rules/lingma/smart-flow.md +2352 -3
- package/rules/windsurf/.windsurf/rules/smart-flow.md +2332 -3
- package/rules/zed/smart-flow.rules +2251 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refactoringSkill = void 0;
|
|
4
|
+
exports.refactoringSkill = {
|
|
5
|
+
id: 'refactoring',
|
|
6
|
+
name: '代码重构',
|
|
7
|
+
nameEn: 'refactoring',
|
|
8
|
+
order: 22,
|
|
9
|
+
category: '质量侧',
|
|
10
|
+
description: '系统化识别代码坏味道,运用重构手法安全改善代码结构,不改变外部行为',
|
|
11
|
+
descriptionEn: 'Systematically identify code smells and apply refactoring techniques to improve structure without changing behavior',
|
|
12
|
+
detailDescription: `按照 Martin Fowler 重构方法论,识别 Bad Smells(重复代码、过长函数、散弹式修改等),
|
|
13
|
+
运用提炼函数/类、移动特性、简化条件逻辑等重构手法,配合测试保驾护航,逐步改善代码质量。`,
|
|
14
|
+
triggers: [
|
|
15
|
+
'代码重构',
|
|
16
|
+
'refactoring',
|
|
17
|
+
'refactor',
|
|
18
|
+
'重构',
|
|
19
|
+
'坏味道',
|
|
20
|
+
'bad smell',
|
|
21
|
+
'技术债',
|
|
22
|
+
'technical debt',
|
|
23
|
+
'代码质量改善',
|
|
24
|
+
'@ethan refactor',
|
|
25
|
+
'@ethan 重构',
|
|
26
|
+
],
|
|
27
|
+
steps: [
|
|
28
|
+
{
|
|
29
|
+
title: '1. 识别代码坏味道(Bad Smells)',
|
|
30
|
+
content: `重构前先诊断,明确改善目标:
|
|
31
|
+
|
|
32
|
+
**最常见的 12 种坏味道**
|
|
33
|
+
|
|
34
|
+
| 坏味道 | 症状 | 危害 |
|
|
35
|
+
|--------|------|------|
|
|
36
|
+
| **重复代码** | 相同逻辑出现 ≥2 处 | 修改需同步多处,极易遗漏 |
|
|
37
|
+
| **过长函数** | 函数 > 20 行 | 难以理解、测试、复用 |
|
|
38
|
+
| **过大的类** | 类承担过多职责 | 违反 SRP,耦合严重 |
|
|
39
|
+
| **过长参数列表** | 参数 > 4 个 | 调用复杂,难以记忆 |
|
|
40
|
+
| **发散式变化** | 一个类因不同原因被修改 | 违反 SRP |
|
|
41
|
+
| **散弹式修改** | 一个变化需改多处 | 高耦合,遗漏风险高 |
|
|
42
|
+
| **依恋情结** | 方法频繁访问其他类数据 | 逻辑放错了地方 |
|
|
43
|
+
| **数据泥团** | 多处总是成组出现的数据 | 缺少封装 |
|
|
44
|
+
| **基本类型偏执** | 用原始类型代替小对象 | 缺少领域建模 |
|
|
45
|
+
| **注释过多** | 用注释弥补代码的不清晰 | 注释是坏味道的遮羞布 |
|
|
46
|
+
| **过深嵌套** | 条件/循环嵌套 > 3 层 | 圈复杂度高,难以追踪 |
|
|
47
|
+
| **僵尸代码** | 死代码、被注释的代码块 | 干扰阅读,增加维护负担 |
|
|
48
|
+
|
|
49
|
+
\`\`\`bash
|
|
50
|
+
# 快速扫描工具
|
|
51
|
+
npx eslint src --rule '{"complexity": ["warn", 10]}' # 圈复杂度
|
|
52
|
+
npx jscpd src --threshold 5 # 重复代码检测
|
|
53
|
+
ethan scan --todo # TODO/FIXME 清单
|
|
54
|
+
\`\`\``,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: '2. 核心重构手法',
|
|
58
|
+
content: `**提炼函数(Extract Function)** — 最常用
|
|
59
|
+
|
|
60
|
+
\`\`\`typescript
|
|
61
|
+
// Before: 过长函数,注释掩盖意图
|
|
62
|
+
function processOrder(order: Order) {
|
|
63
|
+
// 计算折扣
|
|
64
|
+
let discount = 0;
|
|
65
|
+
if (order.user.isPremium) discount = 0.1;
|
|
66
|
+
if (order.total > 1000) discount += 0.05;
|
|
67
|
+
const finalPrice = order.total * (1 - discount);
|
|
68
|
+
|
|
69
|
+
// 发送确认邮件
|
|
70
|
+
const subject = \`订单 \${order.id} 确认\`;
|
|
71
|
+
sendEmail(order.user.email, subject, finalPrice);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// After: 每个函数做一件事
|
|
75
|
+
function calculateDiscount(order: Order): number {
|
|
76
|
+
let discount = 0;
|
|
77
|
+
if (order.user.isPremium) discount = 0.1;
|
|
78
|
+
if (order.total > 1000) discount += 0.05;
|
|
79
|
+
return discount;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sendOrderConfirmation(order: Order, finalPrice: number): void {
|
|
83
|
+
const subject = \`订单 \${order.id} 确认\`;
|
|
84
|
+
sendEmail(order.user.email, subject, finalPrice);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function processOrder(order: Order) {
|
|
88
|
+
const discount = calculateDiscount(order);
|
|
89
|
+
const finalPrice = order.total * (1 - discount);
|
|
90
|
+
sendOrderConfirmation(order, finalPrice);
|
|
91
|
+
}
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
**以多态取代条件(Replace Conditional with Polymorphism)**
|
|
95
|
+
|
|
96
|
+
\`\`\`typescript
|
|
97
|
+
// Before: switch 散弹式修改
|
|
98
|
+
function getShippingCost(order: Order): number {
|
|
99
|
+
switch (order.type) {
|
|
100
|
+
case 'standard': return order.weight * 10;
|
|
101
|
+
case 'express': return order.weight * 20 + 50;
|
|
102
|
+
case 'overnight': return order.weight * 30 + 100;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// After: 策略模式/多态
|
|
107
|
+
abstract class ShippingStrategy {
|
|
108
|
+
abstract calculate(order: Order): number;
|
|
109
|
+
}
|
|
110
|
+
class StandardShipping extends ShippingStrategy {
|
|
111
|
+
calculate(order: Order) { return order.weight * 10; }
|
|
112
|
+
}
|
|
113
|
+
class ExpressShipping extends ShippingStrategy {
|
|
114
|
+
calculate(order: Order) { return order.weight * 20 + 50; }
|
|
115
|
+
}
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
**引入参数对象(Introduce Parameter Object)**
|
|
119
|
+
|
|
120
|
+
\`\`\`typescript
|
|
121
|
+
// Before: 过长参数列表
|
|
122
|
+
function createReport(startDate: Date, endDate: Date, userId: string, format: string) {}
|
|
123
|
+
|
|
124
|
+
// After: 封装为值对象
|
|
125
|
+
interface ReportParams { dateRange: DateRange; userId: string; format: string; }
|
|
126
|
+
function createReport(params: ReportParams) {}
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
**其他常用手法速查**
|
|
130
|
+
|
|
131
|
+
| 手法 | 适用场景 |
|
|
132
|
+
|------|---------|
|
|
133
|
+
| 提炼类(Extract Class) | 一个类承担过多职责 |
|
|
134
|
+
| 移动函数(Move Function) | 方法与数据不在一处 |
|
|
135
|
+
| 内联函数(Inline Function) | 函数体比名字更清晰 |
|
|
136
|
+
| 分解条件(Decompose Conditional) | 复杂 if-else 逻辑 |
|
|
137
|
+
| 卫语句(Guard Clauses) | 深层嵌套 → 提前返回 |
|
|
138
|
+
| 以查询取代临时变量 | 中间临时变量过多 |`,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: '3. 重构安全网:测试先行',
|
|
142
|
+
content: `**重构铁律:没有测试,不要重构**
|
|
143
|
+
|
|
144
|
+
\`\`\`bash
|
|
145
|
+
# Step 1: 确保现有测试覆盖率充足
|
|
146
|
+
npm run test:coverage
|
|
147
|
+
# 目标:被重构的模块覆盖率 > 80%
|
|
148
|
+
|
|
149
|
+
# Step 2: 若无测试,先补特征测试(Characterization Test)
|
|
150
|
+
# 不是测试"应该如何",而是记录"当前如何"
|
|
151
|
+
it('characterization: processOrder returns expected price', () => {
|
|
152
|
+
const result = processOrder(mockOrder);
|
|
153
|
+
expect(result).toMatchSnapshot(); // 先快照,重构后验证不变
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
# Step 3: 小步前进 — 每次重构后立即运行测试
|
|
157
|
+
npm test -- --watch
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
**重构工作流**
|
|
161
|
+
|
|
162
|
+
\`\`\`
|
|
163
|
+
识别目标 → 写/补测试 → 最小重构 → 运行测试 → 提交
|
|
164
|
+
↑____________________________|
|
|
165
|
+
循环,每次改动 < 30min
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
**IDE 辅助重构(减少手工失误)**
|
|
169
|
+
|
|
170
|
+
| 操作 | VS Code / WebStorm |
|
|
171
|
+
|------|-------------------|
|
|
172
|
+
| 提炼函数 | Ctrl+Shift+R → Extract Method |
|
|
173
|
+
| 重命名 | F2 → 自动更新所有引用 |
|
|
174
|
+
| 移动文件 | 拖拽 → 自动更新 import |
|
|
175
|
+
| 提炼变量 | Ctrl+Shift+R → Extract Variable |`,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
title: '4. 重构策略与输出',
|
|
179
|
+
content: `**Boy Scout Rule(童子军规则)**
|
|
180
|
+
> 让代码比你来时更干净一点,每次 PR 顺手重构接触到的代码。
|
|
181
|
+
|
|
182
|
+
**大规模重构策略:Strangler Fig Pattern(绞杀榕模式)**
|
|
183
|
+
|
|
184
|
+
\`\`\`
|
|
185
|
+
旧系统 ──[façade]──→ 新模块(逐步替换)
|
|
186
|
+
|
|
|
187
|
+
└──→ 旧模块(逐步废弃)
|
|
188
|
+
\`\`\`
|
|
189
|
+
|
|
190
|
+
1. 在旧代码外包一层 Façade/Adapter
|
|
191
|
+
2. 新功能全部写在新结构中
|
|
192
|
+
3. 旧调用方逐步迁移到新结构
|
|
193
|
+
4. 旧代码最终归零删除
|
|
194
|
+
|
|
195
|
+
**何时停止重构**
|
|
196
|
+
|
|
197
|
+
| 信号 | 建议 |
|
|
198
|
+
|------|------|
|
|
199
|
+
| 测试全绿,代码可读性提升 | 提交,结束本轮 |
|
|
200
|
+
| 发现需要改外部接口 | 创建新 Issue,本次不做 |
|
|
201
|
+
| 重构范围不断扩大 | 停止,重新评估范围 |
|
|
202
|
+
|
|
203
|
+
**重构输出清单**
|
|
204
|
+
- [ ] 坏味道清单(标注优先级 P1/P2/P3)
|
|
205
|
+
- [ ] 本次重构的 Diff 说明(what changed & why)
|
|
206
|
+
- [ ] 测试覆盖率前后对比
|
|
207
|
+
- [ ] 技术债记录到 Issue/Backlog`,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
outputFormat: 'Markdown 重构报告:坏味道清单 + 重构手法说明 + 测试覆盖率变化 + 技术债 Backlog',
|
|
211
|
+
examples: [
|
|
212
|
+
{
|
|
213
|
+
input: '这个函数有 200 行,我需要重构它',
|
|
214
|
+
output: `## 重构计划 — 200 行过长函数
|
|
215
|
+
|
|
216
|
+
**识别问题**:函数超过 20 行阈值 10 倍,违反单一职责
|
|
217
|
+
|
|
218
|
+
**步骤**
|
|
219
|
+
1. 先写特征测试(Characterization Test),用快照记录当前行为
|
|
220
|
+
2. 按注释/空行识别功能段落,每段提炼为独立函数
|
|
221
|
+
3. 若有共享状态,考虑提炼为类
|
|
222
|
+
4. 每提炼一个函数后立即运行测试
|
|
223
|
+
|
|
224
|
+
**预计拆分结果**:1 个 orchestration 函数 + 5-10 个小函数,每个 < 20 行`,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
notes: [
|
|
228
|
+
'重构前必须有测试覆盖,否则是在盲目改动——叫重写不叫重构',
|
|
229
|
+
'每次重构只做一件事,不要同时修改功能',
|
|
230
|
+
'利用 IDE 的自动重构功能,减少手工失误',
|
|
231
|
+
'技术债需要持续还,但不要以重构为名无限延期需求',
|
|
232
|
+
],
|
|
233
|
+
nextSkill: 'observability',
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=22-refactoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"22-refactoring.js","sourceRoot":"","sources":["../../src/skills/22-refactoring.ts"],"names":[],"mappings":";;;AAEa,QAAA,gBAAgB,GAAoB;IAC/C,EAAE,EAAE,aAAa;IACjB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,EAAE;IACT,QAAQ,EAAE,KAAK;IACf,WAAW,EAAE,mCAAmC;IAChD,aAAa,EAAE,qHAAqH;IACpI,iBAAiB,EAAE;6CACwB;IAC3C,QAAQ,EAAE;QACR,MAAM;QACN,aAAa;QACb,UAAU;QACV,IAAI;QACJ,KAAK;QACL,WAAW;QACX,KAAK;QACL,gBAAgB;QAChB,QAAQ;QACR,iBAAiB;QACjB,WAAW;KACZ;IACD,KAAK,EAAE;QACL;YACE,KAAK,EAAE,wBAAwB;YAC/B,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;OAwBR;SACF;QACD;YACE,KAAK,EAAE,WAAW;YAClB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAgFU;SACpB;QACD;YACE,KAAK,EAAE,eAAe;YACtB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAiC4B;SACtC;QACD;YACE,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BA4BY;SACtB;KACF;IACD,YAAY,EAAE,sDAAsD;IACpE,QAAQ,EAAE;QACR;YACE,KAAK,EAAE,oBAAoB;YAC3B,MAAM,EAAE;;;;;;;;;;sDAUwC;SACjD;KACF;IACD,KAAK,EAAE;QACL,8BAA8B;QAC9B,oBAAoB;QACpB,uBAAuB;QACvB,yBAAyB;KAC1B;IACD,SAAS,EAAE,eAAe;CAC3B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"23-observability.d.ts","sourceRoot":"","sources":["../../src/skills/23-observability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,kBAAkB,EAAE,eAqQhC,CAAC"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.observabilitySkill = void 0;
|
|
4
|
+
exports.observabilitySkill = {
|
|
5
|
+
id: 'observability',
|
|
6
|
+
name: '可观测性',
|
|
7
|
+
nameEn: 'observability',
|
|
8
|
+
order: 23,
|
|
9
|
+
category: '质量侧',
|
|
10
|
+
description: '建立日志、指标、链路追踪三支柱体系,实现系统状态完全可观测,快速定位生产问题',
|
|
11
|
+
descriptionEn: 'Build the three pillars of observability (logs, metrics, traces) to achieve full system visibility and fast production diagnosis',
|
|
12
|
+
detailDescription: `围绕可观测性三支柱(Logs / Metrics / Traces),指导团队从零建立或完善监控体系,
|
|
13
|
+
包括结构化日志规范、Prometheus 指标采集、分布式链路追踪(OpenTelemetry)、告警策略设计和 SLO 定义,
|
|
14
|
+
帮助团队在生产故障时快速定位根因。`,
|
|
15
|
+
triggers: [
|
|
16
|
+
'可观测性',
|
|
17
|
+
'observability',
|
|
18
|
+
'监控',
|
|
19
|
+
'monitoring',
|
|
20
|
+
'日志',
|
|
21
|
+
'logging',
|
|
22
|
+
'链路追踪',
|
|
23
|
+
'tracing',
|
|
24
|
+
'指标',
|
|
25
|
+
'metrics',
|
|
26
|
+
'SLO',
|
|
27
|
+
'SLA',
|
|
28
|
+
'告警',
|
|
29
|
+
'alerting',
|
|
30
|
+
'@ethan 监控',
|
|
31
|
+
'@ethan observability',
|
|
32
|
+
],
|
|
33
|
+
steps: [
|
|
34
|
+
{
|
|
35
|
+
title: '1. 三支柱体系设计',
|
|
36
|
+
content: `**可观测性三支柱(Three Pillars of Observability)**
|
|
37
|
+
|
|
38
|
+
| 支柱 | 回答的问题 | 工具栈 |
|
|
39
|
+
|------|-----------|--------|
|
|
40
|
+
| **Logs(日志)** | 发生了什么? | Winston/Pino + ELK/Loki |
|
|
41
|
+
| **Metrics(指标)** | 系统状况如何? | Prometheus + Grafana |
|
|
42
|
+
| **Traces(链路)** | 请求经过了哪里? | OpenTelemetry + Jaeger/Tempo |
|
|
43
|
+
|
|
44
|
+
**选型建议**
|
|
45
|
+
|
|
46
|
+
\`\`\`
|
|
47
|
+
轻量级单体: Pino + Prometheus + Grafana
|
|
48
|
+
微服务标准: OpenTelemetry SDK → Collector → Jaeger + Prometheus + Loki
|
|
49
|
+
云原生托管: Datadog / New Relic / AWS CloudWatch (开箱即用)
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
**黄金信号(Golden Signals)— 4个必监控指标**
|
|
53
|
+
|
|
54
|
+
| 信号 | 说明 | 告警阈值示例 |
|
|
55
|
+
|------|------|-------------|
|
|
56
|
+
| **Latency(延迟)** | P50/P99/P999 响应时间 | P99 > 500ms |
|
|
57
|
+
| **Traffic(流量)** | RPS / 并发连接数 | 环比突增 50% |
|
|
58
|
+
| **Errors(错误率)** | 5xx / 业务错误比例 | > 0.1% |
|
|
59
|
+
| **Saturation(饱和度)** | CPU/内存/队列深度 | CPU > 80% |`,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: '2. 结构化日志规范',
|
|
63
|
+
content: `**日志必须是结构化 JSON,不要用 console.log**
|
|
64
|
+
|
|
65
|
+
\`\`\`typescript
|
|
66
|
+
// ❌ Bad: 非结构化,无法机器解析
|
|
67
|
+
console.log(\`用户 \${userId} 下单失败: \${error.message}\`);
|
|
68
|
+
|
|
69
|
+
// ✅ Good: 结构化 JSON 日志(使用 Pino)
|
|
70
|
+
import pino from 'pino';
|
|
71
|
+
const logger = pino({ level: 'info' });
|
|
72
|
+
|
|
73
|
+
logger.error({
|
|
74
|
+
event: 'order.create.failed',
|
|
75
|
+
userId,
|
|
76
|
+
orderId,
|
|
77
|
+
errorCode: error.code,
|
|
78
|
+
msg: error.message,
|
|
79
|
+
durationMs: Date.now() - startTime,
|
|
80
|
+
});
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
**日志级别规范**
|
|
84
|
+
|
|
85
|
+
| 级别 | 使用场景 | 生产建议 |
|
|
86
|
+
|------|---------|---------|
|
|
87
|
+
| ERROR | 需要立即处理的错误 | 触发告警 |
|
|
88
|
+
| WARN | 不影响功能但需关注 | 记录 + 汇总 |
|
|
89
|
+
| INFO | 关键业务事件(下单/登录) | 默认级别 |
|
|
90
|
+
| DEBUG | 调试信息,技术细节 | 生产关闭 |
|
|
91
|
+
|
|
92
|
+
**必带字段(Mandatory Fields)**
|
|
93
|
+
|
|
94
|
+
\`\`\`typescript
|
|
95
|
+
interface LogContext {
|
|
96
|
+
traceId: string; // 链路追踪 ID
|
|
97
|
+
spanId: string; // 当前 Span ID
|
|
98
|
+
userId?: string; // 用户 ID(有则带)
|
|
99
|
+
requestId: string; // 请求唯一 ID
|
|
100
|
+
service: string; // 服务名
|
|
101
|
+
version: string; // 服务版本
|
|
102
|
+
env: string; // prod / staging
|
|
103
|
+
}
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
**日志采样策略**
|
|
107
|
+
|
|
108
|
+
\`\`\`typescript
|
|
109
|
+
// 高流量场景:ERROR 全量,INFO 10% 采样
|
|
110
|
+
const shouldLog = (level: string) =>
|
|
111
|
+
level === 'error' || Math.random() < 0.1;
|
|
112
|
+
\`\`\``,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: '3. 指标采集与告警(Prometheus + Grafana)',
|
|
116
|
+
content: `**RED 方法论(微服务推荐)**
|
|
117
|
+
- **R**ate — 每秒请求数
|
|
118
|
+
- **E**rrors — 错误率
|
|
119
|
+
- **D**uration — 请求时延分布
|
|
120
|
+
|
|
121
|
+
\`\`\`typescript
|
|
122
|
+
// Node.js 指标暴露(prom-client)
|
|
123
|
+
import { Counter, Histogram, register } from 'prom-client';
|
|
124
|
+
|
|
125
|
+
const httpRequests = new Counter({
|
|
126
|
+
name: 'http_requests_total',
|
|
127
|
+
help: 'Total HTTP requests',
|
|
128
|
+
labelNames: ['method', 'route', 'status'],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const httpDuration = new Histogram({
|
|
132
|
+
name: 'http_request_duration_seconds',
|
|
133
|
+
help: 'HTTP request duration in seconds',
|
|
134
|
+
labelNames: ['method', 'route'],
|
|
135
|
+
buckets: [0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Express 中间件
|
|
139
|
+
app.use((req, res, next) => {
|
|
140
|
+
const end = httpDuration.startTimer({ method: req.method, route: req.path });
|
|
141
|
+
res.on('finish', () => {
|
|
142
|
+
httpRequests.inc({ method: req.method, route: req.path, status: res.statusCode });
|
|
143
|
+
end();
|
|
144
|
+
});
|
|
145
|
+
next();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// 暴露 /metrics 端点
|
|
149
|
+
app.get('/metrics', async (_, res) => {
|
|
150
|
+
res.set('Content-Type', register.contentType);
|
|
151
|
+
res.end(await register.metrics());
|
|
152
|
+
});
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
**Grafana 告警规则示例(Alertmanager)**
|
|
156
|
+
|
|
157
|
+
\`\`\`yaml
|
|
158
|
+
groups:
|
|
159
|
+
- name: api-alerts
|
|
160
|
+
rules:
|
|
161
|
+
- alert: HighErrorRate
|
|
162
|
+
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
|
|
163
|
+
for: 2m
|
|
164
|
+
labels:
|
|
165
|
+
severity: critical
|
|
166
|
+
annotations:
|
|
167
|
+
summary: "错误率超过 1%,当前: {{ $value | humanizePercentage }}"
|
|
168
|
+
|
|
169
|
+
- alert: SlowP99
|
|
170
|
+
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
|
|
171
|
+
for: 5m
|
|
172
|
+
labels:
|
|
173
|
+
severity: warning
|
|
174
|
+
annotations:
|
|
175
|
+
summary: "P99 延迟超过 1s"
|
|
176
|
+
\`\`\``,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
title: '4. 分布式链路追踪(OpenTelemetry)',
|
|
180
|
+
content: `**OpenTelemetry 是行业标准 —— 一次接入,多后端支持**
|
|
181
|
+
|
|
182
|
+
\`\`\`typescript
|
|
183
|
+
// 初始化 OTel(Node.js)
|
|
184
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
185
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
186
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
187
|
+
|
|
188
|
+
const sdk = new NodeSDK({
|
|
189
|
+
traceExporter: new OTLPTraceExporter({
|
|
190
|
+
url: 'http://otel-collector:4318/v1/traces',
|
|
191
|
+
}),
|
|
192
|
+
instrumentations: [
|
|
193
|
+
getNodeAutoInstrumentations(), // 自动追踪 HTTP/Express/DB
|
|
194
|
+
],
|
|
195
|
+
serviceName: 'order-service',
|
|
196
|
+
});
|
|
197
|
+
sdk.start();
|
|
198
|
+
\`\`\`
|
|
199
|
+
|
|
200
|
+
**手动创建 Span(业务关键路径)**
|
|
201
|
+
|
|
202
|
+
\`\`\`typescript
|
|
203
|
+
import { trace } from '@opentelemetry/api';
|
|
204
|
+
const tracer = trace.getTracer('order-service');
|
|
205
|
+
|
|
206
|
+
async function createOrder(data: OrderData) {
|
|
207
|
+
return tracer.startActiveSpan('order.create', async (span) => {
|
|
208
|
+
try {
|
|
209
|
+
span.setAttributes({
|
|
210
|
+
'order.user_id': data.userId,
|
|
211
|
+
'order.item_count': data.items.length,
|
|
212
|
+
'order.total': data.total,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const order = await db.orders.create(data);
|
|
216
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
217
|
+
return order;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
span.recordException(err as Error);
|
|
220
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
221
|
+
throw err;
|
|
222
|
+
} finally {
|
|
223
|
+
span.end();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
**SLO 定义模板**
|
|
230
|
+
|
|
231
|
+
\`\`\`yaml
|
|
232
|
+
SLO: API 可用性
|
|
233
|
+
SLI: (成功请求数 / 总请求数) * 100%
|
|
234
|
+
目标: ≥ 99.9% (月度 = 允许 43.8 min 故障)
|
|
235
|
+
告警: 1h 内错误预算消耗 > 5% 时 PagerDuty 通知
|
|
236
|
+
\`\`\``,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
outputFormat: 'Markdown 可观测性方案:技术栈选型 + 日志/指标/链路配置代码 + 告警规则 + SLO 定义',
|
|
240
|
+
examples: [
|
|
241
|
+
{
|
|
242
|
+
input: '我的微服务出了问题,但我完全不知道哪里出了问题,怎么建立可观测性?',
|
|
243
|
+
output: `## 可观测性建设方案(从零开始)
|
|
244
|
+
|
|
245
|
+
**优先级排序(按排查价值)**
|
|
246
|
+
1. 🔴 **结构化日志**(1天):用 Pino 替换 console.log,统一 traceId 字段
|
|
247
|
+
2. 🟡 **健康检查 + 黄金信号**(2天):/health + Prometheus 4个核心指标
|
|
248
|
+
3. 🟢 **链路追踪**(3天):OTel 自动插桩,接入 Jaeger
|
|
249
|
+
|
|
250
|
+
**快速 Win:先加这 3 个指标**
|
|
251
|
+
- http_requests_total(按状态码)
|
|
252
|
+
- http_request_duration_p99
|
|
253
|
+
- active_db_connections
|
|
254
|
+
|
|
255
|
+
这 3 个指标已能覆盖 80% 的生产告警场景。`,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
notes: [
|
|
259
|
+
'可观测性要从项目初期建立,生产出了问题再加往往太晚',
|
|
260
|
+
'日志一定要带 traceId,否则微服务间无法串联请求链路',
|
|
261
|
+
'SLO 要与产品/业务方共同制定,不能只是技术侧自说自话',
|
|
262
|
+
'告警要有"降噪"机制(for: 2m),避免毛刺误报打扰团队',
|
|
263
|
+
],
|
|
264
|
+
nextSkill: 'design-patterns',
|
|
265
|
+
};
|
|
266
|
+
//# sourceMappingURL=23-observability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"23-observability.js","sourceRoot":"","sources":["../../src/skills/23-observability.ts"],"names":[],"mappings":";;;AAEa,QAAA,kBAAkB,GAAoB;IACjD,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,eAAe;IACvB,KAAK,EAAE,EAAE;IACT,QAAQ,EAAE,KAAK;IACf,WAAW,EAAE,wCAAwC;IACrD,aAAa,EAAE,kIAAkI;IACjJ,iBAAiB,EAAE;;kBAEH;IAChB,QAAQ,EAAE;QACR,MAAM;QACN,eAAe;QACf,IAAI;QACJ,YAAY;QACZ,IAAI;QACJ,SAAS;QACT,MAAM;QACN,SAAS;QACT,IAAI;QACJ,SAAS;QACT,KAAK;QACL,KAAK;QACL,IAAI;QACJ,UAAU;QACV,WAAW;QACX,sBAAsB;KACvB;IACD,KAAK,EAAE;QACL;YACE,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;kDAuBmC;SAC7C;QACD;YACE,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDR;SACF;QACD;YACE,KAAK,EAAE,kCAAkC;YACzC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4DR;SACF;QACD;YACE,KAAK,EAAE,2BAA2B;YAClC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwDR;SACF;KACF;IACD,YAAY,EAAE,sDAAsD;IACpE,QAAQ,EAAE;QACR;YACE,KAAK,EAAE,mCAAmC;YAC1C,MAAM,EAAE;;;;;;;;;;;;yBAYW;SACpB;KACF;IACD,KAAK,EAAE;QACL,2BAA2B;QAC3B,+BAA+B;QAC/B,8BAA8B;QAC9B,gCAAgC;KACjC;IACD,SAAS,EAAE,iBAAiB;CAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"24-design-patterns.d.ts","sourceRoot":"","sources":["../../src/skills/24-design-patterns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,mBAAmB,EAAE,eA6PjC,CAAC"}
|