看到一些文章说不要使用异常代替正常的控制流,对这个一直都不太清楚,随即查了下,做下笔记。
核心原则:区分预期错误(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}");
}
滥用异常的坏处
- 性能问题,异常处理成本高昂,CLR需要手机堆栈跟踪(Stack Trace)、展开调用栈,相比较条件判断,要慢数百倍。
- 代码可读性低,使用
try-catch
处理正常流程,会让其他人误以为是意外错误,而非预期业务逻辑分支。 - 破坏代码结构,正常的控制流被打断,其他人不得不处理异常,导致代码难以维护
所有业务流程中的错误都不属于异常吗?
非也,本质还是判断错误的性质:是预期内的业务规则失败,还是需要中断当前流程的严重错误。比如:
场景一:某些业务错误需要立即终止当前操作,且不适合通过返回值逐层传递,此时异常是更直接的选择。
场景二:需统一处理的特定错误类型
通过自定义异常,可在全局异常过滤器中统一处理特定业务错误,返回标准化响应。例如:
// 自定义异常
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;}}
}
在判断是否使用自定义异常前,先问自己以下几个问题:
- 是否属于不可恢复的错误?
- 是否需要跨多层传递错误?
- 是否不属于预期内的业务规则失败?
- 是否需要强制调用方处理此错误?
- 发生频率是否很低?
若有半数以上结果为“是”,则说明适合自定义异常。