告别异常臃肿!C# Result模式实战:用.NET构建更优雅的错误处理系统

作者:微信公众号:【架构师老卢】
3-15 15:59
46

现代软件开发中的错误处理挑战

在.NET开发中,异常处理虽广泛使用,但可能带来性能损耗与代码复杂度。本文将揭秘如何通过Result模式替代异常,构建高可读、易维护且性能更优的.NET应用。


.NET异常处理的典型困境

传统异常处理依赖try/catch代码块,但存在显著缺陷:

public async Task<ShipmentResponse> CreateAsync(
    CreateShipmentCommand request,
    CancellationToken cancellationToken)
{
    // 检查订单是否已存在
    if (await CheckExistingShipment(request.OrderId))
    {
        throw new ShipmentAlreadyExistsException(request.OrderId); // 异常中断流程
    }
    
    // 正常业务逻辑
    var shipment = CreateNewShipment(request);
    return shipment.MapToResponse();
}

痛点分析
不可预测性:无法通过方法签名预判可能抛出的异常
性能损耗:异常处理机制存在性能开销(.NET 9虽有优化但仍需注意)
代码碎片化catch语句打断代码线性阅读体验


Result模式:错误处理的优雅解法

Result模式通过封装操作结果(成功/失败)实现显式错误处理:

// 泛型Result类定义
public class Result<T>
{
    public bool IsSuccess { get; }
    public T? Value { get; }
    public string? Error { get; }

    private Result(bool isSuccess, T? value, string? error) 
    { 
        IsSuccess = isSuccess;
        Value = value;
        Error = error;
    }

    public static Result<T> Success(T value) => new(true, value, null);
    public static Result<T> Failure(string error) => new(false, default, error);
}

改造后的业务逻辑

public async Task<Result<ShipmentResponse>> CreateAsync(
    CreateShipmentCommand request,
    CancellationToken cancellationToken)
{
    if (await CheckExistingShipment(request.OrderId))
    {
        return Result.Failure<ShipmentResponse>($"订单{request.OrderId}物流已存在"); // 显式返回错误
    }

    var shipment = CreateNewShipment(request);
    return Result.Success(shipment.MapToResponse()); // 明确返回成功结果
}

API端点处理

private static async Task<IResult> HandleRequest(
    CreateShipmentRequest request,
    IShipmentService service)
{
    var result = await service.CreateAsync(request.ToCommand());
    return result.IsSuccess 
        ? Results.Ok(result.Value) 
        : Results.Conflict(result.Error); // 根据Result状态返回对应HTTP响应
}

进阶方案:Error-Or库实战

NuGet包ErrorOr提供更强大的Result实现

public async Task<ErrorOr<ShipmentResponse>> ProcessOrder(
    CreateShipmentCommand command)
{
    if (await CheckExistingShipment(command.OrderId))
    {
        return Error.Conflict(code: "SHIPMENT_EXISTS", description: "物流记录已存在"); // 预定义错误类型
    }

    return CreateNewShipment(command).ToResponse(); // 自动包装为成功结果
}

统一错误处理中间件

public static class ErrorHandler
{
    public static IResult ToProblem(this Error error)
    {
        var statusCode = error.Type switch
        {
            ErrorType.Conflict => StatusCodes.Status409Conflict,
            ErrorType.Validation => StatusCodes.Status400BadRequest,
            // 其他错误类型映射...
            _ => StatusCodes.Status500InternalServerError
        };

        return Results.Problem(
            statusCode: statusCode,
            detail: error.Description);
    }
}

Result模式VS异常:适用场景分析

推荐使用Result模式
• 业务流程中的预期错误(如订单重复)
• 需要明确错误类型传递的跨层调用
• 高频调用的核心业务逻辑

保留异常处理
• 全局异常捕获(通过.NET 8的IExceptionHandler

internal sealed class GlobalExceptionHandler : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext context,
        Exception exception,
        CancellationToken cancellationToken)
    {
        // 记录日志并返回标准化错误响应
        await context.WriteAsJsonAsync(new ProblemDetails
        {
            Title = "服务器错误",
            Status = StatusCodes.Status500InternalServerError
        });
        return true;
    }
}

• 第三方库集成(保持与传统库的兼容性)
• 领域模型守卫验证(防御非预期状态)


Result模式优势
• ✅ 显式错误路径,提升代码可读性
• ⚡ 减少异常抛出,优化性能指标
• 🧪 便于单元测试(无需模拟异常抛出)

潜在权衡
• ➕ 需统一团队规范以避免不同实现方式混杂
• ➖ 深度调用链需逐层传递Result对象

最佳实践建议

  1. 核心业务模块采用Result模式
  2. 基础设施层保留全局异常处理
  3. 使用ErrorOr等成熟库替代自行封装
  4. 通过代码分析器确保规范统一
相关留言评论
昵称:
邮箱:
阅读排行