【OpenHarmony/HarmonyOs 】51|统一错误处理:从散落 catch 到可恢复的 ArkTS 错误体系 🧭

本文基于当前项目 CloudDBService.etsLoginPage.etsIndex.etsChatPage.etsChatDetailPage.ets 的真实异常处理展开。
项目已经使用 try/catch/finally 避免页面崩溃,也具备 Mock 降级与加载态,但错误仍以未知对象、原始字符串和页面级分支分散存在;本文给出统一错误模型、分层职责、用户提示与恢复策略。

一、为什么 catch 越多,错误处理不一定越好?

当前代码在登录、初始化、查询、订阅和发送消息处都写了 catch。

这能阻止异常直接击穿页面,却容易形成新的问题:

  • 同一异常在多层重复打印;
  • 页面直接展示 SDK message;
  • 有的失败降级为 Mock,有的静默忽略;
  • 调用方无法判断是否可重试;
  • finally 虽结束 loading,却没有形成明确错误状态;
  • 未知对象被随意 JSON.stringify

统一错误处理的目标不是减少 catch 数量,而是明确每一层应该负责什么。


二、项目当前的真实模式

Cloud DB 服务初始化失败后重新抛出:

try {
  await AGConnectCloudDB.initialize(context);
} catch (err) {
  hilog.error(0x0000, 'CloudDBService',
    'Cloud DB Initialization failed: ' + JSON.stringify(err));
  throw new Error(JSON.stringify(err));
}

登录页捕获后把错误字符串直接拼到界面:

catch (e) {
  let errStr = JSON.stringify(e);
  this.message = getStr(this.currentLang).login_failed + ': ' + errStr;
}

聊天列表失败时直接使用 Mock:

catch (e) {
  this.chatList = this.getFallbackChatList();
} finally {
  this.isLoading = false;
}

三种处理各有合理部分,但缺少共同语言。


三、统一错误模型应该回答什么?

一个可用的应用错误至少应回答:

  1. 错误属于哪个业务域;
  2. 稳定错误码是什么;
  3. 用户应该看到什么;
  4. 是否可以重试;
  5. 是否允许降级;
  6. 是否需要重新登录;
  7. 内部诊断追踪 ID 是什么;
  8. 原始异常是否保留在受控内存中。

四、推荐的错误类型

export enum AppErrorKind {
  NETWORK = 'NETWORK',
  AUTH = 'AUTH',
  DATABASE = 'DATABASE',
  VALIDATION = 'VALIDATION',
  PERMISSION = 'PERMISSION',
  NOT_FOUND = 'NOT_FOUND',
  CONFLICT = 'CONFLICT',
  CANCELLED = 'CANCELLED',
  UNKNOWN = 'UNKNOWN'
}

export class AppError extends Error {
  readonly code: string;
  readonly kind: AppErrorKind;
  readonly retryable: boolean;
  readonly userMessageKey: string;
  readonly traceId: string;

  constructor(
    code: string,
    kind: AppErrorKind,
    retryable: boolean,
    userMessageKey: string,
    traceId: string
  ) {
    super(code);
    this.code = code;
    this.kind = kind;
    this.retryable = retryable;
    this.userMessageKey = userMessageKey;
    this.traceId = traceId;
  }
}

注意:示例强调结构,具体 API 和构造方式需要结合当前 ArkTS 编译规则验证。


五、错误码为什么必须稳定?

错误 message 可能变化:

  • SDK 升级后英文文案变化;
  • 系统语言变化;
  • 云端返回字段变化;
  • 开发者调整提示语。

错误码应保持稳定,例如:

AUTH_LOGIN_CANCELLED
AUTH_SESSION_EXPIRED
CLOUDDB_ZONE_UNAVAILABLE
CLOUDDB_QUERY_FAILED
CHAT_SEND_FAILED
PERMISSION_CAMERA_DENIED
VALIDATION_MESSAGE_EMPTY

页面根据 code 或 kind 决定行为,不依赖脆弱的 message 文本判断。


