11-20.【错误处理】错误处理设计中如何平衡 用户提示、日志记录、State 更新 三者?

2 阅读3分钟

在错误处理的架构设计中,用户提示、日志记录、State 更新这三者分别代表了对外沟通、对内回溯、系统一致性

要平衡这三者,核心原则是: “State 为骨、日志为肉、用户提示为皮”


1. 平衡模型:职责分离

一个健壮的错误处理链条应当遵循以下分工:

维度处理目标触发时机存储位置
State 更新逻辑一致性。确保 UI 状态(如按钮禁用、Loading 停止)与后端状态对齐。立即(同步/异步首选)内存 (ViewModel/Store)
日志记录问题复盘。记录原始、未经过滤的底层错误(如 SQL 错误码、堆栈)。捕获时(后台处理)控制台/磁盘/服务端
用户提示情绪安抚与引导。将技术术语转化为人类可读的语言(如“网络拥塞”而非“Error 429”)。仅在需要用户干预时UI 层 (Toast/Alert)

2. 工程实践:统一拦截器模式

为了避免在每个 do-catch 块里写三遍逻辑,建议在架构层建立一个统一处理器

A. 状态更新(State Update)

使用枚举状态机(State Machine)来驱动 UI。State 更新应该是最优先级的,因为它防止了用户的误操作。

Swift

// 防御点:进入失败状态的同时,必须关闭加载状态
self.state = .failure(error) 
self.isLoading = false

B. 日志记录(Logging)

日志应当包含“上下文”。不要只记 error.localizedDescription,要记录:用户 ID、当前页面、失败的操作、原始错误码

C. 用户提示(User Feedback)

基于错误分类进行差异化提示。

  • 致命错误:弹窗(Alert),强制用户操作(如重新登录)。
  • 非致命错误:轻量提示(Toast/Snackbar),不打断操作。
  • 静默错误:不提示用户,仅后台记录日志(如预加载失败)。

3. 典型代码链路(TCA/MVVM 风格)

Swift

func handle(error: AppError) {
    // 1. 记录日志 (对内)
    Logger.error("API 失败", context: ["type": error.category, "trace": error.id])
    
    // 2. 更新状态 (核心逻辑)
    self.loadingState = .failed
    self.isRetryEnabled = error.isRetryable
    
    // 3. 用户提示 (对外)
    if error.shouldNotifyUser {
        NotificationManager.show(
            title: error.userTitle,
            message: error.userDescription,
            style: error.severity == .critical ? .alert : .toast
        )
    }
}

4. 防御式策略:避免“过度提示”

在平衡三者时,最常见的错误是把日志直接展示给用户,或者UI 状态未更新但弹出了错误窗

  1. 脱敏处理:永远不要在用户提示中显示 Optional(nil)JSON parsing error
  2. 状态保护:如果异步操作报错,必须确保 UI 按钮从“禁用”恢复到“可用”,否则用户会陷入死锁。
  3. 日志冗余:高频重试导致的错误不需要每次都记详细日志,可以使用“采样记录”或“合并记录”策略。

总结:评估矩阵

错误类型用户提示日志记录State 更新
断网/超时 (引导开启网络) (仅记录频率)重置按钮/显示空白页
后端逻辑错 (422) (告知输入有误) (排查前端验证漏洞)高亮输入框
系统崩溃 (500) (对不起,请重试)极高 (立即触发告警)返回上一级