错误处理模式Skill error-handling-patterns

这个技能专注于设计和实施健壮的错误处理策略,包括使用try-catch块、自定义错误类、错误边界、API错误响应格式、重试逻辑、日志记录和监控,以提高软件系统的可靠性、容错能力和用户体验。关键词:错误处理、容错系统、异常管理、日志监控、API错误响应、重试策略、React错误边界、Express错误处理。

架构设计 0 次安装 0 次浏览 更新于 3/18/2026

name: 错误处理模式 description: 设计健壮的错误处理策略,包括try-catch块、自定义错误类、错误边界、优雅降级和全面的日志记录。用于实现异常处理、创建自定义错误类型、在React中设置错误边界、为API设计错误响应格式、实现重试逻辑、处理异步错误和承诺拒绝、记录错误以进行监控、创建用户友好的错误消息,或构建能够优雅失败的容错系统。

错误处理模式 - 健壮的错误管理

何时使用此技能

  • 实现try-catch块进行错误处理
  • 创建具有特定错误代码的自定义错误类
  • 设置React错误边界以进行UI错误恢复
  • 设计一致的API错误响应格式
  • 处理异步错误和承诺拒绝
  • 实现具有指数退避的重试逻辑
  • 记录错误以进行监控和调试
  • 创建用户友好的错误消息
  • 构建失败服务的优雅降级
  • 一致地处理验证错误
  • 使用Sentry或类似工具实现错误跟踪
  • 设计容错系统

何时使用此技能

  • 实现错误处理、设计容错系统、调试生产问题或改进错误消息。
  • 当处理相关任务或功能时
  • 在需要此专业知识的开发过程中

使用时机:实现错误处理、设计容错系统、调试生产问题或改进错误消息。

核心原则

  1. 快速失败,明确失败 - 早期检测错误,使其可见
  2. 从不吞没错误 - 总是记录或适当处理
  3. 可操作的错误消息 - 告诉用户出了什么问题以及如何修复
  4. 类型安全的错误 - 使用自定义错误类
  5. 优雅降级 - 系统在错误情况下保持功能性

错误类型

1. 自定义错误类

// ✅ 基础错误类
export class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public code: string = 'INTERNAL_ERROR',
    public isOperational: boolean = true
  ) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

// ✅ 特定错误类型
export class ValidationError extends AppError {
  constructor(message: string, public fields?: Record<string, string>) {
    super(message, 400, 'VALIDATION_ERROR');
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND');
  }
}

export class UnauthorizedError extends AppError {
  constructor(message: string = '需要认证') {
    super(message, 401, 'UNAUTHORIZED');
  }
}

export class ForbiddenError extends AppError {
  constructor(message: string = '权限不足') {
    super(message, 403, 'FORBIDDEN');
  }
}

export class ConflictError extends AppError {
  constructor(message: string) {
    super(message, 409, 'CONFLICT');
  }
}

export class RateLimitError extends AppError {
  constructor(public retryAfter: number) {
    super(`超出速率限制。请在 ${retryAfter} 秒后重试`, 429, 'RATE_LIMIT');
  }
}

// 使用示例
throw new ValidationError('无效的邮箱格式', {
  email: '必须是有效的邮箱地址'
});

throw new NotFoundError('用户', userId);

throw new ConflictError('邮箱已存在');

2. 结果类型模式

// ✅ 类型安全的错误处理(无异常)
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

function divide(a: number, b: number): Result<number> {
  if (b === 0) {
    return {
      success: false,
      error: new Error('除以零')
    };
  }
  
  return {
    success: true,
    data: a / b
  };
}

// 使用示例
const result = divide(10, 2);

if (result.success) {
  console.log(result.data); // TypeScript知道data存在
} else {
  console.error(result.error); // TypeScript知道error存在
}

// ✅ 异步版本
async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const user = await db.users.findById(id);
    if (!user) {
      return {
        success: false,
        error: new NotFoundError('用户', id)
      };
    }
    return { success: true, data: user };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error : new Error('未知错误')
    };
  }
}

Express错误处理

1. 集中式错误处理器

// ✅ 全局错误中间件
import { Request, Response, NextFunction } from 'express';

export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  // 记录错误
  console.error({
    error: err.message,
    stack: err.stack,
    url: req.url,
    method: req.method,
    body: req.body,
    user: req.user?.id
  });

  // 发送到错误跟踪服务
  if (process.env.NODE_ENV === 'production') {
    Sentry.captureException(err);
  }

  // 处理自定义应用错误
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: {
        code: err.code,
        message: err.message,
        ...(err instanceof ValidationError && { fields: err.fields })
      }
    });
  }

  // 处理数据库错误
  if (err.name === 'PrismaClientKnownRequestError') {
    if (err.code === 'P2002') {
      return res.status(409).json({
        error: {
          code: 'DUPLICATE_ENTRY',
          message: '资源已存在'
        }
      });
    }
  }

  // 默认为500处理未知错误
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production'
        ? '发生意外错误'
        : err.message
    }
  });
}

// 应用到Express应用
app.use(errorHandler);

2. 异步错误包装器

