5.4 KiB
2.2 异常处理
【强制】
-
业务异常处理:
- 优先校验:应优先使用条件判断(
if)来检查业务规则是否满足,在发现问题时提前拦截。 - 抛出异常:当校验不通过需中断流程时,应抛出
BusinessException或其子类,而不是System.Exception。 - 消息友好:异常消息(Message)将直接作为错误提示返回给前端/客户端,必须确保消息内容用户友好且无敏感技术信息。
- 记录异常:在业务逻辑中抛出异常时,必须记录异常信息和堆栈到日志中,以便后续分析和排查问题。
- 正例:
if (order.Status != OrderStatus.Created) { throw new BusinessException("当前订单状态不允许取消"); }
- 优先校验:应优先使用条件判断(
-
全局异常处理:
- 中间件接管:严禁在 Controller 层进行通用的
try-catch捕获。Aegis 框架已内置全局异常处理中间件,会自动捕获未处理异常并转换为标准 JSON 响应。 - 响应格式:框架会将未处理异常统一转换为如下格式:
{ "code": 500, "message": "服务器内部错误 / 业务错误提示", "data": null, "success": false }
- 中间件接管:严禁在 Controller 层进行通用的
-
必须通过预检查规避可预见的异常:对于
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(); -
禁止使用异常作为流程控制:异常是为程序运行中的意外情况设计的,不应用于正常的业务逻辑判断和流程跳转。
-
精确捕获异常类型:
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."); } -
捕获后必须处理或转抛:捕获异常后,必须进行处理(如记录日志、事务回滚、向用户返回错误信息等)。如果当前层无法处理,应使用
throw;语句将原始异常向上抛出。严禁“吞掉”异常(即空的catch块)。 -
必须使用
using语句管理资源:对于实现了IDisposable接口的资源对象(如流、数据库连接、HttpClient 等),必须使用using语句来确保资源的正确释放。说明:
using语句会自动生成try-finally块,并调用Dispose()方法,这是 .NET 中管理非托管资源的最安全、最简洁的方式。正例:
using (var stream = new FileStream("path/to/file", FileMode.Open)) { // ... 对 stream 的操作 } // 此处 stream 已被自动关闭和释放 -
禁止在
finally块中使用return:finally块中的return会覆盖try或catch块中的return或异常,导致非预期的行为。
【推荐】
-
启用并遵循可空引用类型 (
Nullable Reference Types):在项目(.csproj)中启用<Nullable>enable</Nullable>,让编译器帮助你从静态层面发现潜在的NullReferenceException。 -
优雅地处理空引用:
- 级联调用:使用空条件运算符
?.和?[]来安全地访问可能为 null 的对象的成员或索引。 正例:string? postalCode = customer?.Address?.PostalCode; - 返回值:方法的返回值可以为 null,但必须在方法签名和 XML 注释中明确指出。调用方有责任处理 null 情况。
- 级联调用:使用空条件运算符
-
使用自定义异常表达业务错误:应定义有具体业务含义的自定义异常(继承自
System.Exception),而不是直接抛出泛泛的new Exception()。正例:
public class OrderNotFoundException : Exception { public OrderNotFoundException(int orderId) : base($"Order with ID {orderId} was not found.") { } }
【参考】
- API 的错误响应:对于对外暴露的 API(如 ASP.NET Core Web API),应建立一个全局异常处理中间件。该中间件负责捕获所有未处理的异常,记录日志,并将其转换为统一、标准的 JSON 错误响应格式返回给客户端,而不是直接暴露异常堆栈信息。