Conventional Commits 与 Semantic Versioning

共 21 题
📑 题目列表 21 题
#
★★★

1. Conventional Commits 1.0.0 的结构中 [optional scope]:

请说明 Conventional Commits 1.0.0 规范所定义的提交消息结构,即 <type>[optional scope]: <description> 的各个组成部分及其含义?

  • 提交消息头部(header)的组成:type、scope、description
  • 各组成部分的书写规范与约束
  • 与 SemVer 的联动关系

Conventional Commits 1.0.0 规定提交消息的头部格式为 <type>[optional scope]: <description>。其中 type 是必填的提交类型,如 feat(新功能)、fix(缺陷修复)、refactordocschore 等;[optional scope] 是可选的作用域,用于标注变更影响的模块或包,通常用括号包裹,如 feat(parser): ...: (冒号加空格)作为分隔符;description 是对变更的简短描述。描述应使用小写、命令式语气,长度一般不超过 50 个字符。整条 header 通常控制在 100 字符以内。该规范的核心价值在于让提交消息可被机器解析,从而自动生成 CHANGELOG 和确定语义化版本号。

规范化的头部是自动化工具链(如 semantic-release、release-please)解析版本变更的基础。featfix 是唯一会直接影响版本号的类型,其中 feat 触发 MINOR 升级、fix 触发 PATCH 升级,BREAKING CHANGE 触发 MAJOR。因此头部的结构约束本质上是把"这次变更是什么"以结构化方式编码,供工具与后续评审者理解。

#
★★★

2. Conventional Commits 的 BREAKING CHANGE 标记中! 后缀或正文 footer

在 Conventional Commits 中,如何标记一个破坏性变更(BREAKING CHANGE)?请说明 ! 后缀与正文 footer 两种写法?

  • 两种 BREAKING CHANGE 标记方式
  • 两种方式的优先级与配合
  • 对版本号的影响(MAJOR)

Conventional Commits 提供两种标记破坏性变更的方式。第一种是在 header 的 type/scope 后加 ! 后缀,例如 feat!:feat(api)!:,这意味着变更会破坏向后兼容性。第二种是在正文(body)末尾的 footer 中写 BREAKING CHANGE: <描述>。两种方式可以同时使用,且 ! 后缀会隐式触发 footer 中的 BREAKING CHANGE 语义。当提交同时包含 ! 和 footer 时,工具会以它们共同表达的破坏性变更语义为准。破坏性变更会触发 MAJOR 版本号升级。

两种标记方式是为了不同场景:! 后缀适合快速、醒目的标记,让代码评审者在 header 中一眼识别;footer 中的 BREAKING CHANGE 则适合承载更详细的破坏性说明,描述受影响用户需要如何迁移。二者配合既能高效传达信息,又能保留完整上下文。

#
★★★

3. Conventional Commits 的 release-please、semantic-release 自动化生成 CHANGELOG

请说明 release-please 与 semantic-release 如何基于 Conventional Commits 自动生成 CHANGELOG 并推进版本号?

  • 两种工具的核心机制
  • 自动版本推算与 CHANGELOG 生成过程
  • 两者的差异与适用场景

release-please 和 semantic-release 都是基于 Conventional Commits 的自动化发布工具,它们通过解析提交历史中的 featfixBREAKING CHANGE 等标记来推算下一个版本号并生成 CHANGELOG。semantic-release 在每个运行周期内自动确定版本(从 PATCH 到 MAJOR 的语义阶梯),创建 git tag、生成 release notes,并可选地执行发布到 npm 等操作。release-please 则采用"release PR"模式,它维护一个存储了当前版本信息的特殊文件,当检测到新提交时自动开启一个版本发布 PR,该 PR 中预先计算好版本号、更新 CHANGELOG 和版本文件,人工合入后即完成发布。两者都强调"从提交历史推断版本",避免人工维护版本号。

这类工具的核心价值是消除"人工决定版本号 + 手写 CHANGELOG"的重复劳动与错误。release-please 的 PR 审核模式更适合希望保留人工确认环节的团队;semantic-release 的全自动模式配合完善的 CI 更适合持续部署。二者都依赖严格的 Conventional Commits 纪律,否则版本推算会失真。

#
★★

4. Conventional Commits 的 commitlint 配置(@commitlint/config-conventional)

