forked from datawhale/whale-town-end
docs:添加异常处理完整性检查规范
- 新增异常吞没问题定义和检查规则 - 添加Service/Repository层异常传播规范 - 补充常见错误模式和修复示例 - 更新检查清单和执行步骤顺序
This commit is contained in:
@@ -177,6 +177,227 @@ private validateUserData(userData: CreateUserDto | UpdateUserDto): void {
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 异常处理完整性检查(关键规范)
|
||||
|
||||
### 问题定义
|
||||
**异常吞没(Exception Swallowing)** 是指在 catch 块中捕获异常后,只记录日志但不重新抛出,导致:
|
||||
- 调用方无法感知错误
|
||||
- 方法返回 undefined 而非声明的类型
|
||||
- 数据不一致或静默失败
|
||||
- 难以调试和定位问题
|
||||
|
||||
### 检查规则
|
||||
|
||||
#### 规则1:catch 块必须有明确的异常处理策略
|
||||
```typescript
|
||||
// ❌ 严重错误:catch 块吞没异常
|
||||
async create(createDto: CreateDto): Promise<ResponseDto> {
|
||||
try {
|
||||
const result = await this.repository.create(createDto);
|
||||
return this.toResponseDto(result);
|
||||
} catch (error) {
|
||||
this.logger.error('创建失败', error);
|
||||
// 错误:没有 throw,方法返回 undefined
|
||||
// 但声明返回 Promise<ResponseDto>
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 错误:只记录日志不处理
|
||||
async findById(id: string): Promise<Entity> {
|
||||
try {
|
||||
return await this.repository.findById(id);
|
||||
} catch (error) {
|
||||
monitor.error(error);
|
||||
// 错误:异常被吞没,调用方无法感知
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确:重新抛出异常
|
||||
async create(createDto: CreateDto): Promise<ResponseDto> {
|
||||
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<ResponseDto> {
|
||||
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<ApiResponse<ResponseDto>> {
|
||||
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<ResponseDto> {
|
||||
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<ResponseDto> {
|
||||
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<User | null> {
|
||||
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<User | null> {
|
||||
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<Entity> 但可能返回 undefined
|
||||
async findById(id: string): Promise<Entity> {
|
||||
try {
|
||||
return await this.repo.findById(id);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
// 没有 throw,TypeScript 不会报错但运行时返回 undefined
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 正确
|
||||
async findById(id: string): Promise<Entity> {
|
||||
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连接管理完整性
|
||||
- 双模式服务行为一致性
|
||||
- 属性测试实现质量
|
||||
|
||||
Reference in New Issue
Block a user