核心概念
理解四个词就能读懂整份报告:契约、使用点、传播、风险与置信度。
契约(Contract)
契约是两个程序共同依赖的、有明确身份的东西。它不属于任何一方,而是双方的约定。
本工具识别六类契约:
| 契约 | 身份是什么 | 例子 |
|---|---|---|
| HTTP 接口 | 规范化路径模板 | GET /api/users/{} |
| 数据库字段 | 表.字段(table.column) |
orders.status |
| 配置项 | 配置 key | app.limit |
| Maven 依赖 | artifactId | common |
| 消息主题 | Topic / Exchange / Queue | order.created |
| 消息 Schema | 消息体结构指纹 | OrderEvent |
数据库契约以字段为单位,不是以表为单位:本程序给自己多查一列属于内部行为,对方看不见,
不算契约变化。字段级匹配前还要确认两侧是同一数据库对象(JDBC URL / 显式 schema),并且只让
「对方能观察到」的变更扩散:删列、收窄类型命中读写方,加列、建表、类型放宽不扩散。
只有 DROP TABLE 这种字段未知的整表变更,才退回按表名匹配。
为什么契约是关键? 两个仓库编译期互不可见,它们唯一的联系方式就是这些共享标识。 所以”是否影响对方”这个问题,可以精确化为”本次改动是否改动了某个契约,而对方是否在用它”。
使用点(Usage)
对方程序里真正读到或写到这个契约的位置。
UserClient#fetchUser调用了/api/users/→ 它是 HTTP 契约的使用点OrderReader.loadOrder里SELECT status FROM orders→ 它是orders.status字段契约的使用点LimitConfig里@Value("\${app.limit}")→ 它是配置契约的使用点
使用点决定了影响的起点。没有使用点,契约改得再狠也不影响对方。
传播(Propagation)
影响不止于直接使用点。如果 UserClient#fetchUser 受影响,那么调用它的
OrderService#canOrder 也受影响——这是在对方程序自己的调用图里做的反向遍历。
本程序改动
↓ 命中契约
UserClient#fetchUser ← 直接使用点(深度 1)
↑ 反向调用边
OrderService#canOrder ← 传播结果(深度 2)
两条重要规则:
- 只在对方图里传播。 不会回到本程序,也不合并两张图——它们本来就不连通。
- 用法决定权重。 返回值参与分支(
CONDITION)权重 1.0,只打日志(LOG_ONLY)权重 0.2。 同样是调用,后果可能差五倍。
风险与置信度(Risk vs Confidence)
这是两个独立的数字,永远不要合并成一个:
| 维度 | 回答的问题 | 高值意味着 |
|---|---|---|
| 风险 | 如果真坏了,有多严重? | 需要优先处理 |
| 置信度 | 静态分析有多确定? | 结论可信,可直接采信 |
最需要人工介入的组合是高风险 + 低置信度:可能很严重,但工具没把握。 此时正确做法是人工确认,而不是当成”确定受影响”或”确定没事”。
反例:一个
public方法被删除,风险很高(调用方会编译失败), 但如果调用方是反射调用的,静态分析找不到它——置信度很低。 两个数字分开显示,才让这种情形可被识别。
一次分析的完整链路
① 取两个版本快照(base / head)
② 索引两版代码 → 各自的代码知识图谱
③ 比对图谱 → 语义变更清单(不是文本 diff)
④ 每个变更 → 找出它触及的契约
⑤ 对每个被选中的其他程序 → 找出契约使用点
⑥ 在使用点所在的图里反向传播
⑦ 打分:风险 + 置信度,并生成可读原因