如何配置 commitlint 使用 @commitlint/config-conventional 规则集来约束提交消息?

  • commitlint 的作用与配置入口
  • @commitlint/config-conventional 的默认规则
  • 与 git hook 的集成

commitlint 是一个校验提交消息是否符合规范的工具,@commitlint/config-conventional 是它提供的与 Conventional Commits 规范对应的预设规则集。使用方式是在项目中创建 commitlint.config.js(或 .commitlintrc),并通过 extends: ['@commitlint/config-conventional'] 引入该规则集。该规则集会校验 header 的 type 是否在允许列表中(如 feat、fix、docs、style、refactor 等)、scope 格式、header 最大长度、body/footer 的存在性等。commitlint 通常通过 husky 的 commit-msg hook 在提交时执行拦截,不合格的提交会被拒绝。

配置 commitlint 的目的是把规范从"口头约定"变成"机器强制"。@commitlint/config-conventional 提供了开箱即用的合理默认值,团队可通过 rules 字段覆盖默认规则(如缩小 type 列表、限制 header 长度)。与 husky 集成后,开发者在提交时即可获得即时反馈,避免提交历史被污染。

#
★★

5. Conventional Commits 与 git message 的 lint(commitlint、commitizen)协同

请说明 commitlint 与 commitizen 如何协同,共同保证 git 提交消息符合 Conventional Commits 规范?

  • commitlint 与 commitizen 各自的职责
  • 两者如何配合形成"写规范 + 验规范"闭环
  • 各自的触发时机

commitizen 是交互式提交辅助工具,它通过引导式问答(选择 type、填写 scope、描述等)帮助开发者"规范地写"提交消息,常用配置为 cz-conventional-changelog 适配器,触发方式为 git cznpx cz。commitlint 则在提交时"校验"已写好的消息是否符合规范。两者形成互补闭环:commitizen 在写入阶段降低错误概率,commitlint 在提交阶段兜底拦截不合规消息。commitizen 通常与 husky 的 prepare-commit-msg hook 配合在提交时自动弹出向导,commitlint 则挂在 commit-msg hook。

单独使用 commitlint 只能"事后拦截",开发者在写消息时仍可能犯错;单独使用 commitizen 则无法强制团队都使用交互式方式。二者的协同是"引导 + 校验"的完整防线,既提升体验又保证一致性,是规范落地的常见组合。

#
★★

6. Conventional Commits 与 squash merge 的协同

在 GitHub 等平台使用 squash merge 合并 PR 时,如何与 Conventional Commits 协同以保证提交历史规范?

  • squash merge 的合并机制
  • 如何保证 squash 后的提交仍符合规范
  • 与自动发布工具的关系

squash merge 会将 PR 中所有提交压缩为单个提交后合入主干。这既是机会也是风险:如果 PR 内提交杂乱,squash 后只保留一个提交,可保持主干历史整洁;但若该 PR 的标题或 squash 后的消息标题不符合 Conventional Commits,则会污染主干历史。协同做法是:将 PR 标题作为 squash 提交的 header,并强制 PR 标题遵循 Conventional Commits 格式(如通过 PR 标题校验的 CI 或行动),同时利用 PR 描述中的 body/footer 作为提交正文,承载 BREAKING CHANGE 等详细说明。

在使用 squash merge 的团队里,PR 标题实际上取代了每个提交成为历史的最小单元,因此规范化 PR 标题比规范每个提交更关键。自动化发布工具(release-please、semantic-release)解析的是 squashed 后的历史,所以确保 squash 后的消息符合规范,是让版本推算与 CHANGELOG 生成正确的必要条件。

#
★★

7. Conventional Commits 在 git hook(commit-msg)的强制门禁

如何通过 git hook(commit-msg)强制提交消息符合 Conventional Commits 规范?

  • commit-msg hook 的作用与触发时机
  • 结合 commitlint 的实现方式
  • 与 husky 的集成

commit-msg 是 git 在用户提交时触发、且以提交消息文件为参数执行的 hook,适合做提交消息校验。实现方式通常是通过 husky 配置 commit-msg 钩子,在其中运行 commitlint:npx --no -- commitlint --edit $1。当校验失败时,hook 以非零退出码终止本次提交,从而强制开发者在提交前修正消息。

