异常处理与响应消息模式设计指南

异常处理与响应消息模式设计指南

异常处理与响应消息模式设计指南

在编码过程中,常常有种疑惑,什么时候用Response包裹错误返回,什么时候直接抛异常,对此,我进行了分析。

1 两种机制的定位

维度异常 (Exception)响应消息 (ResponseMsg / Result)
语义系统级或非预期 的错误;表示当前代码路径已无法继续正常执行业务。业务级或可预期 的结果;包含成功/失败、提示信息、业务数据。
控制流通过 CLR 的异常机制跳出当前调用栈;必须 try–catch作为方法返回值正常流转;调用方显式检查 IsSuccess 等标志。
代价抛出 & 捕获成本高(创建堆栈信息、影响 JIT 内联)。只是一块数据,几乎无额外开销。
可测试性单元测试需 Assert.Throws...;断言粒度粗。断言对象属性;更细粒度。
并发/异步异常会通过 Task.Exception 聚合,若未捕获→TaskScheduler.UnobservedTaskException结果对象天然兼容 async/await;无额外陷阱。

2 什么时候用异常?

只有当 代码无法恢复调用方不可能预料 时,才应该抛异常。

场景示例

  1. 系统资源或环境问题
    • 文件读写失败(磁盘损坏、权限不足)。
    • 数据库连接超时。
  2. 外部组件/平台调用失败
    • COM 组件返回错误。
    • HTTP 请求网络级错误(DNS 解析失败)。
  3. 编程错误(Bug)
    • 空引用、越界、格式错误。
  4. 服务端内部严重故障
    • 依赖服务挂掉。

建议

  • 抛异常时立即捕获并记录日志(NLog)再向上抛或包装为业务错误。
  • 队列消费者中的异常应 catch,避免吞掉线程;用 Dispatcher.Invoke 把提示回 UI。
/// <summary>读取配置文件,无法读取时抛自定义异常</summary>
public static Config LoadConfig(string path)
{
    try
    {
        var json = File.ReadAllText(path);
        return JsonSerializer.Deserialize<Config>(json)!;
    }
    catch (IOException ex)
    {
        _logger.Error(ex, "配置文件读取失败");
        throw new ConfigLoadException("配置文件读取失败,请检查磁盘或权限", ex);
    }
}

3 什么时候用 ResponseMsg / Result?

错误是业务层可预测且希望继续流程 时,用响应消息更合适。

场景示例

  1. 表单校验
    • 字段必填、格式、范围检测——告诉用户“手机号格式不正确”。
  2. 权限/流程判断
    • “用户余额不足”“工单已关闭,不能再审批”。
  3. 幂等或软失败
    • “记录已存在”返回 Code=409,但整体流程不崩溃。
  4. 后台任务结果
    • 队列消费完毕后,需要返回执行状态 & 消息。

典型定义

/// <summary>统一业务结果包装</summary>
public sealed record ResponseMsg<T>
{
    public bool IsSuccess { get; init; }
    public string? Message { get; init; }
    public T? Data { get; init; }

    public static ResponseMsg<T> Ok(T data, string? msg = null) =>
        new() { IsSuccess = true, Data = data, Message = msg };

    public static ResponseMsg<T> Fail(string msg) =>
        new() { IsSuccess = false, Message = msg };
}

调用方示例:

var result = await _orderService.PlaceAsync(request);
if (!result.IsSuccess)
{
    _uiNotifier.Warn(result.Message);
    return;
}
UpdateUI(result.Data);

4 在全局多线程队列中的融合做法

4.1 消息体区分系统异常与业务结果

public record ToolExecuteResultMessage : QueueMessage
{
    public ResponseMsg<object?> Result { get; init; } = ResponseMsg<object?>.Ok(null);
    public Exception? Exception { get; init; }
}
  • 消费者:内部 try–catch;捕获系统异常填 Exception;业务失败设置 Result.IsSuccess=false
  • UI 线程
    • Exception 不为空 → 显示“系统错误”并写日志。
    • 否则根据 Result.IsSuccess 决定提示样式。

4.2 最佳实践

规则说明
宁可多返回 ResponseMsg,也不要滥抛异常业务流转更清晰,可组合。
所有线程边界都要 catch包括 Task.Run、队列循环;绝不让异常溢出到线程池。
异常要带上下文自定义异常加属性,如 ToolIdStepName,方便排障。
错误日志五要素时间、级别、位置、上下文、堆栈。NLog 可配置。

5 结合 Unit Test 的对比

// 断言异常
Assert.ThrowsAsync<ConfigLoadException>(() => _service.LoadConfigAsync("bad.json"));

// 断言业务失败
var res = await _orderService.PlaceAsync(invalidDto);
Assert.False(res.IsSuccess);
Assert.Equal("手机号格式不正确", res.Message);
  • 异常测试:通常只验证类型。
  • 响应对象测试:可验证业务语义(Message/Data)。

6 小结

  1. 用异常处理不可预期/系统级错误,让程序“快失败”并留痕。
  2. 用 ResponseMsg 表达业务可预期的成功与失败,让调用链保持顺畅。
  3. 在多线程/队列环境:
    • 消费者内部 捕获异常并包装后丢入结果消息,避免线程终止。
    • UI 层 根据 ExceptionResult.IsSuccess 做区分化提示。
  4. 配合 NLog、Autofac、CommunityToolkit.Mvvm,可实现易排查、可扩展、线程安全的错误处理体系。

主题测试文章,只做测试使用。发布者:admin,转转请注明出处:http://onebyone.icu/2025/06/05/%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86%e4%b8%8e%e5%93%8d%e5%ba%94%e6%b6%88%e6%81%af%e6%a8%a1%e5%bc%8f%e8%ae%be%e8%ae%a1%e6%8c%87%e5%8d%97/

Like (0)
Previous 2025年4月10日
Next 2025年3月3日

相关推荐

发表回复

Please Login to Comment