3194 字
约 10 分钟
16
S24-A07 · AI 生成代码的归属与评审责任
线上出了一个数据串号的事故,回溯到一段由助手生成的查询拼接代码。评审记录里写着"AI 生成,已看"。这句话在事故复盘里没有任何作用:它既不能定位责任人,也不能解释为什么没人发现分支漏了。
归属问题不是法务才会问的问题,它是发布决策的组成部分。谁按下合并键,谁就承接了这段代码的全部后果——这条规则在 AI 出现之前就成立,AI 没有改变它,只是让它更容易被忘记。
1. 归属的三个层面
| 层面 | 回答的问题 | 现实答案 | 谁在执行 |
|---|---|---|---|
| 法律与许可 | 这段代码能不能用、归谁 | 由训练数据来源与输出相似度决定,且不可完全验证 | 法务/合规,事后 |
| 团队内部责任 | 出问题谁复盘、谁修 | 提交者与评审者,与是否 AI 生成无关 | 值班与复盘流程 |
| 职业责任 | 我是否理解自己签下的代码 | 由个人承担,无法转移 | 工程师本人 |
三层里唯一能事前控制的是第三层。前两层都是事后判定,判定依据来自你在提交时留下的痕迹。
2. 许可风险的实际形态
| 风险形态 | 怎么出现 | 可检测性 | 处理 |
|---|---|---|---|
| 训练数据来源不可追溯 | 供应商不公开语料构成 | 不可检测 | 只能按供应商条款承担 |
| 生成片段与开源代码高度相似 | 常见算法、样板代码被逐字复现 | 可检测(相似度) | 替换或补版权头 |
| 许可证传染(copyleft) | 生成内容带入 GPL 片段 | 弱可检测 | 相似命中后人工确认 |
| 专利/算法主张 | 冷门但非零 | 不可检测 | 高风险域走人工审查 |
现实做法不是"证明干净",而是在可检测的层面留下检测记录:跑过相似度、命中过什么、怎么处理的。
# 用许可证扫描器加相似度扫描,输出待人工确认清单
$ scancode --license --json . > /tmp/lic.json
$ grep -l "GPL\|AGPL" /tmp/lic.json && echo "需人工确认"
3. 相似度检测的成本与盲区
| 检测方式 | 成本 | 覆盖 | 盲区 |
|---|---|---|---|
| 公开代码搜索引擎比对 | 低(手工) | 知名片段 | 私有仓库、改写过的代码 |
| 代码克隆检测(token 级) | 中(需基线库) | 结构相似 | 语义相似但结构不同 |
| 供应商自带溯源报告 | 低 | 取决于供应商 | 黑盒,报告不可复核 |
盲区是结构性的:改写两行就能绕过 token 级检测,而改写两行不改变法律风险。所以相似度检测只能降低概率,不能作为"已合规"的证明。
相似度命中后的三步(不要直接合并)
1. 确认来源与许可证
2. 判断是"通用写法"还是"有创造性表达"
3. 有创造性表达 → 重写或替换;通用写法 → 记录判定理由
4. 评审责任不会转移
| 说法 | 为什么站不住 | 正确的表述 |
|---|---|---|
| "这是 AI 写的" | 发布权在人手上,合并键是人按的 | "这段由助手生成,我逐行确认过以下三点" |
| "AI 说这样没问题" | AI 不是责任主体,无法被追责 | "我用 X 验证了边界条件" |
| "评审时看不出来" | 看不出是评审能力问题,不是免责理由 | "这类生成代码需要额外的检查项" |
| "工具默认配置就这样" | 默认值是你的选择 | "我确认过默认值的影响范围" |
机制解释只有一句:责任跟着发布权走。谁能让代码进生产,谁就承接它。这条机制不因工具而变化,所以"AI 生成"在归因上等价于"我从网上抄了一段没看懂的代码"。
5. 评审 AI 代码该多查什么
| 检查项 | 为什么 AI 更容易出错 | 具体动作 |
|---|---|---|
| 上下文理解 | 只看到片段,不知道调用方约定 | 看完整调用链与不变量 |
| 边界条件 | 倾向给"正常路径"实现 | 空值、零、超长、并发 |
| 异常路径 | 容易吞异常或只 log | 检查 catch 块与重试语义 |
| 与既有约定的偏离 | 不知道团队惯例 | 与相邻模块的写法比对 |
| 隐含假设 | 会补上不存在的字段/接口 | 每个外部调用逐一核对 |
生成代码的额外评审清单(贴在 PR 模板里)
[ ] 这段代码在什么输入下会抛异常?谁捕获?
[ ] 新增依赖是否必要?版本与既有约束是否冲突?
[ ] 有没有引入团队里不存在的模式/框架?
[ ] 分支是否覆盖到空集合、单元素、超限三种情况?
[ ] 与相邻模块的错误处理风格是否一致?
6. 不该多查什么
| 项目 | 谁该管 | 为什么不必人看 |
|---|---|---|
| 缩进、换行、引号 | formatter | 工具输出比人稳定 |
| 命名风格 | linter | 规则可枚举 |
| import 顺序 | linter | 同上 |
| 显而易见的空指针检查 | 静态分析 | 覆盖率更高 |
| 测试文件结构 | 测试框架约定 | 有模板 |
把评审注意力放在风格上,是生成代码最典型的浪费:它会让人产生"我认真看过了"的错觉,而真正的风险在语义层。
# 把可自动化的部分交给 CI,人只看语义
lint: { run: "ruff check && eslint .", blocking: true }
format: { run: "prettier --check .", blocking: true }
types: { run: "mypy --strict src", blocking: true }
semantic: { run: "human-review", blocking: true } # 只剩这一层给人
7. 生成占比与维护成本
| 生成占比 | 生成节省 | 理解成本 | 净效果 |
|---|---|---|---|
| < 20% | 小 | 小 | 正 |
| 30–50% | 明显 | 中(需读全部生成部分) | 通常正,取决于审查深度 |
| 60–80% | 大 | 高(代码风格分裂) | 可能为负 |
| > 80%(核心模块) | 最大 | 极高(无人有完整心智模型) | 长期为负 |
判据不是占比,是改这段代码的人是否需要重新推导它。需要重新推导,就等于生成时省下的时间被转移到了维护期,还加了利息。
一个可观测的代理指标
同一文件的平均修改间隔(MTBI)
生成代码密集的文件 MTBI 明显偏短 → 理解不足导致的反复修补
8. 注释与文档的责任
| 类型 | 可信度 | 风险 | 处理 |
|---|---|---|---|
| AI 生成的说明性注释 | 低 | 描述与实现不符,误导后来者 | 与代码同步生成时校验 |
| AI 生成的 API 文档 | 中 | 参数语义写错 | 以类型与测试为准 |
| AI 生成的变更说明 | 中低 | 漏掉副作用 | 由提交者改写 |
| 人工写的决策记录 | 高 | 成本高 | 关键决策必须人工 |
注释的失效比代码的失效更隐蔽:代码错了会报错,注释错了只会让人往错的方向改。
9. 代码溯源要不要标注
| 标注粒度 | 成本 | 价值 | 适用 |
|---|---|---|---|
| 不标注 | 零 | 零 | 一次性脚本 |
| PR 描述里说明 | 低 | 中(复盘可查) | 默认做法 |
| 提交 trailer | 低 | 高(可统计、可筛选) | 规模化团队 |
| 行内注释 | 高 | 低(会腐烂) | 不建议 |
# 提交约定:用 trailer 记录生成参与度,便于事后统计与筛选
Assisted-by: assistant-name
Reviewed-by: human-name
标注的目的不是追责,是在事故复盘时能快速定位"这类代码"的分布。行内注释最容易腐烂,改两行之后就没人维护它了。
10. 团队规范的最小集
| 场景 | 要求 | 理由 |
|---|---|---|
| 涉及资金、权限、隐私字段 | 必须人工逐行看 | 错了不可逆 |
| 并发/事务/幂等逻辑 | 必须人工逐行看 + 有测试 | 生成模型对时序最不可靠 |
| 加密、序列化、协议实现 | 必须人工逐行看 | 细节错误不可见 |
| 业务 CRUD | 常规评审 + 测试 | 风险可控 |
| 测试代码 | 常规评审 | 但要检查断言是否真的断言 |
| 一次性脚本 | 不进入评审流程 | 成本高于收益 |
规范的写法(可执行,不是口号)
- 用目录/标签标记高风险区:risk/finance, risk/auth
- CI 检查:高风险区变更必须有两个 reviewer,其中之一为 owner
- PR 模板强制回答:生成部分占比、验证方式、未覆盖的边界
11. 测试是责任锚点
生成代码最大的隐患不是写错,是没人有能力改。测试把"能不能改"变成"改完有没有坏"。
| 测试类型 | 对生成代码的作用 | 最低要求 |
|---|---|---|
| 单元测试 | 固定住意图与边界 | 关键分支覆盖 |
| 契约测试 | 固定住与外部系统的假设 | 每个外部调用一条 |
| 回归用例 | 固定住历史缺陷 | 每个线上缺陷一条 |
# 生成的实现可以替换,测试表达的是"这段代码必须满足什么"
def test_split_handles_empty_batch():
assert split_batch([], size=100) == []
def test_split_rejects_zero_size():
with pytest.raises(ValueError):
split_batch([1, 2], size=0)
测试写得出来,说明有人理解了需求;写不出来,说明这段代码目前只有生成它的模型理解。
12. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 评审记录只有"已看" | 没有检查清单 | PR 模板加强制项 |
| 同类缺陷反复出现 | 未转成回归用例 | 每个缺陷补一条用例 |
| 生成代码无人敢改 | 缺测试与文档 | 先补测试再改 |
| 风格在文件内分裂 | 生成与手写混在一起 | 提交前跑 formatter,语义分块 |
| 复盘时找不到责任人 | 提交信息缺 trailer | 约定 Assisted-by/Reviewed-by |
13. 边界
| 场景 | 管理要求的强度 | 理由 |
|---|---|---|
| 一次性脚本、数据清洗 | 最低 | 不进生产仓库,出错成本低 |
| 纯脚手架、样板代码 | 低 | 结构可验证,逻辑少 |
| 不进生产仓库的实验代码 | 低 | 无发布权,无责任链 |
| 生产业务代码 | 中高 | 有发布权,有用户 |
| 高风险域(资金、权限、隐私) | 最高 | 错误不可逆 |
规范的价值随发布权的存在而上升。没有发布权的地方套用高风险域的规范,只会让人绕过流程。
14. 自测
- 你的评审记录里,"AI 生成"这句话能回答哪一层归属问题?
- 相似度检测命中之后,你的团队下一步做什么?
- 生成代码的评审清单里,哪几项是"看调用链"而不是"看片段"?
- 有没有哪个文件的平均修改间隔明显偏短,为什么?
- 高风险目录的变更,是否强制要求第二个 reviewer 是 owner?
- 最近一次由生成代码引入的缺陷,补了对应的回归用例吗?
S24-A07 · AI 生成代码的归属与评审责任
https://www.clxhxhhr.top/posts/6223/ 评论
0 条
还没有评论,先写一条吧。