强制门禁的关键是 hook 在提交"落盘"之前执行,若校验失败则提交被拒绝。相比仅靠 code review 人工把关,commit-msg hook 把规范变成硬性约束,保证所有进入历史的消息都合规。这是自动化发布工具可靠运行的前提。

#
★★

8. Conventional Commits 的"type"语义边界中 feat vs fix、refactor vs perf

请说明 Conventional Commits 中 type 的语义边界,特别是 feat vs fix、refactor vs perf 之间如何区分?

  • feat 与 fix 的区别
  • refactor 与 perf 的区别
  • 错误分类对版本号的影响

feat(feature)表示新增用户可见的功能,fix 表示修复缺陷;两者都会影响版本号(feat 触发 MINOR,fix 触发 PATCH)。refactor 表示不改功能、不修 bug 的代码结构调整,perf 表示性能优化。区分 refactor 与 perf 的关键在于目的:refactor 的目标是改善可读性/可维护性而不改变行为,perf 的目标是提升资源或时间效率。若某个改动同时兼具两者,通常以主要意图归类。错误分类会导致版本号误判,例如把不兼容的改动标成 fix 会漏掉 MAJOR 升级。

语义边界决定了自动化工具如何推算版本。feat/fix 是"版本驱动"类型,refactor/perf/docs 等是"非版本驱动"类型(不改变版本号)。把破坏性变更误标为 fix 是常见错误,会导致发布不安全版本。因此团队应明确 type 定义并在评审中提醒。

#
★★

9. SemVer 2.0.0 的三段式版本中 MAJOR.MINOR.PATCH 的语义定义

请说明 SemVer 2.0.0 中 MAJOR.MINOR.PATCH 三段式版本的语义定义?

  • 三段分别的含义
  • 各自的触发条件
  • 版本号的作用

SemVer(语义化版本)2.0.0 规定版本号格式为 MAJOR.MINOR.PATCH。MAJOR 在不兼容 API 变更时递增;MINOR 在向后兼容的功能新增时递增;PATCH 在向后兼容的缺陷修复时递增。它允许在 MINOR 或 PATCH 前使用预发布标识(如 1.0.0-alpha),以及在构建元数据后使用 + 加构建号(如 1.0.0+build.5)。版本号的作用是让依赖方通过版本号即可判断兼容性,从而安全地自动升级依赖。

三段式版本的实质是"用版本号传达兼容性语义"。只要遵循规范,依赖方就能通过 ^1.2.0 等范围安全地接收兼容性(PATCH/MINOR)更新,而无需查看每个版本的实际变更。这是现代包管理生态(npm、Cargo、PyPI)得以自动解析依赖的基础。

#
★★

10. SemVer 与"hyrum's law"(海拉姆定律)的张力

请说明 SemVer 与"Hyrum's Law"(海拉姆定律)之间的张力?

  • Hyrum's Law 的含义
  • 它如何挑战 SemVer 的兼容性承诺
  • 工程上的应对策略

Hyrum's Law(海拉姆定律)指:当你的 API 被足够多的使用者使用时,你无法再修改任何行为而不使某些使用者破坏,即便你从未承诺过该行为。这打破了 SemVer 的一个前提——即"只要 API 契约不变,向后兼容就有保证"。实际上,用户可能依赖未文档化的行为、实现细节、错误信息、甚至运行时性能表现,这些都不在 SemVer 的契约范围内,但一旦被依赖,就成了"有效契约"。

这导致 SemVer 的 MAJOR 升级承诺在实践中难以完全兑现。工程应对策略包括:谨慎对待"未承诺行为"的稳定性、将内部实现细节封装以避免被依赖、通过测试与文档明确契约边界、以及衡量破坏性变更在真实用户中的影响。这提醒我们 SemVer 是"尽力而为"的契约,而非绝对保证。

#
★★

11. SemVer 的 0.x.y 语义中初始开发期的不稳定性

请说明 SemVer 中 0.x.y 版本号在初始开发期的语义?

  • 0.x.y 的语义定位
  • 如何处理 0.x.y 的破坏性变更
  • 对依赖方的影响

SemVer 规定,在 0.x.y 阶段(主版本号为 0)代表初始开发期,此时公共 API 尚不稳定,任何变更都可能破坏兼容性。在此阶段,MINOR 版本号递增(0.1.x → 0.2.x)可能包含破坏性变更,而 PATCH 表示修复。语义上,0.x.y 等同于"everything may change at any time"。很多库在 0.x 阶段使用 0.1.00.2.0 等,并在 0.x 中发布破坏性变更而不递增 MAJOR(因为 MAJOR 仍为 0)。

