异常最佳实践 #
一、异常处理原则 #
1.1 基本原则 #
- 只捕获能处理的异常
- 不要用异常控制流程
- 尽早抛出异常
- 提供有意义的异常信息
1.2 何时抛出异常 #
csharp
public void SetAge(int age)
{
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "年龄必须在0-150之间");
_age = age;
}
public User GetUser(int id)
{
var user = _repository.Find(id);
if (user == null)
throw new UserNotFoundException(id);
return user;
}
1.3 何时捕获异常 #
csharp
public bool TryProcess(string input)
{
try
{
Process(input);
return true;
}
catch (FormatException)
{
return false;
}
}
public void SaveWithRetry(Action saveAction, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
saveAction();
return;
}
catch (TransientException ex) when (i < maxRetries - 1)
{
Thread.Sleep(1000 * (i + 1));
}
}
}
二、避免反模式 #
2.1 空catch块 #
csharp
try
{
Process();
}
catch (Exception)
{
}
try
{
Process();
}
catch (Exception ex)
{
_logger.Error(ex, "处理失败");
}
2.2 捕获所有异常 #
csharp
try
{
Process();
}
catch (Exception)
{
}
try
{
Process();
}
catch (InvalidOperationException ex)
{
HandleInvalidOperation(ex);
}
catch (ArgumentException ex)
{
HandleArgumentError(ex);
}
2.3 异常控制流程 #
csharp
public bool IsNumeric(string value)
{
try
{
int.Parse(value);
return true;
}
catch
{
return false;
}
}
public bool IsNumeric(string value)
{
return int.TryParse(value, out _);
}
2.4 重新抛出问题 #
csharp
try
{
Process();
}
catch (Exception ex)
{
throw ex;
}
try
{
Process();
}
catch (Exception)
{
throw;
}
三、异常与性能 #
3.1 异常的性能开销 #
csharp
public void ProcessWithValidation(string input)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentException("输入不能为空");
if (!IsValidFormat(input))
throw new FormatException("格式无效");
DoProcess(input);
}
public bool TryProcess(string input, out string error)
{
error = null;
if (string.IsNullOrEmpty(input))
{
error = "输入不能为空";
return false;
}
if (!IsValidFormat(input))
{
error = "格式无效";
return false;
}
DoProcess(input);
return true;
}
3.2 Try-Parse模式 #
csharp
public static bool TryParse(string input, out int result)
{
result = 0;
if (string.IsNullOrEmpty(input))
return false;
return int.TryParse(input, out result);
}
public static bool TryGetUser(int id, out User user)
{
user = _repository.Find(id);
return user != null;
}
3.3 避免频繁异常 #
csharp
public decimal Divide(decimal a, decimal b)
{
if (b == 0)
throw new DivideByZeroException();
return a / b;
}
public bool TryDivide(decimal a, decimal b, out decimal result)
{
result = 0;
if (b == 0)
return false;
result = a / b;
return true;
}
四、异常日志记录 #
4.1 基本日志记录 #
csharp
public void Process(string data)
{
try
{
DoProcess(data);
}
catch (Exception ex)
{
_logger.Error(ex, "处理数据失败");
throw;
}
}
4.2 结构化日志 #
csharp
public void Process(Order order)
{
try
{
ValidateOrder(order);
SaveOrder(order);
NotifyCustomer(order);
}
catch (ValidationException ex)
{
_logger.Warning(ex, "订单验证失败 {@Order}", order);
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "订单处理失败 {@OrderId}", order.Id);
throw new OrderProcessingException("订单处理失败", ex);
}
}
4.3 异常过滤器日志 #
csharp
try
{
Process();
}
catch (Exception ex) when (Log(ex))
{
throw;
}
private bool Log(Exception ex)
{
_logger.Error(ex, "操作失败");
return false;
}
五、全局异常处理 #
5.1 应用程序级别 #
csharp
public class Program
{
public static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "应用程序启动失败");
}
}
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Log.Fatal((Exception)e.ExceptionObject, "未处理异常");
}
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Log.Error(e.Exception, "未观察的任务异常");
e.SetObserved();
}
}
5.2 ASP.NET Core中间件 #
csharp
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "请求处理失败");
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
context.Response.ContentType = "application/json";
var response = ex switch
{
ValidationException validationEx => new ErrorResponse
{
Status = 400,
Message = validationEx.Message,
Details = validationEx.ValidationErrors
},
NotFoundException notFoundEx => new ErrorResponse
{
Status = 404,
Message = notFoundEx.Message
},
_ => new ErrorResponse
{
Status = 500,
Message = "服务器内部错误"
}
};
context.Response.StatusCode = response.Status;
await context.Response.WriteAsJsonAsync(response);
}
}
六、异常处理策略 #
6.1 重试策略 #
csharp
public async Task<T> ExecuteWithRetryAsync<T>(
Func<Task<T>> action,
int maxRetries = 3,
TimeSpan? delay = null)
{
var retryCount = 0;
var retryDelay = delay ?? TimeSpan.FromSeconds(1);
while (true)
{
try
{
return await action();
}
catch (Exception ex) when (IsTransient(ex) && retryCount < maxRetries)
{
retryCount++;
_logger.Warning(ex, "操作失败,第{Retry}次重试", retryCount);
await Task.Delay(retryDelay * retryCount);
}
}
}
private static bool IsTransient(Exception ex)
{
return ex is TimeoutException
|| ex is HttpRequestException
|| (ex is SqlException sql && IsTransientSqlError(sql.Number));
}
6.2 断路器模式 #
csharp
public class CircuitBreaker
{
private readonly int _failureThreshold;
private readonly TimeSpan _resetTimeout;
private int _failureCount;
private DateTime _lastFailureTime;
private CircuitState _state = CircuitState.Closed;
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
{
if (_state == CircuitState.Open)
{
if (DateTime.Now - _lastFailureTime > _resetTimeout)
{
_state = CircuitState.HalfOpen;
}
else
{
throw new CircuitBreakerOpenException();
}
}
try
{
var result = await action();
Reset();
return result;
}
catch (Exception ex)
{
TrackFailure();
throw;
}
}
private void TrackFailure()
{
_failureCount++;
_lastFailureTime = DateTime.Now;
if (_failureCount >= _failureThreshold)
{
_state = CircuitState.Open;
}
}
private void Reset()
{
_failureCount = 0;
_state = CircuitState.Closed;
}
}
public enum CircuitState { Closed, Open, HalfOpen }
七、总结 #
异常最佳实践要点:
| 要点 | 说明 |
|---|---|
| 只捕获能处理的 | 避免空catch |
| 不用异常控制流程 | 使用Try-Parse模式 |
| 提供有意义信息 | 包含上下文 |
| 记录异常日志 | 结构化日志 |
| 全局异常处理 | 统一处理 |
下一步,让我们学习文件与IO!
最后更新:2026-03-26