ai-developer-skill-os 8.1.7 → 8.1.9
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/.agents/README.md +3 -3
- package/.agents/skills/qk-code-review/SKILL.md +189 -0
- package/.agents/skills/qk-code-review/references/ai/ai-anti-patterns.md +28 -0
- package/.agents/skills/qk-code-review/references/ai/v8-schema-validation.md +65 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/architecture-review-guide.md +212 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/async-concurrency-patterns.md +515 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/code-quality-universal.md +358 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/code-review-best-practices.md +136 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/common-bugs-checklist.md +124 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/error-handling-principles.md +492 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/n-plus-one-queries.md +309 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/performance-review-guide.md +387 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/security-review-guide.md +318 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/sql-injection-prevention.md +308 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/xss-prevention.md +264 -0
- package/.agents/skills/qk-code-review/references/languages/angular.md +768 -0
- package/.agents/skills/qk-code-review/references/languages/c.md +890 -0
- package/.agents/skills/qk-code-review/references/languages/cpp.md +893 -0
- package/.agents/skills/qk-code-review/references/languages/csharp.md +519 -0
- package/.agents/skills/qk-code-review/references/languages/css-less-sass.md +661 -0
- package/.agents/skills/qk-code-review/references/languages/django.md +985 -0
- package/.agents/skills/qk-code-review/references/languages/fastapi.md +580 -0
- package/.agents/skills/qk-code-review/references/languages/go.md +993 -0
- package/.agents/skills/qk-code-review/references/languages/java.md +409 -0
- package/.agents/skills/qk-code-review/references/languages/java8.md +586 -0
- package/.agents/skills/qk-code-review/references/languages/kotlin.md +1018 -0
- package/.agents/skills/qk-code-review/references/languages/nestjs.md +593 -0
- package/.agents/skills/qk-code-review/references/languages/php.md +684 -0
- package/.agents/skills/qk-code-review/references/languages/python.md +1073 -0
- package/.agents/skills/qk-code-review/references/languages/qt.md +757 -0
- package/.agents/skills/qk-code-review/references/languages/react.md +871 -0
- package/.agents/skills/qk-code-review/references/languages/ruby.md +964 -0
- package/.agents/skills/qk-code-review/references/languages/rust.md +846 -0
- package/.agents/skills/qk-code-review/references/languages/svelte.md +1064 -0
- package/.agents/skills/qk-code-review/references/languages/swift.md +936 -0
- package/.agents/skills/qk-code-review/references/languages/typescript.md +1016 -0
- package/.agents/skills/qk-code-review/references/languages/vue.md +924 -0
- package/.agents/skills/qk-code-review/references/languages/zig.md +440 -0
- package/README.md +3 -3
- package/bin/install.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# 错误处理原则 — 跨语言通用指南
|
|
2
|
+
|
|
3
|
+
> 本文档覆盖错误处理的核心原则、常见反模式、错误层次设计和日志最佳实践。每个原则附带跨语言代码示例。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [核心原则](#核心原则)
|
|
8
|
+
- [反模式](#反模式)
|
|
9
|
+
- [错误层次设计](#错误层次设计)
|
|
10
|
+
- [日志最佳实践](#日志最佳实践)
|
|
11
|
+
- [跨语言代码示例](#跨语言代码示例)
|
|
12
|
+
- [Review Checklist](#review-checklist)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 核心原则
|
|
17
|
+
|
|
18
|
+
### 原则 1: 不要吞掉错误
|
|
19
|
+
|
|
20
|
+
每个错误都必须被处理:向上传播、记录日志、或转换为更有意义的错误。**永远不要**静默忽略。
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
// 伪代码
|
|
24
|
+
result = risky_operation()
|
|
25
|
+
if error:
|
|
26
|
+
// 必须做以下之一:
|
|
27
|
+
// 1. return error to caller(传播)
|
|
28
|
+
// 2. log + return fallback(降级)
|
|
29
|
+
// 3. panic/crash(不可恢复时)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 原则 2: 添加上下文
|
|
33
|
+
|
|
34
|
+
错误信息应包含**操作描述**和**关键参数**,使调试者无需阅读调用链即可定位问题。
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
// ❌ 无上下文
|
|
38
|
+
"failed"
|
|
39
|
+
|
|
40
|
+
// ✅ 有上下文
|
|
41
|
+
"failed to process order #12345: payment gateway timeout after 30s"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 原则 3: 使用特定类型
|
|
45
|
+
|
|
46
|
+
用错误类型区分失败原因,让调用者能精确处理不同的失败场景。
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
// ❌ 通用错误
|
|
50
|
+
throw new Error("something went wrong")
|
|
51
|
+
|
|
52
|
+
// ✅ 特定类型
|
|
53
|
+
throw new OrderNotFoundError(orderId)
|
|
54
|
+
throw new PaymentTimeoutException(gatewayName, timeoutMs)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 原则 4: Fail Fast
|
|
58
|
+
|
|
59
|
+
在操作开始前验证前置条件,尽早失败。这避免了部分执行后才发现错误导致的不一致状态。
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
// ❌ 执行到一半才发现参数无效
|
|
63
|
+
def process(data, config):
|
|
64
|
+
result = expensive_computation(data) # 已花费 5 秒
|
|
65
|
+
if not config.valid:
|
|
66
|
+
raise ValueError("invalid config") # 5 秒白费了
|
|
67
|
+
|
|
68
|
+
// ✅ 先验证
|
|
69
|
+
def process(data, config):
|
|
70
|
+
if not config.valid:
|
|
71
|
+
raise ValueError("invalid config")
|
|
72
|
+
result = expensive_computation(data)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 原则 5: 错误处理只做一次
|
|
76
|
+
|
|
77
|
+
不要在每个层级都处理同一个错误(既 log 又 return 又 wrap)。选择一种方式,让调用者决定如何处理。
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
// ❌ 既 log 又 return(重复处理)
|
|
81
|
+
if err:
|
|
82
|
+
log.error("failed: %s", err)
|
|
83
|
+
return err
|
|
84
|
+
|
|
85
|
+
// ✅ 只包装并返回,让顶层统一处理
|
|
86
|
+
if err:
|
|
87
|
+
return wrap_error("operation failed", err)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 反模式
|
|
93
|
+
|
|
94
|
+
### 反模式 1: 空 catch 块
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# ❌ Python: 空 except 吞掉所有异常(包括 KeyboardInterrupt)
|
|
98
|
+
try:
|
|
99
|
+
result = risky()
|
|
100
|
+
except:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
# ❌ Java: 空 catch 吞掉异常
|
|
104
|
+
try {
|
|
105
|
+
result = risky();
|
|
106
|
+
} catch (Exception e) {
|
|
107
|
+
// 什么都不做
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# ❌ Go: 忽略 error
|
|
111
|
+
result, _ := risky()
|
|
112
|
+
|
|
113
|
+
# ❌ Rust: unwrap() 在生产代码中
|
|
114
|
+
let result = risky().unwrap(); // panic on error
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 反模式 2: 过宽的 catch
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# ❌ 捕获所有异常,无法区分失败类型
|
|
121
|
+
try:
|
|
122
|
+
result = risky()
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"failed: {e}")
|
|
125
|
+
|
|
126
|
+
# ✅ 捕获特定异常
|
|
127
|
+
try:
|
|
128
|
+
result = risky()
|
|
129
|
+
except ConnectionError as e:
|
|
130
|
+
logger.warning(f"network issue, retrying: {e}")
|
|
131
|
+
result = retry(risky)
|
|
132
|
+
except ValueError as e:
|
|
133
|
+
logger.error(f"bad input: {e}")
|
|
134
|
+
raise
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 反模式 3: 丢失原始异常
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# ❌ 丢失了原始异常的堆栈和信息
|
|
141
|
+
try:
|
|
142
|
+
result = external_api.call()
|
|
143
|
+
except APIError as e:
|
|
144
|
+
raise RuntimeError("API failed") # 丢失了原因
|
|
145
|
+
|
|
146
|
+
# ✅ 保留异常链
|
|
147
|
+
try:
|
|
148
|
+
result = external_api.call()
|
|
149
|
+
except APIError as e:
|
|
150
|
+
raise RuntimeError("API failed") from e
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```java
|
|
154
|
+
// ❌ 丢失原始异常
|
|
155
|
+
catch (IOException e) {
|
|
156
|
+
throw new ServiceException("IO failed");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ✅ 保留原因
|
|
160
|
+
catch (IOException e) {
|
|
161
|
+
throw new ServiceException("IO failed", e);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 反模式 4: 用异常做流程控制
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# ❌ 异常做正常流程控制(慢且不清晰)
|
|
169
|
+
try:
|
|
170
|
+
user = users[name]
|
|
171
|
+
except KeyError:
|
|
172
|
+
user = create_default_user(name)
|
|
173
|
+
|
|
174
|
+
# ✅ 显式检查
|
|
175
|
+
user = users.get(name) or create_default_user(name)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```go
|
|
179
|
+
// ❌ Go: panic 做流程控制
|
|
180
|
+
func getUser(id int) User {
|
|
181
|
+
if id <= 0 {
|
|
182
|
+
panic("invalid id")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ✅ Go: 返回 error
|
|
187
|
+
func getUser(id int) (User, error) {
|
|
188
|
+
if id <= 0 {
|
|
189
|
+
return User{}, fmt.Errorf("invalid user id: %d", id)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 反模式 5: 忽略返回值
|
|
195
|
+
|
|
196
|
+
```csharp
|
|
197
|
+
// ❌ 忽略返回的 bool/Result
|
|
198
|
+
dict.TryGetValue("key", out var value);
|
|
199
|
+
// value 可能是默认值,但代码继续执行如同成功
|
|
200
|
+
|
|
201
|
+
// ✅ 检查返回值
|
|
202
|
+
if (!dict.TryGetValue("key", out var value))
|
|
203
|
+
{
|
|
204
|
+
throw new KeyNotFoundException("key not found");
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 错误层次设计
|
|
211
|
+
|
|
212
|
+
### 三层错误架构
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
┌─────────────────────────────────────────────────┐
|
|
216
|
+
│ Application Errors(应用级) │
|
|
217
|
+
│ - AppError / ServiceError │
|
|
218
|
+
│ - 全局异常处理器捕获,返回用户友好的响应 │
|
|
219
|
+
├─────────────────────────────────────────────────┤
|
|
220
|
+
│ Module Errors(模块级) │
|
|
221
|
+
│ - PaymentError, AuthError, ValidationError │
|
|
222
|
+
│ - 每个业务模块定义自己的错误类型 │
|
|
223
|
+
├─────────────────────────────────────────────────┤
|
|
224
|
+
│ Infrastructure Errors(基础设施级) │
|
|
225
|
+
│ - IOError, NetworkError, DatabaseError │
|
|
226
|
+
│ - 来自操作系统、网络、数据库的底层错误 │
|
|
227
|
+
└─────────────────────────────────────────────────┘
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 设计规则
|
|
231
|
+
|
|
232
|
+
1. **模块级错误继承自应用级基类**,便于全局 catch
|
|
233
|
+
2. **基础设施错误在模块边界转换为模块级错误**,不暴露给上层
|
|
234
|
+
3. **每个错误类型包含足够的上下文**用于调试(ID、时间戳、操作名称)
|
|
235
|
+
|
|
236
|
+
### 示例层次(Python)
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
class AppError(Exception):
|
|
240
|
+
"""应用基础异常"""
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
class PaymentError(AppError):
|
|
244
|
+
"""支付模块错误"""
|
|
245
|
+
def __init__(self, order_id: str, reason: str):
|
|
246
|
+
self.order_id = order_id
|
|
247
|
+
super().__init__(f"payment failed for order {order_id}: {reason}")
|
|
248
|
+
|
|
249
|
+
class PaymentGatewayTimeout(PaymentError):
|
|
250
|
+
"""支付网关超时"""
|
|
251
|
+
def __init__(self, order_id: str, gateway: str, timeout_ms: int):
|
|
252
|
+
self.gateway = gateway
|
|
253
|
+
self.timeout_ms = timeout_ms
|
|
254
|
+
super().__init__(order_id, f"gateway {gateway} timed out after {timeout_ms}ms")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 示例层次(Java)
|
|
258
|
+
|
|
259
|
+
```java
|
|
260
|
+
public class AppException extends RuntimeException {
|
|
261
|
+
private final String errorCode;
|
|
262
|
+
public AppException(String errorCode, String message, Throwable cause) {
|
|
263
|
+
super(message, cause);
|
|
264
|
+
this.errorCode = errorCode;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public class OrderNotFoundException extends AppException {
|
|
269
|
+
public OrderNotFoundException(Long orderId) {
|
|
270
|
+
super("ORDER_NOT_FOUND", "Order " + orderId + " not found", null);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 日志最佳实践
|
|
278
|
+
|
|
279
|
+
### 日志级别选择
|
|
280
|
+
|
|
281
|
+
| 级别 | 何时使用 | 示例 |
|
|
282
|
+
|------|---------|------|
|
|
283
|
+
| **ERROR** | 需要人工介入的故障 | 支付失败、数据不一致 |
|
|
284
|
+
| **WARN** | 可自动恢复的异常 | 重试成功、降级处理 |
|
|
285
|
+
| **INFO** | 正常业务事件 | 订单创建、用户登录 |
|
|
286
|
+
| **DEBUG** | 调试信息 | 函数参数、中间状态 |
|
|
287
|
+
|
|
288
|
+
### 日志格式
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
// ❌ 无结构化信息
|
|
292
|
+
log.error("failed to process")
|
|
293
|
+
|
|
294
|
+
// ✅ 结构化信息 + 上下文
|
|
295
|
+
log.error("payment_failed", {
|
|
296
|
+
"order_id": "12345",
|
|
297
|
+
"gateway": "stripe",
|
|
298
|
+
"error_code": "card_declined",
|
|
299
|
+
"amount": 99.99,
|
|
300
|
+
"duration_ms": 2340
|
|
301
|
+
})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 日志安全
|
|
305
|
+
|
|
306
|
+
- **不要记录敏感信息**:密码、token、PII、完整信用卡号
|
|
307
|
+
- **脱敏处理**:`email: a***@example.com`
|
|
308
|
+
- **日志注入防护**:对用户输入做转义,防止伪造日志行
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 跨语言代码示例
|
|
313
|
+
|
|
314
|
+
### Python
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
# ✅ 特定异常 + 上下文 + 异常链
|
|
318
|
+
try:
|
|
319
|
+
response = http_client.post(url, data=payload)
|
|
320
|
+
response.raise_for_status()
|
|
321
|
+
except requests.ConnectionError as e:
|
|
322
|
+
raise PaymentGatewayError(f"cannot reach {gateway_name}") from e
|
|
323
|
+
except requests.HTTPError as e:
|
|
324
|
+
if response.status_code == 429:
|
|
325
|
+
raise RateLimitError(f"rate limited by {gateway_name}") from e
|
|
326
|
+
raise PaymentGatewayError(f"HTTP {response.status_code} from {gateway_name}") from e
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Java
|
|
330
|
+
|
|
331
|
+
```java
|
|
332
|
+
// ✅ 特定异常 + 上下文 + 原因链
|
|
333
|
+
try {
|
|
334
|
+
var response = httpClient.send(request, BodyHandlers.ofString());
|
|
335
|
+
if (response.statusCode() == 404) {
|
|
336
|
+
throw new OrderNotFoundException(orderId);
|
|
337
|
+
}
|
|
338
|
+
} catch (IOException e) {
|
|
339
|
+
throw new PaymentGatewayException(
|
|
340
|
+
"gateway unreachable: " + gatewayUrl, e);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Go
|
|
345
|
+
|
|
346
|
+
```go
|
|
347
|
+
// ✅ 错误包装 + 上下文 + %w 保留链
|
|
348
|
+
result, err := client.Do(req)
|
|
349
|
+
if err != nil {
|
|
350
|
+
return fmt.Errorf("payment gateway %s request failed: %w", gatewayName, err)
|
|
351
|
+
}
|
|
352
|
+
defer result.Body.Close()
|
|
353
|
+
|
|
354
|
+
if result.StatusCode == http.StatusNotFound {
|
|
355
|
+
return fmt.Errorf("order %d not found: %w", orderID, ErrNotFound)
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Rust
|
|
360
|
+
|
|
361
|
+
```rust
|
|
362
|
+
// ✅ thiserror 定义错误类型 + 上下文
|
|
363
|
+
#[derive(Debug, thiserror::Error)]
|
|
364
|
+
enum PaymentError {
|
|
365
|
+
#[error("gateway {gateway} unreachable")]
|
|
366
|
+
GatewayUnreachable {
|
|
367
|
+
gateway: String,
|
|
368
|
+
#[source]
|
|
369
|
+
source: reqwest::Error,
|
|
370
|
+
},
|
|
371
|
+
#[error("order {order_id} not found")]
|
|
372
|
+
OrderNotFound { order_id: u64 },
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async fn process_payment(gateway: &str, order_id: u64) -> Result<(), PaymentError> {
|
|
376
|
+
let response = client.post(url)
|
|
377
|
+
.send()
|
|
378
|
+
.await
|
|
379
|
+
.map_err(|e| PaymentError::GatewayUnreachable {
|
|
380
|
+
gateway: gateway.into(),
|
|
381
|
+
source: e,
|
|
382
|
+
})?;
|
|
383
|
+
Ok(())
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### C#
|
|
388
|
+
|
|
389
|
+
```csharp
|
|
390
|
+
// ✅ 特定异常 + 上下文
|
|
391
|
+
try
|
|
392
|
+
{
|
|
393
|
+
var response = await httpClient.PostAsync(url, content);
|
|
394
|
+
response.EnsureSuccessStatusCode();
|
|
395
|
+
}
|
|
396
|
+
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
|
|
397
|
+
{
|
|
398
|
+
throw new OrderNotFoundException(orderId, ex);
|
|
399
|
+
}
|
|
400
|
+
catch (HttpRequestException ex)
|
|
401
|
+
{
|
|
402
|
+
throw new PaymentGatewayException($"gateway unreachable: {url}", ex);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Swift
|
|
407
|
+
|
|
408
|
+
```swift
|
|
409
|
+
// ✅ Error enum + 上下文
|
|
410
|
+
enum PaymentError: Error {
|
|
411
|
+
case gatewayUnreachable(name: String, underlying: Error)
|
|
412
|
+
case orderNotFound(id: Int)
|
|
413
|
+
case declined(reason: String)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
func processPayment(orderId: Int) throws -> Receipt {
|
|
417
|
+
guard orderId > 0 else {
|
|
418
|
+
throw PaymentError.orderNotFound(id: orderId)
|
|
419
|
+
}
|
|
420
|
+
do {
|
|
421
|
+
let response = try networkClient.post(url, body: payload)
|
|
422
|
+
return try Receipt(from: response)
|
|
423
|
+
} catch let error as NetworkError {
|
|
424
|
+
throw PaymentError.gatewayUnreachable(name: gateway, underlying: error)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### TypeScript
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// ✅ 自定义错误类 + 上下文
|
|
433
|
+
class PaymentError extends Error {
|
|
434
|
+
constructor(
|
|
435
|
+
message: string,
|
|
436
|
+
public readonly orderId: string,
|
|
437
|
+
public readonly gateway: string,
|
|
438
|
+
public readonly cause?: Error,
|
|
439
|
+
) {
|
|
440
|
+
super(message);
|
|
441
|
+
this.name = 'PaymentError';
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function processPayment(orderId: string): Promise<Receipt> {
|
|
446
|
+
try {
|
|
447
|
+
const response = await fetch(url, { method: 'POST', body: payload });
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
throw new PaymentError(
|
|
450
|
+
`gateway returned ${response.status}`,
|
|
451
|
+
orderId,
|
|
452
|
+
gatewayName,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return await response.json();
|
|
456
|
+
} catch (err) {
|
|
457
|
+
if (err instanceof TypeError) {
|
|
458
|
+
throw new PaymentError('gateway unreachable', orderId, gatewayName, err);
|
|
459
|
+
}
|
|
460
|
+
throw err;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Review Checklist
|
|
468
|
+
|
|
469
|
+
### 核心检查
|
|
470
|
+
- [ ] 没有空 catch 块或静默忽略错误
|
|
471
|
+
- [ ] 错误信息包含操作描述和关键参数
|
|
472
|
+
- [ ] 使用特定错误类型(非通用 Error/Exception)
|
|
473
|
+
- [ ] 异常链保留(from / cause / %w)
|
|
474
|
+
- [ ] 前置条件在操作开始前验证(fail fast)
|
|
475
|
+
|
|
476
|
+
### 架构检查
|
|
477
|
+
- [ ] 定义了清晰的错误层次(应用/模块/基础设施)
|
|
478
|
+
- [ ] 全局异常处理器捕获未处理错误
|
|
479
|
+
- [ ] API 边界将内部错误转换为适当的 HTTP 状态码
|
|
480
|
+
|
|
481
|
+
### 日志检查
|
|
482
|
+
- [ ] 错误日志包含结构化上下文
|
|
483
|
+
- [ ] 没有记录敏感信息(密码、token、PII)
|
|
484
|
+
- [ ] 日志级别使用正确(ERROR vs WARN vs INFO)
|
|
485
|
+
|
|
486
|
+
### 语言特定
|
|
487
|
+
- [ ] Go: error 不忽略,使用 `%w` 包装
|
|
488
|
+
- [ ] Python: catch 特定异常,使用 `from` 保留链
|
|
489
|
+
- [ ] Java: 异常有 cause,使用特定类型
|
|
490
|
+
- [ ] Rust: `?` 传播,自定义 Error 类型
|
|
491
|
+
- [ ] C#: when 过滤器,特定异常类型
|
|
492
|
+
- [ ] Swift: do-catch,Result 用于延迟处理
|