这给依赖方带来风险:^0.2.0 的 npm 范围会锁定在 0.2.x(因为 npm 对 0.x 的 ^ 有特殊处理),但即便如此,0.x 阶段仍可能语义漂移。依赖方应谨慎对待 0.x 库,或将其视为"可能破坏"的依赖。对维护者而言,应在 0.x 阶段尽早稳定 API 以尽快进入 1.0.0。

#
★★

12. SemVer 的 MAJOR 升级条件中不向后兼容的 API 变更

请说明 SemVer 中触发 MAJOR 版本升级的条件?

  • MAJOR 升级的触发条件
  • 什么是"不向后兼容的 API 变更"
  • 常见例子

根据 SemVer,当做出不向后兼容的公共 API 变更时,MAJOR 版本号必须递增。所谓"不向后兼容",指依赖方在升级后无法在不修改自身代码的情况下继续工作。常见例子包括:删除或重命名公共函数/方法/类、改变函数签名或返回类型、移除参数、改变默认行为、移除已发布的功能等。MAJOR 递增时,MINOR 和 PATCH 都归零(如 1.4.2 → 2.0.0)。

MAJOR 升级是向用户发出"破坏性变更"的明确信号,用户在升级时需阅读迁移指南。判断是否触发 MAJOR 需要审视"公共 API 契约"是否有破坏性变化,而非仅看改动规模。这要求维护者明确定义公共 API 边界,否则无法判断哪些变更属于破坏性变更。

#
★★

13. SemVer 的 MINOR 升级条件中向后兼容的功能新增

请说明 SemVer 中触发 MINOR 版本升级的条件?

  • MINOR 升级的触发条件
  • 什么算"向后兼容的功能新增"
  • 与 PATCH 的区分

根据 SemVer,当以向后兼容的方式新增功能时,MINOR 版本号递增。所谓"向后兼容的功能新增",指在保持既有 API 不变的前提下新增加能力,例如新增公共方法、新增可选参数、新增类/模块、新增导出等。这些新增不破坏已有使用方。MINOR 递增时 PATCH 归零(如 1.2.4 → 1.3.0)。在 0.x 阶段,MINOR 递增可能包含破坏性变更。

MINOR 升级既传达"有新功能",又传达"兼容性未破坏",因此依赖方可以安全地升级。将功能新增与缺陷修复区分开,是让依赖方在 ^ 范围下自动获取新功能的关键。这也是为什么 feat 提交触发 MINOR、fix 触发 PATCH 的约定。

#
★★

14. SemVer 的 PATCH 升级条件中向后兼容的 bug 修复

请说明 SemVer 中触发 PATCH 版本升级的条件?

  • PATCH 升级的触发条件
  • 什么叫"向后兼容的 bug 修复"
  • 与 MINOR 的区分

根据 SemVer,当进行向后兼容的缺陷修复时,PATCH 版本号递增。PATCH 修复不改变公共 API 的签名或行为契约,只修正实现中的错误(如错误的返回值、崩溃、边界条件处理等)。PATCH 递增不触碰 MINOR 和 MAJOR(如 1.2.4 → 1.2.5)。某些看似 bug 修复但实际改变了依赖方所依赖行为的改动,可能应视为破坏性变更而升级 MAJOR。

PATCH 是依赖方最安全、最应自动采纳的升级类型,因为它意味着"行为修正且无破坏"。PATCH 与 MINOR 的区分在于"是否新增能力"——只修 bug 用 PATCH,新增功能用 MINOR。这要求维护者准确判断修复是否真的向后兼容。

#
★★

15. SemVer 的先行版本(pre-release)中 1.0.0-alpha、1.0.0-rc.1 的语义

请说明 SemVer 先行版本(pre-release)如 1.0.0-alpha、1.0.0-rc.1 的语义?

  • pre-release 标识的格式与位置
  • 语义含义与优先级
  • 与正式版的比较

