一个三方库从旧版本升到新版本,ohpm install 没报错,项目也能够进入构建流程,这就算升级完成了吗?代码审查时,最容易被跳过的恰恰是这个问题。依赖能够解析,只说明包管理器拿到了符合规则的产物;业务接口是否兼容、默认参数是否改变、资源体积是否增加、失败分支是否还成立,都是另外几层检查。

三方库升级的兼容性与回归路径

继续沿用前几篇的工程结构:外部库只进入 Adapter 层,页面不直接引用第三方类型。这样做既便于替换实现,也能让升级回归集中在一个清楚的边界上。本篇不讨论“把版本号改大”本身,而是设计一条能解释变化、能定位失败、能回退的升级流程。

一、先审查升级范围,而不是直接升级全部依赖

在 oh-package.json5 中,dependencies 声明业务运行所需的包,devDependencies 放置开发或测试阶段所需的包。版本范围使用语义化版本规则:^ 和 ~ 对可接受的更新范围不同。对于尚未完成兼容验证的依赖,先建立单包升级分支更稳妥,避免一口气引入多个未知变量。

假设工程使用华为开发文档中给出的 @ohos/lottie 依赖声明方式,原始配置可以写为:

// entry/oh-package.json5:版本范围采用公开文档中的写法
{
  "name": "entry",
  "version": "1.0.0",
  "description": "reader entry module",
  "main": "",
  "dependencies": {
    "@ohos/lottie": "^2.0.0"
  }
}

这里的 ^2.0.0 代表一个依赖范围,但不能据此判断本地已经安装了哪个候选补丁版本。候选产物需要以 oh-package-lock.json5 解析结果和实际安装树为准。维护升级记录时应同时保留“声明范围”和“解析版本”两列,不要把它们混为一谈。

二、把旧版本、候选版本和差异记录完整

开始操作前先提交现有清单与锁文件,并记录当前依赖树。随后仅更新目标库,查看有哪些直接与间接依赖发生了变化。ohpm update 会按照语义化版本规则更新符合条件的依赖;它不是一个无条件跳到最新大版本的命令。这些细节可以核对ohpm update 文档。

# 基线阶段:查看依赖树并保存变更前的锁文件
ohpm list -d 2 -j > baseline-dependencies.json
cp oh-package-lock.json5 baseline-oh-package-lock.json5

# 候选阶段:仅处理目标包
ohpm update @ohos/lottie

# 观察实际解析结果与间接依赖
ohpm list -d 2 -j > candidate-dependencies.json

git diff -- entry/oh-package.json5 oh-package-lock.json5

上面的 cp 适用于提供该命令的 Shell;Windows PowerShell 可以用 Copy-Item 完成同类操作。基线文件是工作区的比较产物,不应放入正式应用包。ohpm list 的 -d 用于展开依赖层级,-j 输出 JSON,较适合程序化比较。华为的ohpm list 文档也说明其默认深度为零,因此直接执行 ohpm list 看到的列表不足以覆盖传递依赖。

这一步关注三件事:目标库到底解析到哪个版本;它的间接依赖有没有变化;锁文件是否出现非预期的新增、移除或者版本漂移。若出现与本次改动无关的大面积更新,应先解释原因,不要急于合并。只看 oh-package.json5,会漏掉依赖树中实际发生的更新。

开发环境中的依赖差异与检查入口

三、接口兼容要靠业务契约兜底

从业务方视角看,第三方返回的数据只是一种输入格式。比如文章列表需要的是稳定的 id、可显示的 title 和更新时间;至于某个网络库升级后回调形式如何变化,应尽量封闭在 Adapter 内部。页面直接消费三方对象,后续升级就会把大量修改扩散到组件层。

下面的适配器以最小契约为边界。RawFeedItem 是接入层已经完成类型映射后的输入,FeedItem 才是 UI 真正使用的模型。未知字段在接入层处理,业务层无需感知;如果 ID 为空,直接判为数据错误,避免错误项进入路由栈。

// services/FeedAdapter.ets
export interface RawFeedItem {
  id: string;
  title?: string;
  updatedAt?: number;
}

export interface FeedItem {
  id: string;
  title: string;
  updatedAt: number;
}

export class FeedAdapter {
  static normalize(raw: RawFeedItem): FeedItem {
    const safeId: string = raw.id.trim();
    if (safeId.length === 0) {
      throw new Error('数据项缺少有效 ID');
    }
    const safeTitle: string = (raw.title ?? '').trim();
    return {
      id: safeId,
      title: safeTitle.length > 0 ? safeTitle : '未命名条目',
      updatedAt: raw.updatedAt ?? 0
    };
  }
}

