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

123 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`)中启用 `<Nullable>enable</Nullable>`,让编译器帮助你从静态层面发现潜在的 `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 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。