SemVer 允许在正式版本号后追加由连字符连接的 pre-release 标识,如 1.0.0-alpha1.0.0-beta.11.0.0-rc.1。pre-release 表示该版本尚未稳定,可能包含未完成的功能或已知缺陷,不应被依赖方当作正式版本使用。pre-release 版本的优先级低于对应正式版本(1.0.0-alpha < 1.0.0),且标识符按字母数字顺序比较(如 alpha < beta < rc)。标识符可以包含数字和点,如 alpha.1

pre-release 用于在正式发布前向用户提供预览,便于收集反馈、进行 RC 测试。其优先级规则保证依赖方在默认范围(如 ^1.0.0)下不会自动获取 pre-release 版本,除非显式指定。命名约定(alpha/beta/rc)虽非 SemVer 强制,但已是行业惯例。

#
★★

16. SemVer 与 calendar versioning(CalVer)中 Ubuntu、Chrome 的取舍

请比较 SemVer 与 Calendar Versioning(CalVer)的取舍,并说明 Ubuntu、Chrome 等为何采用 CalVer?

  • SemVer 与 CalVer 的核心差异
  • CalVer 的格式与适用场景
  • 各自动物性选择的原因

CalVer(日历版本)使用日期作为版本号,常见格式如 YY.MM.RR(年.月.修订)或 YYYY.MM.DD。Ubuntu 使用 22.0424.04 等(年份.月份),Chrome 使用连续递增的整数版本号(如 120、121)。SemVer 强调"语义兼容性",适合面向开发者的库/API;CalVer 强调"时间可读性",让用户或发布者一眼看出版本的发布时间与节奏。Chrome 的连续大版本号与发布节奏(约每 4 周一次大版本)绑定,因为其频繁发布且用户无需关心 API 兼容性细节;Ubuntu 的 LTS 命名(如 24.04 LTS)让用户直接通过版本号判断发布周期与支持时长。

选择取决于受众与发布模式。面向"依赖方需要判断兼容性"的库/框架优先 SemVer;面向"用户需要知道是新还是旧、发布周期如何"的消费级产品优先 CalVer。CalVer 牺牲了兼容性语义,但要表达清楚的兼容性信息仍可通过 LTS 标签、支持矩阵补充。两者并非互斥,部分项目会组合使用。

#
★★

17. SemVer 的 Cargo 版本约束中^1.2.3、~1.2.3、1.2.* 的差异

请说明 Cargo 中版本约束 ^1.2.3~1.2.31.2.* 的差异?

  • caret(^)约束的语义
  • tilde(~)约束的语义
  • 通配符(*)约束的语义

在 Cargo 中(与 npm 的 ^/~ 语义略有差异):

  • ^1.2.3 允许 >= 1.2.3,且 < 2.0.0,即允许同一 MAJOR 内所有更新(若 MAJOR 为 0,则有特殊规则:^0.2.3 允许 >= 0.2.3 且 < 0.3.0)。
  • ~1.2.3 允许 >= 1.2.3 且 < 1.3.0,即只允许同一 MINOR 内的 PATCH 更新。
  • 1.2.* 允许 >= 1.2.0 且 < 1.3.0,语义等价于允许 1.2 系列内任意版本。 Cargo 默认使用 ^ 约束(在 Cargo.toml 中写 1.2.3 等价于 ^1.2.3)。它通过 Cargo.lock 锁定精确版本,保证构建可复现。

这些约束是在"弹性"与"安全性"之间权衡。^ 让依赖方自动获得同 MAJOR 内的兼容更新,~ 更保守(只收 PATCH),通配符则精确控制 MINOR 范围。Cargo.lock 进一步锁定实际使用的精确版本,使 ^ 的弹性不会导致不同机器构建不一致。理解差异有助于正确声明依赖范围。

#

18. Conventional Commits 与 Gitmoji 的取舍

请比较 Conventional Commits 与 Gitmoji 的取舍?

  • Gitmoji 的特点
  • 两者的差异
  • 各自的适用场景

Gitmoji 是一种用 emoji 作为提交消息前缀的约定,如 :sparkles: feat: 新功能:bug: fix: 修复,通过表情符号快速传达变更类型。它与 Conventional Commits 的一个关键区别是:Gitmoji 是非正式、无严格规范约束的,且 emoji 的种类远多于 Conventional Commits 的 type 列表,表达更灵活丰富。但这也带来问题:emoji 无法被标准化工具解析,且主观性强、难以保证一致性。Conventional Commits 则用结构化、机器可解析的 type 单词,便于自动化(CHANGELOG、版本推算)。