六、分层职责

SDK 适配层

把 AGC、Cloud DB 与系统 API 的异常转换为 AppError

服务层

补充业务操作上下文,决定是否重试、是否降级,但不直接操作 UI。

ViewModel 或页面逻辑层

把错误映射为加载、空、错误、离线或需登录状态。

UI 层

展示本地化文案和恢复按钮,不解析 SDK 异常对象。

日志层

只记录安全错误码、阶段与追踪 ID,不复制用户数据。


七、错误标准化函数

推荐所有 catch 首先标准化:

function normalizeError(error: Object, operation: string): AppError {
  if (error instanceof AppError) {
    return error;
  }

  return new AppError(
    operation + '_UNKNOWN',
    AppErrorKind.UNKNOWN,
    false,
    'error_unknown',
    createTraceId()
  );
}

实际适配器应优先识别 SDK 明确错误码,未知情况才进入 UNKNOWN。

不要通过 message 包含某个英文单词来判断网络、权限或取消。


八、用户提示与诊断信息必须分离

用户需要的是:

  • 发生了什么;
  • 是否影响当前操作;
  • 下一步可以做什么。

工程师需要的是:

  • 错误码;
  • 操作阶段;
  • 追踪 ID;
  • 耗时;
  • 重试次数。

不能把后者原样拼接到前者。

推荐 UI 文案:

消息发送失败,请检查网络后重试。

推荐内部日志:

event=chat_send_failed code=CHAT_SEND_TIMEOUT trace=7F2A retry=1

九、页面状态不能只有 isLoading

建议状态至少包含:

enum LoadPhase {
  IDLE = 'IDLE',
  LOADING = 'LOADING',
  CONTENT = 'CONTENT',
  EMPTY = 'EMPTY',
  ERROR = 'ERROR',
  OFFLINE = 'OFFLINE'
}

这样页面可以区分:

  • 首次加载失败;
  • 有旧数据但刷新失败;
  • Cloud DB 不可用且已降级;
  • 真实空数据;
  • 用户取消;
  • 会话过期。

十、哪些错误适合重试?

通常可重试:

  • 临时网络不可用;
  • 超时;
  • 服务暂时繁忙;
  • 订阅中断;
  • Zone 初始化竞态。

通常不可自动重试:

  • 参数校验失败;
  • 权限被用户拒绝;
  • 登录被用户取消;
  • 数据不存在;
  • 业务冲突;
  • 认证会话失效且需要用户操作。

重试策略必须由错误类型驱动,而不是任何 catch 都 setTimeout。


十一、逐项统一错误处理核验

