# 2.2 异常处理 --- ## 【强制】 1. **业务异常处理**: * **优先校验**:应优先使用条件判断(`if`)来检查业务规则是否满足,在发现问题时提前拦截。 * **抛出异常**:当校验不通过需中断流程时,应抛出 `BusinessException` 或其子类,而不是 `System.Exception`。 * **消息友好**:异常消息(Message)将直接作为错误提示返回给前端/客户端,必须确保消息内容用户友好且无敏感技术信息。 * **记录异常**:在业务逻辑中抛出异常时,必须记录异常信息和堆栈到日志中,以便后续分析和排查问题。 * *正例*: ```csharp if (order.Status != OrderStatus.Created) { throw new BusinessException("当前订单状态不允许取消"); } ``` 2. **全局异常处理**: * **中间件接管**:严禁在 Controller 层进行通用的 `try-catch` 捕获。Aegis 框架已内置全局异常处理中间件,会自动捕获未处理异常并转换为标准 JSON 响应。 * **响应格式**:框架会将未处理异常统一转换为如下格式: ```json { "code": 500, "message": "服务器内部错误 / 业务错误提示", "data": null, "success": false } ``` 3. **必须通过预检查规避可预见的异常**:对于 `NullReferenceException`, `ArgumentNullException`, `IndexOutOfRangeException` 等可以通过代码检查来避免的运行时异常,不应该使用 `try-catch` 来处理。 *说明*:异常处理的性能开销远高于条件判断。编写健壮的代码,通过防御性编程(如卫语句)来处理可预见的问题,是程序员的基本素养。 *反例*: ```csharp try { // obj 可能为 null obj.DoSomething(); } catch (NullReferenceException ex) { _logger.LogError(ex, "obj was null"); } ``` *正例*: ```csharp if (obj is null) { throw new ArgumentNullException(nameof(obj)); } obj.DoSomething(); ``` 2. **禁止使用异常作为流程控制**:异常是为程序运行中的意外情况设计的,不应用于正常的业务逻辑判断和流程跳转。 3. **精确捕获异常类型**:`catch` 时应尽可能捕获具体的异常类型,而不是宽泛的 `System.Exception`。对不同的异常类型应有不同的处理逻辑。 *说明*:对大段代码进行笼统的 `try-catch`,会使程序无法对特定错误做出正确响应,不利于问题定位和系统稳定性。 *正例*: ```csharp try { // ... 数据库操作 } catch (SqlException ex) { // 处理数据库相关的特定错误 _logger.LogError(ex, "A database error occurred."); } catch (TimeoutException ex) { // 处理连接超时错误 _logger.LogWarning(ex, "The database connection timed out."); } ``` 4. **捕获后必须处理或转抛**:捕获异常后,必须进行处理(如记录日志、事务回滚、向用户返回错误信息等)。如果当前层无法处理,应使用 `throw;` 语句将原始异常向上抛出。严禁“吞掉”异常(即空的 `catch` 块)。 5. **必须使用 `using` 语句管理资源**:对于实现了 `IDisposable` 接口的资源对象(如流、数据库连接、HttpClient 等),必须使用 `using` 语句来确保资源的正确释放。 *说明*:`using` 语句会自动生成 `try-finally` 块,并调用 `Dispose()` 方法,这是 .NET 中管理非托管资源的最安全、最简洁的方式。 *正例*: ```csharp using (var stream = new FileStream("path/to/file", FileMode.Open)) { // ... 对 stream 的操作 } // 此处 stream 已被自动关闭和释放 ``` 6. **禁止在 `finally` 块中使用 `return`**:`finally` 块中的 `return` 会覆盖 `try` 或 `catch` 块中的 `return` 或异常,导致非预期的行为。 ## 【推荐】 1. **启用并遵循可空引用类型 (`Nullable Reference Types`)**:在项目(`.csproj`)中启用 `enable`,让编译器帮助你从静态层面发现潜在的 `NullReferenceException`。 2. **优雅地处理空引用**: - **级联调用**:使用空条件运算符 `?.` 和 `?[]` 来安全地访问可能为 null 的对象的成员或索引。 *正例*: `string? postalCode = customer?.Address?.PostalCode;` - **返回值**:方法的返回值可以为 null,但必须在方法签名和 XML 注释中明确指出。调用方有责任处理 null 情况。 3. **使用自定义异常表达业务错误**:应定义有具体业务含义的自定义异常(继承自 `System.Exception`),而不是直接抛出泛泛的 `new Exception()`。 *正例*: ```csharp 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 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。