取舍在于"人类可读性"与"机器可解析性"。Gitmoji 更直观、更亲和,适合内部项目或追求表达力的团队;Conventional Commits 更利于工具链与严格流程。实践中可两者结合(emoji + 结构化 type),但应明确主从关系,避免 emoji 破坏解析。若团队依赖语义化发布,应优先保证 Conventional Commits 的规范性。

#

19. Conventional Commits 的 type 自定义扩展(warning、security、deps)

请说明 Conventional Commits 的 type 自定义扩展,如添加 warning、security、deps 等自定义类型?

  • type 自定义扩展的方式
  • 扩展类型在工具链中的识别
  • 自定义的注意事项

Conventional Commits 规范允许团队扩展自定义 type,只要遵循 type: description 的结构。常见自定义如 security(安全修复)、deps(依赖更新)、warning(警告/提示)等。实现方式是在 commitlint 配置中扩展 type 枚举列表,并在 CHANGELOG 生成工具(如 standard-version、release-please 的配置)中指定如何映射这些自定义 type 到 CHANGELOG 的分类。但要注意:只有 featfix 和 BREAKING CHANGE 会影响 SemVer 版本号,自定义 type 默认不触发版本升级,除非显式配置。

自定义 type 的动机是让提交消息更精确地表达变更类别,提升可读性与 CHANGELOG 分组。但过度扩展会破坏一致性、增加学习成本,且若工具未配置,自定义 type 可能被忽略或归类错误。建议仅在确有需要时扩展,并统一在 commitlint 与 CHANGELOG 工具中同步配置。

#

20. SemVer 的 npm 版本范围中^、~、>=、<、1.2.x 的工程语义

请说明 npm 中版本范围 ^~>=<1.2.x 的工程语义?

  • 各操作符/通配符的含义
  • 对 0.x 的特殊处理
  • 在实际工程中的语义

npm 版本范围语法:

  • ^1.2.3:允许 >= 1.2.3 且 < 2.0.0(同 MAJOR 内更新)。对 0.x 有特殊处理:^0.2.3 允许 >= 0.2.3 且 < 0.3.0,^0.0.3 仅允许 0.0.3。
  • ~1.2.3:允许 >= 1.2.3 且 < 1.3.0(同 MINOR 内 PATCH 更新);~1.2 等价于 >=1.2.0 <1.3.0
  • >=1.2.3:允许 >= 1.2.3 的所有版本。
  • <1.2.3:允许 < 1.2.3 的所有版本。
  • 1.2.x:等价于 >=1.2.0 <1.3.0(不固定最后一位)。
  • 精确版本 1.2.3:仅允许 1.2.3。 npm 使用 package-lock.json 锁定实际安装版本以保证可复现。

^ 是 npm 默认推荐的多数场景(自动获取兼容更新),~ 更保守。>=< 更灵活,可组合出范围(如 >=1.0.0 <2.0.0)。对 0.x 的特殊处理反映了"0.x 阶段不稳定"的语义,避免 ^ 自动引入破坏性变更。理解这些语义有助于正确声明依赖、避免意外升级或无法解析。

#

21. SemVer 的"deprecation"(弃用)周期中标记 → 警告 → 移除

请说明 SemVer 语境下"弃用"(deprecation)的周期:标记 → 警告 → 移除?

  • deprecation 的三个阶段
  • 各阶段对应的版本号策略
  • 工程价值

规范的弃用周期通常分三步:标记(deprecate)——在某个版本中将 API 标记为已弃用(如 Java 的 @Deprecated、程序化 log 警告),但功能仍可用,此阶段通常不递增 MAJOR(可能只做 MINOR/PATCH 说明);警告(warn)——在后续版本中加强弃用警告,提示用户准备迁移,同时提供替代 API 说明;移除(remove)——在若干版本后移除该 API,此时必须递增 MAJOR 版本号以明确破坏性变更。整个过程通常横跨多个 MINOR/PATCH 版本,给用户充足迁移时间。

弃用周期是在"保持向后兼容"与"维持代码库健康"之间平衡的机制。通过分阶段标记,用户能在破坏性变更真正到来前收到明确信号并完成迁移,从而降低 MAJOR 升级的冲击。工程上应设置合理的弃用时长(如至少一个 MAJOR 版本轮回),并在文档中记录迁移路径。