检查项 1:AGC 初始化

  • 检查目标:SDK 异常转换为稳定 AppError。
  • 常见反例:直接 throw JSON 字符串。
  • 推荐动作:映射初始化错误码与可重试性。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 2:Cloud DB Zone

  • 检查目标:区分未初始化、打开失败和已关闭。
  • 常见反例:统一当 unknown。
  • 推荐动作:建立 ZONE_UNAVAILABLE 等明确代码。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 3:登录取消

  • 检查目标:识别用户主动取消。
  • 常见反例:显示“登录失败”并自动重试。
  • 推荐动作:返回 CANCELLED 且不打 error。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 4:登录网络错误

  • 检查目标:提示检查网络并允许重试。
  • 常见反例:展示 SDK 原始 message。
  • 推荐动作:映射 AUTH_NETWORK_ERROR。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 5:会话过期

  • 检查目标:清理本地状态并回登录。
  • 常见反例:无限重试原请求。
  • 推荐动作:使用 AUTH_SESSION_EXPIRED。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 6:游客登录

  • 检查目标:Cloud DB 失败可降级。
  • 常见反例:初始化失败阻止游客进入。
  • 推荐动作:按业务要求标记 fallback。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 7:用户同步

  • 检查目标:同步失败不一定阻止登录。
  • 常见反例:次要操作失败覆盖主流程成功。
  • 推荐动作:区分关键与非关键副作用。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 8:用户查询

  • 检查目标:首次失败进入错误态。
  • 常见反例:无条件展示 Mock 且不提示。
  • 推荐动作:标记数据来源与重试入口。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 9:聊天列表

  • 检查目标:保留旧数据并提示刷新失败。
  • 常见反例:catch 后清空内容。
  • 推荐动作:区分 initial_error 与 refresh_error。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 10:消息订阅

  • 检查目标:建立失败可手动重连。
  • 常见反例:只 console.error 后静默。
  • 推荐动作:维护 subscription state。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 11:订阅中断

  • 检查目标:记录断线并退避重连。
  • 常见反例:每次回调立即无限重订阅。
  • 推荐动作:设置最大次数和抖动。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 12:发送消息

  • 检查目标:发送失败保留本地失败气泡。
  • 常见反例:catch 后消息消失。
  • 推荐动作:使用 sending、sent、failed 状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 13:重复发送

  • 检查目标:以客户端消息 ID 幂等。
  • 常见反例:重试生成新 ID。
  • 推荐动作:同一操作复用 idempotency key。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 14:消息为空

  • 检查目标:本地校验并阻止请求。
  • 常见反例:交给 Cloud DB 报错。
  • 推荐动作:返回 VALIDATION_MESSAGE_EMPTY。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 15:附件失败

  • 检查目标:按选择、读取、上传、发送分阶段。
  • 常见反例:全部显示“操作失败”。
  • 推荐动作:建立阶段化错误码。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 16:权限拒绝

  • 检查目标:提示去设置或取消。
  • 常见反例:当系统异常反复请求。
  • 推荐动作:使用 PERMISSION_DENIED。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 17:路由参数

  • 检查目标:入口校验类型与必填值。
  • 常见反例:使用空 targetUid 继续查询。
  • 推荐动作:返回 ROUTE_PARAM_INVALID。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 18:数据不存在

  • 检查目标:显示空或已删除状态。
  • 常见反例:当成系统错误。
  • 推荐动作:使用 NOT_FOUND。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 19:并发冲突

  • 检查目标:提示刷新后重试。
  • 常见反例:静默覆盖新数据。
  • 推荐动作:使用 CONFLICT 与版本控制。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 20:点赞失败

  • 检查目标:回滚乐观更新。
  • 常见反例:UI 数字永久错误。
  • 推荐动作:保存旧值并按结果恢复。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 21:删除失败

  • 检查目标:恢复被移除的本地项。
  • 常见反例:先删 UI 后失败无提示。
  • 推荐动作:使用事务式 UI 状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 22:心情发布

  • 检查目标:防止重复点击。
  • 常见反例:多次并发创建。
  • 推荐动作:提交期锁定并使用唯一请求 ID。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 23:私密日记

  • 检查目标:权限错误不能降级为公开。
  • 常见反例:失败后自动改成公开保存。
  • 推荐动作:安全边界错误禁止降级。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 24:搜索超时

  • 检查目标:保留关键词并允许重试。
  • 常见反例:清空输入。
  • 推荐动作:错误状态与输入状态分离。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 25:搜索竞态

  • 检查目标:旧错误不能覆盖新结果。
  • 常见反例:任意 catch 设置 ERROR。
  • 推荐动作:校验 requestVersion。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 26:分页失败

  • 检查目标:保留已加载数据和游标。
  • 常见反例:重置整个列表。
  • 推荐动作:使用 load_more_error。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 27:无更多数据

  • 检查目标:作为正常结束状态。
  • 常见反例:记录 error。
  • 推荐动作:使用 hasMore=false。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 28:空结果

  • 检查目标:与失败状态明确区分。
  • 常见反例:空数组统一显示失败。
  • 推荐动作:建立 EMPTY phase。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 29:Mock 降级

  • 检查目标:用户或测试可识别来源。
  • 常见反例:伪装为云端成功。
  • 推荐动作:使用 degraded=true。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 30:缓存回退

  • 检查目标:展示缓存并提示可能过期。
  • 常见反例:缓存命中掩盖请求失败。
  • 推荐动作:记录 stale 状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 31:加载超时

  • 检查目标:结束 loading 并提供恢复。
  • 常见反例:转圈无限持续。
  • 推荐动作:超时映射为 retryable error。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 32:finally

  • 检查目标:只做无条件清理。
  • 常见反例:finally 覆盖成功状态。
  • 推荐动作:状态迁移在 try/catch 内完成。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 33:嵌套 try

  • 检查目标:每层有明确责任。
  • 常见反例:空 catch 吞掉初始化错误。
  • 推荐动作:次要错误也应可观测。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 34:空 catch

  • 检查目标:禁止无解释吞异常。
  • 常见反例:catch (e) {}。
  • 推荐动作:至少转换、降级或返回状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 35:重新抛出

  • 检查目标:保留稳定错误类型。
  • 常见反例:throw new Error(JSON.stringify(err))。
  • 推荐动作:throw AppError 或标准化结果。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 36:未知类型

  • 检查目标:安全处理 Object/unknown 来源。
  • 常见反例:强制 any 后访问字段。
  • 推荐动作:通过适配器和 instanceof 判断。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 37:Error message

  • 检查目标:仅作内部安全摘要。
  • 常见反例:把 message 当错误码。
  • 推荐动作:code 与 message 分离。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 38:错误码

  • 检查目标:全局唯一且稳定。
  • 常见反例:页面自定义中文字符串。
  • 推荐动作:域_操作_原因格式。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 39:错误类型

  • 检查目标:覆盖网络、认证、数据库等域。
  • 常见反例:所有错误都是 UNKNOWN。
  • 推荐动作:优先使用 SDK code 映射。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 40:可重试性

  • 检查目标:由类型决定。
  • 常见反例:UI 自己猜测。
  • 推荐动作:AppError.retryable 明确输出。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 41:降级性

  • 检查目标:安全评估后决定。
  • 常见反例:所有错误都回退 Mock。
  • 推荐动作:权限与隐私错误禁止降级。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 42:严重级别

  • 检查目标:区分提示、警告和阻断。
  • 常见反例:任何失败都弹模态框。
  • 推荐动作:按影响范围选择 UI。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 43:用户文案

  • 检查目标:本地化且可行动。
  • 常见反例:显示 JSON 异常。
  • 推荐动作:使用 userMessageKey。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 44:诊断文案

  • 检查目标:包含 code 与 trace。
  • 常见反例:与用户提示混在一起。
  • 推荐动作:只进入安全日志。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 45:追踪 ID

  • 检查目标:每次操作稳定。
  • 常见反例:使用 UID 作为 trace。
  • 推荐动作:生成无身份含义的短 ID。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 46:日志去重

  • 检查目标:责任边界只记一次。
  • 常见反例:服务层和页面层重复 error。
  • 推荐动作:下层转换,上层最终记录。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 47:Toast

  • 检查目标:适合短暂且非阻断错误。
  • 常见反例:重要错误一闪而过。
  • 推荐动作:按严重度选择状态区或对话框。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 48:错误页

  • 检查目标:适合首屏无法继续。
  • 常见反例:局部按钮失败跳整页。
  • 推荐动作:局部错误局部呈现。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 49:内联提示

  • 检查目标:靠近输入或操作对象。
  • 常见反例:所有校验都 Toast。
  • 推荐动作:表单错误绑定字段。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 50:重试按钮

  • 检查目标:只对 retryable 显示。
  • 常见反例:权限拒绝也显示重试。
  • 推荐动作:提供去设置等正确动作。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 51:取消按钮

  • 检查目标:用户可退出长流程。
  • 常见反例:强制等待不可取消请求。
  • 推荐动作:取消映射为正常状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 52:防重复

  • 检查目标:处理中禁用提交。
  • 常见反例:按钮可连续触发。
  • 推荐动作:isSubmitting 独立管理。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 53:请求版本

  • 检查目标:只接受最新意图结果。
  • 常见反例:旧失败覆盖新成功。
  • 推荐动作:比较单调递增版本。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 54:页面销毁

  • 检查目标:忽略或取消后续回调。
  • 常见反例:离页后更新 @State。
  • 推荐动作:维护 active 标识或取消句柄。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 55:超时控制

  • 检查目标:每类操作有合理期限。
  • 常见反例:所有请求共用一个超时。
  • 推荐动作:登录、查询、上传分别配置。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 56:指数退避

  • 检查目标:临时错误逐步延迟。
  • 常见反例:固定 100ms 高频重试。
  • 推荐动作:增加上限与随机抖动。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 57:最大重试

  • 检查目标:存在明确上限。
  • 常见反例:后台无限循环。
  • 推荐动作:超限转手动恢复。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 58:网络恢复

  • 检查目标:恢复时触发受控重试。
  • 常见反例:每个页面同时请求。
  • 推荐动作:集中协调恢复事件。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 59:离线写入

  • 检查目标:明确队列和冲突策略。
  • 常见反例:假装已同步成功。
  • 推荐动作:展示 pending_sync。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 60:幂等设计

  • 检查目标:写操作可安全重放。
  • 常见反例:重试产生重复数据。
  • 推荐动作:使用稳定业务 ID。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 61:事务边界

  • 检查目标:关联写入共同成功或补偿。
  • 常见反例:半成功无处理。
  • 推荐动作:定义补偿动作。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 62:回滚失败

  • 检查目标:升级错误并保留证据。
  • 常见反例:继续假装已恢复。
  • 推荐动作:使用 ROLLBACK_FAILED。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 63:数据解析

  • 检查目标:校验字段完整与类型。
  • 常见反例:JSON.parse 失败崩溃。
  • 推荐动作:映射 DATA_INVALID。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 64:旧数据兼容

  • 检查目标:未知枚举有回退。
  • 常见反例:直接强转模型。
  • 推荐动作:区分可修复与不可读。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 65:本地存储

  • 检查目标:读写失败可解释。
  • 常见反例:把 null、空串、损坏混为一类。
  • 推荐动作:建立 STORAGE 错误域。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 66:资源读取

  • 检查目标:缺失资源在构建或测试发现。
  • 常见反例:运行时显示空文案。
  • 推荐动作:错误键纳入校验。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 67:国际化键

  • 检查目标:错误码映射有效 key。
  • 常见反例:直接把 code 显示给用户。
  • 推荐动作:缺失时使用通用安全文案。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 68:权限状态

  • 检查目标:区分首次拒绝与永久拒绝。
  • 常见反例:只显示 denied。
  • 推荐动作:提供对应恢复路径。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 69:相机能力

  • 检查目标:区分无设备、权限和调用失败。
  • 常见反例:统一 UNKNOWN。
  • 推荐动作:系统错误适配到业务域。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 70:相册选择

  • 检查目标:用户取消不是 error。
  • 常见反例:取消时弹失败。
  • 推荐动作:使用 CANCELLED 正常结束。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 71:文件上传

  • 检查目标:区分读取、网络和服务失败。
  • 常见反例:统一“发送失败”。
  • 推荐动作:阶段化状态支持针对性重试。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 72:大文件

  • 检查目标:本地校验大小。
  • 常见反例:上传后才报错。
  • 推荐动作:使用 VALIDATION_FILE_TOO_LARGE。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 73:不支持格式

  • 检查目标:在选择后立即提示。
  • 常见反例:SDK 抛异常后显示内部信息。
  • 推荐动作:建立格式校验码。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 74:数据库权限

  • 检查目标:禁止自动改为公开查询。
  • 常见反例:权限失败后全表查询。
  • 推荐动作:安全错误必须阻断。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 75:数据库索引

  • 检查目标:识别配置错误并阻断发布。
  • 常见反例:当网络错误自动重试。
  • 推荐动作:使用 DATABASE_INDEX_REQUIRED。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 76:对象模型

  • 检查目标:Schema 不匹配有明确诊断。
  • 常见反例:转换异常只显示 unknown。
  • 推荐动作:记录模型版本和安全 code。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 77:订阅快照

  • 检查目标:回调错误进入统一通道。
  • 常见反例:只 console.error。
  • 推荐动作:触发订阅状态变化。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 78:监听释放

  • 检查目标:释放失败记录 warn。
  • 常见反例:完全忽略。
  • 推荐动作:保证生命周期最终一致。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 79:窗口加载

  • 检查目标:加载失败保留错误码。
  • 常见反例:完整序列化窗口异常。
  • 推荐动作:能力层标准化。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 80:备份恢复

  • 检查目标:区分版本不兼容与 IO 失败。
  • 常见反例:一律成功日志。
  • 推荐动作:失败向系统正确返回。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 81:启动守卫

  • 检查目标:鉴权异常安全回登录。
  • 常见反例:无限停留 loading。
  • 推荐动作:设置最终路由状态。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 82:关键与非关键

  • 检查目标:主流程与附加同步分开。
  • 常见反例:头像同步失败导致登录失败。
  • 推荐动作:定义 operation criticality。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 83:批量操作

  • 检查目标:返回逐项结果或整体策略。
  • 常见反例:首个失败后状态未知。
  • 推荐动作:明确 all-or-nothing。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 84:部分成功

  • 检查目标:UI 清楚展示成功与失败项。
  • 常见反例:统一提示成功。
  • 推荐动作:使用 PartialResult 模型。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 85:错误聚合

  • 检查目标:多个并发失败保留主因。
  • 常见反例:只显示最后一个错误。
  • 推荐动作:按优先级选择主错误。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 86:错误优先级

  • 检查目标:认证和权限高于普通网络提示。
  • 常见反例:Toast 相互覆盖。
  • 推荐动作:集中调度提示。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 87:全局处理器

  • 检查目标:只兜底未处理异常。
  • 常见反例:取代所有局部业务处理。
  • 推荐动作:局部恢复优先,全局防崩溃。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 88:未捕获异常

  • 检查目标:安全上报并终止危险流程。
  • 常见反例:继续使用未知状态。
  • 推荐动作:清理关键状态后提示重启。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 89:Promise 拒绝

  • 检查目标:所有异步入口显式处理。
  • 常见反例:调用 async 不 await。
  • 推荐动作:返回 Promise 并在边界捕获。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 90:回调错误

  • 检查目标:转换为同一错误模型。
  • 常见反例:Object 类型随处传递。
  • 推荐动作:在 SDK 回调边界适配。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 91:构建检查

  • 检查目标:错误类型符合 ArkTS 规则。
  • 常见反例:依赖 any 与对象字面量推断。
  • 推荐动作:明确接口、类和返回类型。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 92:单元测试

  • 检查目标:覆盖每个错误映射。
  • 常见反例:只测成功路径。
  • 推荐动作:使用表驱动错误码用例。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 93:恢复测试

  • 检查目标:验证重试后状态闭合。
  • 常见反例:只验证出现错误提示。
  • 推荐动作:覆盖失败到成功迁移。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 94:弱网测试

  • 检查目标:覆盖超时、断网和切网。
  • 常见反例:只在稳定 Wi-Fi 测试。
  • 推荐动作:注入延迟和失败。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 95:竞态测试

  • 检查目标:旧成功和旧失败都被丢弃。
  • 常见反例:只测响应顺序正常。
  • 推荐动作:反转返回时序。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 96:生命周期测试

  • 检查目标:离页后不更新 UI。
  • 常见反例:只看无崩溃。
  • 推荐动作:检查监听和任务释放。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 97:安全测试

  • 检查目标:用户文案无内部信息。
  • 常见反例:错误页显示堆栈。
  • 推荐动作:扫描 code、URL、token 模式。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 98:可访问性

  • 检查目标:错误被读屏感知。
  • 常见反例:仅红色边框。
  • 推荐动作:增加文本和状态语义。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 99:统计指标

  • 检查目标:按稳定 code 聚合错误率。
  • 常见反例:按 message 分组。
  • 推荐动作:监控 code、operation、version。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 100:告警阈值

  • 检查目标:关键错误有明确阈值。
  • 常见反例:任何单次错误都告警。
  • 推荐动作:结合比例、时间窗和影响用户数。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 101:灰度发布

  • 检查目标:比较新旧错误分布。
  • 常见反例:上线后才发现映射遗漏。
  • 推荐动作:灰度观察 UNKNOWN 占比。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 102:UNKNOWN 治理

  • 检查目标:持续降低未知错误比例。
  • 常见反例:UNKNOWN 成为永久垃圾桶。
  • 推荐动作:定期分析并补充适配。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 103:错误文档

  • 检查目标:code 含义和恢复动作一致。
  • 常见反例:文档与代码两套命名。
  • 推荐动作:从代码枚举生成清单。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 104:代码评审

  • 检查目标:检查 catch 是否履行职责。
  • 常见反例:看到 catch 就认为安全。
  • 推荐动作:审查转换、状态、提示、日志。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 105:发布检查

  • 检查目标:P0/P1 错误路径已验证。
  • 常见反例:只测 happy path。
  • 推荐动作:按业务关键度建立矩阵。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

