diff --git a/docs/ai-reading/step3-code-quality.md b/docs/ai-reading/step3-code-quality.md index 5336eb6..b06d718 100644 --- a/docs/ai-reading/step3-code-quality.md +++ b/docs/ai-reading/step3-code-quality.md @@ -177,6 +177,227 @@ private validateUserData(userData: CreateUserDto | UpdateUserDto): void { } ``` +## 🚨 异常处理完整性检查(关键规范) + +### 问题定义 +**异常吞没(Exception Swallowing)** 是指在 catch 块中捕获异常后,只记录日志但不重新抛出,导致: +- 调用方无法感知错误 +- 方法返回 undefined 而非声明的类型 +- 数据不一致或静默失败 +- 难以调试和定位问题 + +### 检查规则 + +#### 规则1:catch 块必须有明确的异常处理策略 +```typescript +// ❌ 严重错误:catch 块吞没异常 +async create(createDto: CreateDto): Promise { + try { + const result = await this.repository.create(createDto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('创建失败', error); + // 错误:没有 throw,方法返回 undefined + // 但声明返回 Promise + } +} + +// ❌ 错误:只记录日志不处理 +async findById(id: string): Promise { + try { + return await this.repository.findById(id); + } catch (error) { + monitor.error(error); + // 错误:异常被吞没,调用方无法感知 + } +} + +// ✅ 正确:重新抛出异常 +async create(createDto: CreateDto): Promise { + try { + const result = await this.repository.create(createDto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('创建失败', error); + throw error; // 必须重新抛出 + } +} + +// ✅ 正确:转换为特定异常类型 +async create(createDto: CreateDto): Promise { + try { + const result = await this.repository.create(createDto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('创建失败', error); + if (error.message.includes('duplicate')) { + throw new ConflictException('记录已存在'); + } + throw error; // 其他错误继续抛出 + } +} + +// ✅ 正确:返回错误响应(仅限顶层API) +async create(createDto: CreateDto): Promise> { + try { + const result = await this.repository.create(createDto); + return { success: true, data: this.toResponseDto(result) }; + } catch (error) { + this.logger.error('创建失败', error); + return { + success: false, + error: error.message, + errorCode: 'CREATE_FAILED' + }; // 顶层API可以返回错误响应 + } +} +``` + +#### 规则2:Service 层方法必须传播异常 +```typescript +// ❌ 错误:Service 层吞没异常 +@Injectable() +export class UserService { + async update(id: string, dto: UpdateDto): Promise { + try { + const result = await this.repository.update(id, dto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('更新失败', { id, error }); + // 错误:Service 层不应吞没异常 + } + } +} + +// ✅ 正确:Service 层传播异常 +@Injectable() +export class UserService { + async update(id: string, dto: UpdateDto): Promise { + try { + const result = await this.repository.update(id, dto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('更新失败', { id, error }); + throw error; // 传播给调用方处理 + } + } +} +``` + +#### 规则3:Repository 层必须传播数据库异常 +```typescript +// ❌ 错误:Repository 层吞没数据库异常 +@Injectable() +export class UserRepository { + async findById(id: bigint): Promise { + try { + return await this.repository.findOne({ where: { id } }); + } catch (error) { + this.logger.error('查询失败', { id, error }); + // 错误:数据库异常被吞没,调用方以为查询成功但返回 null + } + } +} + +// ✅ 正确:Repository 层传播异常 +@Injectable() +export class UserRepository { + async findById(id: bigint): Promise { + try { + return await this.repository.findOne({ where: { id } }); + } catch (error) { + this.logger.error('查询失败', { id, error }); + throw error; // 数据库异常必须传播 + } + } +} +``` + +### 异常处理层级规范 + +| 层级 | 异常处理策略 | 说明 | +|------|-------------|------| +| **Repository 层** | 必须 throw | 数据访问异常必须传播 | +| **Service 层** | 必须 throw | 业务异常必须传播给调用方 | +| **Business 层** | 必须 throw | 业务逻辑异常必须传播 | +| **Gateway/Controller 层** | 可以转换为 HTTP 响应 | 顶层可以将异常转换为错误响应 | + +### 检查清单 + +- [ ] **所有 catch 块是否有 throw 语句?** +- [ ] **方法返回类型与实际返回是否一致?**(避免返回 undefined) +- [ ] **Service/Repository 层是否传播异常?** +- [ ] **只有顶层 API 才能将异常转换为错误响应?** +- [ ] **异常日志是否包含足够的上下文信息?** + +### 快速检查命令 +```bash +# 搜索可能吞没异常的 catch 块(没有 throw 的 catch) +# 在代码审查时重点关注这些位置 +grep -rn "catch.*error" --include="*.ts" | grep -v "throw" +``` + +### 常见错误模式 + +#### 模式1:性能监控后忘记抛出 +```typescript +// ❌ 常见错误 +} catch (error) { + monitor.error(error); // 只记录监控 + // 忘记 throw error; +} + +// ✅ 正确 +} catch (error) { + monitor.error(error); + throw error; // 必须抛出 +} +``` + +#### 模式2:条件分支遗漏 throw +```typescript +// ❌ 常见错误 +} catch (error) { + if (error.code === 'DUPLICATE') { + throw new ConflictException('已存在'); + } + // else 分支忘记 throw + this.logger.error(error); +} + +// ✅ 正确 +} catch (error) { + if (error.code === 'DUPLICATE') { + throw new ConflictException('已存在'); + } + this.logger.error(error); + throw error; // else 分支也要抛出 +} +``` + +#### 模式3:返回类型不匹配 +```typescript +// ❌ 错误:声明返回 Promise 但可能返回 undefined +async findById(id: string): Promise { + try { + return await this.repo.findById(id); + } catch (error) { + this.logger.error(error); + // 没有 throw,TypeScript 不会报错但运行时返回 undefined + } +} + +// ✅ 正确 +async findById(id: string): Promise { + try { + return await this.repo.findById(id); + } catch (error) { + this.logger.error(error); + throw error; + } +} +``` + ## 🚫 TODO项处理(强制要求) ### 处理原则 @@ -323,12 +544,19 @@ describe('AdminService Properties', () => { - 抽象为可复用的工具方法 - 消除代码重复 -6. **处理所有TODO项** +6. **🚨 检查异常处理完整性(关键步骤)** + - 扫描所有 catch 块 + - 检查是否有 throw 语句 + - 验证 Service/Repository 层是否传播异常 + - 确认方法返回类型与实际返回一致 + - 识别异常吞没模式并修复 + +7. **处理所有TODO项** - 搜索所有TODO注释 - 要求真正实现功能或删除代码 - 确保最终文件无TODO项 -7. **游戏服务器特殊检查** +8. **游戏服务器特殊检查** - WebSocket连接管理完整性 - 双模式服务行为一致性 - 属性测试实现质量