// ✅ 包装器以捕获异步错误
function asyncHandler(fn: Function) {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

// 使用示例 - 无需try/catch!
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.getUser(req.params.id);
  
  if (!user) {
    throw new NotFoundError('用户', req.params.id);
  }
  
  res.json(user);
}));

// 或使用express-async-errors包
import 'express-async-errors';

// 现在异步错误自动捕获
app.get('/users/:id', async (req, res) => {
  const user = await userService.getUser(req.params.id);
  if (!user) {
    throw new NotFoundError('用户', req.params.id);
  }
  res.json(user);
});

React错误处理

1. 错误边界

// ✅ 错误边界组件
import React, { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: (error: Error) => ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = {
    hasError: false,
    error: null
  };

  static getDerivedStateFromError(error: Error): State {
    return {
      hasError: true,
      error
    };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('错误边界捕获:', error, errorInfo);
    
    // 发送到错误跟踪
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack
        }
      }
    });
  }

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback(this.state.error!);
      }
      
      return (
        <div className="error-container">
          <h1>出了点问题</h1>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            重试
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用示例
function App() {
  return (
    <ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
      <MyComponent />
    </ErrorBoundary>
  );
}

2. 异步错误处理

// ✅ 在组件中处理异步错误
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUser() {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
          throw new Error(`获取用户失败:${response.statusText}`);
        }
        
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('未知错误'));
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <NotFound />;

  return <div>{user.name}</div>;
}

// ✅ 或使用React Query进行更好的错误处理
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
  });

  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <NotFound />;

  return <div>{user.name}</div>;
}

重试策略

1. 指数退避

// ✅ 使用指数退避重试
async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  } = {}
): Promise<T> {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    maxDelay = 30000
  } = options;

  let lastError: Error;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error('未知错误');
      
      // 最后一次尝试不重试
      if (attempt === maxRetries) {
        break;
      }
      
      // 计算具有指数退避的延迟
      const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
      
      console.log(`尝试 ${attempt + 1} 失败,${delay}ms后重试...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}

// 使用示例
const user = await fetchWithRetry(() => fetch('/api/user').then(r => r.json()), {
  maxRetries: 3,
  baseDelay: 1000
});

2. 断路器模式

// ✅ 断路器模式
class CircuitBreaker {
  private failureCount = 0;
  private successCount = 0;
  private nextAttempt = Date.now();
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

  constructor(
    private threshold: number = 5,
    private timeout: number = 60000,
    private monitoringPeriod: number = 120000
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('断路器已打开');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    
    if (this.state === 'HALF_OPEN') {
      this.state = 'CLOSED';
      this.successCount = 0;
    }
  }

  private onFailure() {
    this.failureCount++;
    this.successCount = 0;

    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
      console.error('由于失败过多,断路器打开');
    }
  }
}

// 使用示例
const breaker = new CircuitBreaker(5, 60000);

try {
  const data = await breaker.execute(() => fetch('/api/unstable-service'));
} catch (error) {
  console.error('服务不可用');
}

错误监控

1. Sentry集成

// ✅ Sentry设置
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0,
  beforeSend(event, hint) {
    // 过滤敏感数据
    if (event.request) {
      delete event.request.cookies;
      delete event.request.headers?.authorization;
    }
    return event;
  }
});

// 为错误添加上下文
Sentry.setUser({
  id: user.id,
  email: user.email
});

Sentry.setContext('order', {
  id: order.id,
  total: order.total
});

// 捕获带有上下文的异常
try {
  await processPayment(order);
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'payment',
      paymentMethod: order.paymentMethod
    },
    extra: {
      orderId: order.id,
      amount: order.total
    }
  });
  throw error;
}

2. 结构化日志记录

// ✅ 带有错误跟踪的Winston日志记录器
import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// 记录带有上下文的信息
logger.error('支付失败', {
  error: error.message,
  stack: error.stack,
  userId: user.id,
  orderId: order.id,
  amount: order.total
});

// 创建具有默认上下文的子日志记录器
const paymentLogger = logger.child({ service: 'payment' });
paymentLogger.error('交易失败', { transactionId: tx.id });

错误处理检查清单

错误检测:
□ 所有异步函数包装在try/catch中
□ 未处理的承诺拒绝被捕获
□ 输入点在入口处验证
□ 数据库错误已处理
□ 网络错误已处理

错误类型:
□ 为不同场景使用自定义错误类
□ 区分操作性和程序员错误
□ HTTP状态码适当
□ API中错误代码一致

错误消息:
□ 用户友好的消息(无技术术语)
□ 可操作(告诉用户该做什么)
□ 不暴露敏感数据
□ 对开发调试有帮助
□ 生产环境中通用

错误恢复:
□ 为临时失败实现重试逻辑
□ 为外部服务使用断路器
□ 优雅降级策略
□ 在适当时使用回退值
□ 在finally块中进行清理

监控:
□ 配置错误跟踪服务(Sentry等)
□ 实现结构化日志记录
□ 监控错误率
□ 关键错误警报
□ 错误报告中包含上下文

资源


记住:良好的错误处理使调试更容易、用户更满意、系统更可靠。优雅失败、全面记录,并始终提供可操作的反馈。