检查项 106:最终标准

  • 检查目标:错误可识别、可解释、可恢复、可观测。
  • 常见反例:catch 后不崩溃就算完成。
  • 推荐动作:形成端到端闭环。
  • 验证证据:错误码、页面状态、用户提示、日志事件与恢复结果。
  • 通过标准:调用方无需解析 SDK 原始异常即可做出正确决策。

十二、推荐改造路线

阶段 1:盘点

检索所有 catch、throw、Promise 拒绝、回调错误和直接展示的异常字符串。

阶段 2:建模

建立 AppErrorKind、稳定 code、retryable、userMessageKey 与 traceId。

阶段 3:适配

先为 AGC Auth、Cloud DB、系统权限和文件选择器建立错误适配器。

阶段 4:页面状态

isLoading + message 升级为 IDLE、LOADING、CONTENT、EMPTY、ERROR、OFFLINE。

阶段 5:恢复

为可重试错误接入退避、手动重试、幂等和竞态隔离。

阶段 6:观测

日志只记录安全 code、operation 与 trace,并统计 UNKNOWN 比例。

阶段 7:测试

覆盖取消、权限、断网、超时、部分成功、离页回调与恢复成功。


十三、常见反模式速查

反模式 结果 正确方向
throw new Error(JSON.stringify(err)) 类型与语义丢失 转换为稳定 AppError
UI 展示 SDK message 内部信息泄露且不可本地化 userMessageKey
所有错误都自动重试 重复请求与糟糕体验 retryable 驱动
catch 后无状态 用户看见静默失败 明确 ERROR 或 DEGRADED
空 catch 故障不可观测 转换、降级或传播
多层重复 error 日志 噪声与重复告警 责任边界记录一次
取消也算失败 用户被错误责备 CANCELLED 正常结束
Mock 冒充云端成功 数据来源不透明 degraded 状态
只用 isLoading 空、错、离线混在一起 明确页面状态机
重试生成新业务 ID 重复写入 幂等键

十四、总结

统一错误处理不是创建一个工具类后把所有 catch 搬进去。

真正完整的体系应贯穿:

  1. SDK 异常标准化;
  2. 服务层业务语义补充;
  3. 页面状态机;
  4. 本地化且可行动的用户提示;
  5. 安全日志和稳定错误指标;
  6. 重试、降级、回滚与幂等恢复。

当调用方只依赖稳定错误码和明确能力,而不再猜测 SDK message 时,错误才从“偶发异常”变成了可以设计、测试和治理的产品状态。

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