1. 为什么我们需要摆脱手动 try-catch
在十多年的编码生涯里,我见过太多这样的代码块:
try { const result = someRiskyOperation(); // 业务逻辑代码... } catch (error) { console.error('操作失败:', error); // 可能还有各种错误处理分支 }这种模式的问题在于:它把业务逻辑和错误处理强耦合在一起。当项目规模扩大时,你会发现:
- 代码中遍布着重复的异常处理逻辑
- 核心业务逻辑被错误处理代码淹没
- 不同类型的错误难以统一管理
- 测试用例变得臃肿难维护
2. 现代异常处理方案解析
2.1 装饰器方案
在 TypeScript/JavaScript 生态中,装饰器提供了一种优雅的解决方案:
function errorHandler(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function(...args: any[]) { try { return await originalMethod.apply(this, args); } catch (error) { // 统一的错误处理逻辑 this.handleError(error); } }; return descriptor; } class APIService { @errorHandler async fetchData() { // 业务逻辑... } private handleError(error: Error) { // 集中式错误处理 } }实际项目中,我们可以扩展这个装饰器,支持:
- 错误类型分类处理
- 自动重试机制
- 错误上报集成
2.2 函数式方案
对于函数式编程风格,我们可以实现高阶函数:
const withErrorHandling = (fn) => async (...args) => { try { return await fn(...args); } catch (error) { // 可以在这里添加上下文信息 error.context = { args }; throw error; // 或者返回统一的错误格式 } }; // 使用示例 const safeFetchUser = withErrorHandling(async (userId) => { // 原始业务逻辑 });这种方案的优点在于:
- 保持函数的纯净性
- 方便组合和重用
- 适合函数式编程范式
3. 高级错误处理模式
3.1 错误分类系统
建立清晰的错误类型体系至关重要:
class AppError extends Error { constructor( public readonly code: string, public readonly details?: Record<string, any> ) { super(); } } class NetworkError extends AppError { constructor(details?: { url: string; status: number }) { super('NETWORK_ERROR', details); } } // 使用示例 throw new NetworkError({ url: '/api/users', status: 500 });3.2 错误边界模式
借鉴 React 的错误边界概念,我们可以实现:
class ErrorBoundary { static async execute<T>(fn: () => Promise<T>): Promise<T | ErrorResponse> { try { return await fn(); } catch (error) { if (error instanceof AppError) { return this.handleKnownError(error); } return this.handleUnknownError(error); } } private static handleKnownError(error: AppError) { // 根据错误类型返回特定响应 } }4. 实战中的经验技巧
4.1 错误上下文增强
在异步操作中,错误经常丢失关键上下文。解决方案:
async function withContext(fn, context) { try { return await fn(); } catch (error) { error.context = { ...error.context, ...context }; throw error; } } // 使用示例 await withContext(() => fetchUser(userId), { userId, requestId: generateId() });4.2 错误恢复策略
对于可恢复错误,实现智能重试:
async function withRetry<T>( fn: () => Promise<T>, options: { maxRetries: number; retryDelay: number; shouldRetry: (error: Error) => boolean; } ): Promise<T> { let attempts = 0; while (true) { try { return await fn(); } catch (error) { if (attempts >= options.maxRetries || !options.shouldRetry(error)) { throw error; } await new Promise(resolve => setTimeout(resolve, options.retryDelay) ); attempts++; } } }5. 测试策略优化
5.1 错误注入测试
describe('API Service', () => { it('should handle network errors', async () => { const mockFetch = jest.fn().mockRejectedValue( new NetworkError({ status: 500 }) ); const service = new APIService(mockFetch); const result = await service.fetchData(); expect(result).toEqual({ success: false, code: 'NETWORK_ERROR' }); }); });5.2 错误覆盖率检查
配置 Jest 或其它测试框架,确保:
- 所有定义的错误类型都被测试用例覆盖
- 每个错误分支都有对应的测试
- 错误消息格式符合规范
6. 监控与日志集成
6.1 结构化错误日志
interface ErrorLog { timestamp: string; code: string; message: string; stack?: string; context?: Record<string, any>; severity: 'low' | 'medium' | 'high'; } function logError(error: Error): ErrorLog { const entry: ErrorLog = { timestamp: new Date().toISOString(), message: error.message, severity: 'medium' }; if (error instanceof AppError) { entry.code = error.code; entry.context = error.details; } // 发送到日志服务 logger.send(entry); return entry; }6.2 错误聚合分析
使用 Sentry 或类似工具时,配置:
- 错误分组规则
- 严重程度自动分类
- 上下文信息自动附加
- 用户反馈收集
7. 性能考量
错误处理引入的性能开销主要来自:
- 调用栈收集
- 上下文序列化
- 日志写入
优化建议:
- 生产环境限制调用栈深度
- 异步非阻塞的日志写入
- 敏感信息过滤前置
8. 渐进式迁移策略
对于已有项目,推荐迁移路径:
- 先在新代码中使用新方案
- 逐步重构高频修改的模块
- 最后处理稳定不变的遗留代码
- 建立代码规范防止倒退
关键指标监控:
- 未处理错误数量
- 错误处理代码重复率
- 错误解决平均时间