mi-assessment/docs/开发规范/2-异常日志/2.2-异常处理.md
2026-02-03 20:50:51 +08:00

5.4 KiB
Raw Blame History

2.2 异常处理


【强制】

  1. 业务异常处理

    • 优先校验:应优先使用条件判断(if)来检查业务规则是否满足,在发现问题时提前拦截。
    • 抛出异常:当校验不通过需中断流程时,应抛出 BusinessException 或其子类,而不是 System.Exception
    • 消息友好异常消息Message将直接作为错误提示返回给前端/客户端,必须确保消息内容用户友好且无敏感技术信息。
    • 记录异常:在业务逻辑中抛出异常时,必须记录异常信息和堆栈到日志中,以便后续分析和排查问题。
    • 正例
      if (order.Status != OrderStatus.Created)
      {
          throw new BusinessException("当前订单状态不允许取消");
      }
      
  2. 全局异常处理

    • 中间件接管:严禁在 Controller 层进行通用的 try-catch 捕获。Aegis 框架已内置全局异常处理中间件,会自动捕获未处理异常并转换为标准 JSON 响应。
    • 响应格式:框架会将未处理异常统一转换为如下格式:
      {
        "code": 500,
        "message": "服务器内部错误 / 业务错误提示",
        "data": null,
        "success": false
      }
      
  3. 必须通过预检查规避可预见的异常:对于 NullReferenceException, ArgumentNullException, IndexOutOfRangeException 等可以通过代码检查来避免的运行时异常,不应该使用 try-catch 来处理。

    说明:异常处理的性能开销远高于条件判断。编写健壮的代码,通过防御性编程(如卫语句)来处理可预见的问题,是程序员的基本素养。

    反例

    try
    {
        // obj 可能为 null
        obj.DoSomething();
    }
    catch (NullReferenceException ex)
    {
        _logger.LogError(ex, "obj was null");
    }
    

    正例

    if (obj is null)
    {
        throw new ArgumentNullException(nameof(obj));
    }
    obj.DoSomething();
    
  4. 禁止使用异常作为流程控制:异常是为程序运行中的意外情况设计的,不应用于正常的业务逻辑判断和流程跳转。

  5. 精确捕获异常类型catch 时应尽可能捕获具体的异常类型,而不是宽泛的 System.Exception。对不同的异常类型应有不同的处理逻辑。

    说明:对大段代码进行笼统的 try-catch,会使程序无法对特定错误做出正确响应,不利于问题定位和系统稳定性。

    正例

    try
    {
        // ... 数据库操作
    }
    catch (SqlException ex)
    {
        // 处理数据库相关的特定错误
        _logger.LogError(ex, "A database error occurred.");
    }
    catch (TimeoutException ex)
    {
        // 处理连接超时错误
        _logger.LogWarning(ex, "The database connection timed out.");
    }
    
  6. 捕获后必须处理或转抛:捕获异常后,必须进行处理(如记录日志、事务回滚、向用户返回错误信息等)。如果当前层无法处理,应使用 throw; 语句将原始异常向上抛出。严禁“吞掉”异常(即空的 catch 块)。

  7. 必须使用 using 语句管理资源:对于实现了 IDisposable 接口的资源对象如流、数据库连接、HttpClient 等),必须使用 using 语句来确保资源的正确释放。

    说明using 语句会自动生成 try-finally 块,并调用 Dispose() 方法,这是 .NET 中管理非托管资源的最安全、最简洁的方式。

    正例

    using (var stream = new FileStream("path/to/file", FileMode.Open))
    {
        // ... 对 stream 的操作
    }
    // 此处 stream 已被自动关闭和释放
    
  8. 禁止在 finally 块中使用 returnfinally 块中的 return 会覆盖 trycatch 块中的 return 或异常,导致非预期的行为。

【推荐】

  1. 启用并遵循可空引用类型 (Nullable Reference Types):在项目(.csproj)中启用 <Nullable>enable</Nullable>,让编译器帮助你从静态层面发现潜在的 NullReferenceException

  2. 优雅地处理空引用

    • 级联调用:使用空条件运算符 ?.?[] 来安全地访问可能为 null 的对象的成员或索引。 正例: string? postalCode = customer?.Address?.PostalCode;
    • 返回值:方法的返回值可以为 null但必须在方法签名和 XML 注释中明确指出。调用方有责任处理 null 情况。
  3. 使用自定义异常表达业务错误:应定义有具体业务含义的自定义异常(继承自 System.Exception),而不是直接抛出泛泛的 new Exception()

    正例

    public class OrderNotFoundException : Exception
    {
        public OrderNotFoundException(int orderId) 
            : base($"Order with ID {orderId} was not found.") { }
    }
    

【参考】

  1. API 的错误响应:对于对外暴露的 API如 ASP.NET Core Web API应建立一个全局异常处理中间件。该中间件负责捕获所有未处理的异常记录日志并将其转换为统一、标准的 JSON 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。