异常最佳实践 #

一、异常处理原则 #

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