变更识别规则
文本 diff 只能告诉你”这一行变了”。本工具要回答的是什么语义变了, 因为不同类型的变更传播方式完全不同。
稳定符号标识
每个符号都有稳定 key,形如:
模块路径::全限定类名#方法名(参数类型,参数类型)
order-service::com.example.order.OrderService#createOrder(Long,Money)
base 与 head 用同一个 key 对齐,所以即使方法在文件里换了行号也能认出是同一个符号。
方法级变更
| 变更类型 | 判定依据 | 传播方式 | 风险种子 |
|---|---|---|---|
METHOD_SIGNATURE_CHANGED |
签名哈希变化(参数或返回类型) | 编译影响 → 所有调用方 | 0.90 |
METHOD_VISIBILITY_CHANGED |
public → 非 public | 编译影响 | 0.85 |
METHOD_CONDITION_CHANGED |
分支/返回条件指纹变化 | 行为影响(控制依赖) | 0.80 |
METHOD_BODY_CHANGED |
方法体哈希变化,签名不变 | 行为影响(较弱) | 0.55 |
METHOD_REMOVED |
符号在 head 缺失 | 编译影响 | 0.95 |
为什么区分条件变化和普通实现变化? 条件变化意味着”同样的输入可能走不同分支”, 这是行为变化最强的信号;而方法体内部重构(改变量名、提取局部方法)往往不影响调用方。
字段与类型
| 变更类型 | 判定依据 | 风险种子 |
|---|---|---|
FIELD_TYPE_CHANGED |
字段类型变化 | 0.85 |
FIELD_ADDED / FIELD_REMOVED |
字段出现/消失 | 0.40 / 0.90 |
ENUM_CONSTANT_ADDED / REMOVED |
枚举值增删 | 0.40 / 0.90 |
TYPE_HIERARCHY_CHANGED |
extends / implements 变化 | 0.75 |
TYPE_ADDED / TYPE_REMOVED |
类型增删 | 0.40 / 0.90 |
契约级变更
| 变更类型 | 判定依据 | 风险种子 |
|---|---|---|
API_ADDED |
新端点 | 0.20 |
API_REMOVED |
端点消失 | 0.95 |
API_CONTRACT_CHANGED |
同一端点响应/请求类型变化 | 0.85 |
SQL_CHANGED |
SQL 语句体变化 | 0.70 |
DDL_CHANGED |
表结构变化(.sql 脚本与内联 DDL) |
0.60 – 0.95,见下 |
CONFIG_CHANGED |
配置值变化 | 0.60 |
BUILD_CHANGED |
pom.xml 变化 | 0.60 |
SQL_CHANGED 与 DDL_CHANGED 的区别: 前者是语句变了(可能只是 WHERE 条件改了),
后者是表结构变了(加列、删列、改列类型)。前者不一定跨程序扩散——只有写入字段集合跟着变才算;
后者是否扩散取决于具体操作,而不是「表结构变了就一定影响所有读写方」。
数据库侧的变更风险按具体操作与兼容性区分:
| 数据库侧变更 | 风险种子 | 是否跨程序扩散 |
|---|---|---|
| 删字段 | 0.95 | 是:读方、写方、映射方 |
| 删表(整表变更) | 0.95 | 是:表级读写与映射 |
| 表重命名(整表变更) | 0.90 | 是:旧表名的表级读写与映射;新表名不扩散 |
| 改字段类型:收窄 / 不兼容 | 0.90 | 是:读方、写方、映射方 |
| 改字段类型:放宽 / 兼容 | 0.45 | 否,只计数 |
| 写入字段集合变化 | 0.85 | 是:只命中读方 |
| 加字段 | 0.40 | 否,只计数;SELECT * / 无列名 INSERT 记盲区 |
加必填字段(NOT NULL 且无默认值) |
0.70 | 是:省略该字段的完整 INSERT(写方);自动填充列除外 |
| 建表(整表变更) | 0.60 | 否:新表不可能是既有依赖 |
| 删除迁移脚本 | 0.60(文件变化) | 否:已执行的 migration 不回滚 |
| 实体字段删除 / Java 类型变化 | 0.85 / 0.85 | 只命中映射方,低置信度 |
加字段与放宽类型不扩散,是因为对方按列名读写时看不见新列,已有值也仍然可表示;
删字段、收窄类型则会让对方读到的值消失或失真,且不一定报错——这是数据库契约里最需要人工确认的一类。
例外是 NOT NULL 且无默认值的加列:对方省略该列的完整 INSERT 会被数据库拒绝,属于确定的写侧不兼容,
因此按 0.70 命中写方;列可空、有默认值,或由数据库自动填充(AUTO_INCREMENT / GENERATED /
IDENTITY)时不算必填。UPDATE / DELETE 不命中,SELECT * 与动态 SQL 仍记盲区。
一条 ALTER TABLE 里的多个动作会拆成多条 DDL_CHANGED,每条带自己的
ddl / table / column / oldType / newType / renamedTo 与风险种子:加列按 0.40(必填 0.70)、
删列按 0.95、收窄按 0.90……而不是让整条语句共用一个结论。列重命名只报旧列名一条
(ddl=DROP_COLUMN、renamedTo=新列名),新列名不会再被记成一次「加列」。
兼容性规则与数据库身份校验见 数据库影响判断。
为什么 API_REMOVED 种子最高? 端点消失时所有消费方都会立刻失败,
没有”可能”的余地。而 API_ADDED 只是新增能力,不影响已有调用方。
消息契约
| 变更类型 | 判定依据 |
|---|---|
TOPIC_ADDED / TOPIC_REMOVED |
主题出现/消失 |
MESSAGE_ROUTE_CHANGED |
Topic、Exchange、Queue、Routing Key 或 Tag 变化 |
MESSAGE_FIELD_ADDED / REMOVED / TYPE_CHANGED |
消息 DTO 字段变化 |
MESSAGE_REQUIRED_CHANGED |
字段必填性变化 |
MESSAGE_SCHEMA_CHANGED |
JSON / Avro / Protobuf Schema 变化 |
消息契约的风险在于它不报错:字段改了但消费方还在读旧字段,程序照常运行, 只是数据悄悄错了。所以这类变更即使风险分不高,也值得复核。
未解析的调用去哪了
解析失败的调用点不会被悄悄忽略,而是进入报告里的「分析边界与盲区」清单:
未解析调用 order-service · OrderService.java:42
getBean("dynamicHandler").handle(x) — 反射调用,静态不可解析
盲区条目本身就是结论的一部分:它告诉你”这部分我没法保证”。