这段代码还有一个测试收益:不用依赖网络、窗口或完整页面,也可以单独确认数据映射的行为。升级后,即使底层库替换了序列化方案,只要它最终交给 Adapter 的字段类型符合约定,业务层就保持稳定;如果格式改变,差异也能集中暴露在边界附近。

四、回归不能只覆盖“正常数据”

升级最容易破坏的是原来不显眼的分支:空结果、缺失标题、重复 ID、请求超时、错误码、页面退出后的迟到响应。仅检查首页能否打开,无法覆盖这些问题。建议把测试分成三层:最底层验证字段转换;中间层检查接口请求与错误映射;最上层复查核心界面行为。覆盖路径不同,发现问题的位置也更清楚。

以下采用普通 ArkTS 方法组织可重复调用的契约检查,而不依赖特定测试框架。业务团队可以把它放进现有的单元测试或 CI 任务中执行。预期断言是明确的字符串与数字结果,便于自动化脚本判断。

// test/FeedContractCheck.ets:可复用的纯数据契约检查
import { FeedAdapter, FeedItem, RawFeedItem } from '../services/FeedAdapter';

export class FeedContractCheck {
  static run(): string[] {
    const failures: string[] = [];
    const normal: RawFeedItem = {
      id: 'item-101', title: '工程笔记', updatedAt: 16
    };
    const first: FeedItem = FeedAdapter.normalize(normal);
    if (first.id !== 'item-101' || first.updatedAt !== 16) {
      failures.push('正常条目字段映射异常');
    }

    const missingTitle: FeedItem = FeedAdapter.normalize({ id: 'item-102' });
    if (missingTitle.title !== '未命名条目') {
      failures.push('缺失标题默认值异常');
    }

    let rejected: boolean = false;
    try {
      FeedAdapter.normalize({ id: '   ', title: '空编号' });
    } catch (_) {
      rejected = true;
    }
    if (!rejected) {
      failures.push('空 ID 未被拒绝');
    }
    return failures;
  }
}

对于异步接口,可以再建立固定输入的 Port 实现,让服务层依次收到成功、超时、服务端拒绝、空列表等结果;检验加载状态是否收尾、错误提示是否可操作、重试是否复用上一次请求参数。这里真正需要保持的不是某个三方库的内部类名,而是用户能观察到的业务行为。

升级检查的界面状态与回归项目

五、让升级结果具备可追踪性

一次有效的升级记录至少需要四组信息:源版本和候选版本、依赖树差异、契约检查结果、核心交互检查结果。还要把构建产物体积、权限声明与资源内容纳入复核,防止新版本悄悄引入不需要的能力或文件。前一篇已经介绍了包体审计,本篇的增量重点是把升级前后指标放在同一张对照表里。

检查项基线记录候选版本检查不通过时的动作
直接依赖声明范围与解析版目标是否按预期变化回查更新命令与锁文件
间接依赖依赖树新增、移除、版本漂移追溯上游链路
接口契约关键字段、错误分支边界数据能否保持一致修改 Adapter 或暂缓升级
页面行为选中、跳转、重试关键业务流程定位 UI 或服务回归
包体与权限HAP 大小、权限清单增量是否合理排查资源与清单差异

版本回退同样应该是确定的动作。保留升级前已经提交的 oh-package.json5 和锁文件,出现阻断问题时回退本次变更,恢复锁文件对应的依赖集合,再重新安装并检查依赖树。不要只改一个声明版本而保留候选锁文件,否则源配置与解析产物可能仍然不一致。依赖项配置文档解释了模块级与工程级依赖配置的关系。ohpm clean 默认还会清除锁文件,需要保留锁文件时使用 --keep-lockfile,具体行为可见ohpm clean 文档。

六、升级审核只回答四个问题

实际代码评审不必陷入庞大的工具清单,抓住四个问题即可:我们为什么需要新版本?实际安装了什么?业务契约有没有发生变化?如果失败,如何在短时间内恢复原依赖集合?四个问题都有可查证的结果,升级过程才可维护。

这也解释了三方库治理与普通组件开发的区别。组件开发更多关注交互是否正确,依赖治理还要维护上游变化与内部业务契约之间的缓冲层。把升级控制在 Adapter 边界,把证据留在依赖树和回归结果里,才能让三方库持续更新,而不让业务页面一次次跟着改写。

相关文档:ohpm update · ohpm list · 添加依赖项。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