自定义异常的使用场景

看到一些文章说不要使用异常代替正常的控制流,对这个一直都不太清楚,随即查了下,做下笔记。

核心原则:区分预期错误(Expected Errors)意外异常(Unexpected Exceptions)

预期错误应当显示处理:输入验证、业务规则检查等都属于预期内的错误,应该通过返回值、状态码或结果对象明确传递。

假设需要验证用户输入内容合法性,有两种方式:

方式一:使用异常处理,不好的做法,会使调用方处理起来麻烦

// 通过抛出异常表示验证失败
public void ValidateEmail(string email)
{
    if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
    {
        throw new InvalidEmailException("邮箱格式错误");
    }
}

// 调用方必须用 try-catch 处理正常逻辑
try
{
    ValidateEmail("invalid-email");
    Console.WriteLine("邮箱有效");
}
catch (InvalidEmailException ex)
{
    Console.WriteLine($"验证失败: {ex.Message}");
}

方式二:返回明确结果

// 返回一个结果对象,包含是否成功和错误信息
public ValidationResult ValidateEmail(string email)
{
    bool isValid = Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
    return new ValidationResult
    {
        IsValid = isValid,
        ErrorMessage = isValid ? null : "邮箱格式错误"
    };
}

// 调用方直接处理结果,无需 try-catch
var result = ValidateEmail("invalid-email");
if (result.IsValid)
{
    Console.WriteLine("邮箱有效");
}
else
{
    Console.WriteLine($"验证失败: {result.ErrorMessage}");
}

滥用异常的坏处

  1. 性能问题,异常处理成本高昂,CLR需要手机堆栈跟踪(Stack Trace)、展开调用栈,相比较条件判断,要慢数百倍。
  2. 代码可读性低,使用try-catch处理正常流程,会让其他人误以为是意外错误,而非预期业务逻辑分支。
  3. 破坏代码结构,正常的控制流被打断,其他人不得不处理异常,导致代码难以维护

所有业务流程中的错误都不属于异常吗?

非也,本质还是判断错误的性质:是预期内的业务规则失败,还是需要中断当前流程的严重错误。比如:

场景一:某些业务错误需要立即终止当前操作,且不适合通过返回值逐层传递,此时异常是更直接的选择。
场景二:需统一处理的特定错误类型
通过自定义异常,可在全局异常过滤器中统一处理特定业务错误,返回标准化响应。例如:

// 自定义异常
public class InsufficientBalanceException : Exception
{
    public decimal RequiredAmount { get; }
    public InsufficientBalanceException(decimal required) 
        : base($"余额不足,需支付 {required}") 
        => RequiredAmount = required;
}

// 全局异常过滤器
public class BusinessExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is InsufficientBalanceException ex)
        {
            context.Result = new JsonResult(new
            {
                Code = 400,
                Message = ex.Message,
                RequiredAmount = ex.RequiredAmount
            });
            context.ExceptionHandled = true;
        }
    }
}

在判断是否使用自定义异常前,先问自己以下几个问题:

  1. 是否属于不可恢复的错误?
  2. 是否需要跨多层传递错误?
  3. 是否不属于预期内的业务规则失败?
  4. 是否需要强制调用方处理此错误?
  5. 发生频率是否很低?
    若有半数以上结果为“是”,则说明适合自定义异常。
posted @ 2025-02-23 18:08  南山有榛  阅读(53)  评论(0)    收藏  举报