软件正确性=想要的都做了+不想要的没做

正确的代码,不只是实现了该实现的,还包括没有实现不该实现的,不多不少,刚刚好就是正确。

引子:一个被忽视的 " 正确性 " 另一半

在用AI编程时,常常发生如下的现象:

需求很简单:" 在用户支付成功后,发送一条 App 内通知。 " AI很"贴心"地加了几个额外功能:

  • 顺带记录了一条埋点日志
  • 多返回了一个payment_detail字段
  • 顺手修复了另一个模块的时区显示问题

上线后,一切看似正常。直到两周后,数据分析师发现埋点数据被重复计算了两次;又过了一周,前端同学发现多出来的payment_detail字段里包含了不该暴露的内部状态码。

需求实现了,但正确性出了问题。

这类事情让我意识到:在AI辅助编程的时代,"正确性"这个词需要被重新定义。传统的"需求都实现了"只是正确性的一半,而另一半------" 不该做的都没有做 "------正变得越来越重要,也越来越容易被忽视。

一、正确性 = 正向覆盖 + 反向约束

我们把"正确性"拆解为两个可独立验证的部分:

  • 正向穷举(该做的都做了) :从需求条目出发,逐一检查代码中是否有对应的实现。方法是查漏,验证成本相对较低,可以穷举。
  • 反向映射(不该做的都没做) :从代码中的每一个"行为"出发,反向追溯它支撑了哪一条需求。如果一段代码找不到任何需求归属,它可能就是多余功能 。方法是查多,需要更深入的语义理解。

正向验证回答的是 " 有没有遗漏? "
反向验证回答的是 " 有没有超出? "

这两个合在一起,才构成完整的正确性定义。

二、 AI" 多做 " 的隐性代价

一个典型的AI编程助手在实现需求时,往往会"顺手"做一些这样的事:

  • 在API响应里多返回几个"可能有用"的字段
  • 添加一个"以后会用上"的缓存层
  • 在修改A模块时,"顺便"修正了B模块的一个小问题
  • 引入一个需求并未要求的设计模式
  • 写一些"以防万一"的防御性逻辑

这些行为短期看无害,甚至显得贴心。但长期积累的代价是惊人的:

|----------|----------------------------|
| 代价类型 | 具体表现 |
| 测试负担 | 多余的代码需要多余的测试覆盖 |
| 安全风险 | 未经评审的额外功能可能引入未知漏洞 |
| 维护成本 | 没人敢删"可能有用"的代码,代码库不断膨胀 |
| 变更阻力 | 多余行为与其它模块产生非预期耦合,改动牵一发而动全身 |

更本质的问题是:一段找不到需求归属的代码,就是一段无法被证明正确的代码。

三、如何让 AI 执行 " 反向映射 " ?四步可落地

我们团队在实践过程中,将反向映射拆解为四个可自动化的步骤。

第一步:将代码拆解为 " 原子行为单元 "

不是按函数拆,而是按对系统状态产生可观测改变的动作拆。

例如,对于这段代码:

def reset_password(user_id, new_pwd):

user = db.find(user_id) # 行为1:读数据库

user.password_hash = hash(new_pwd) # 行为2:修改状态

db.save(user) # 行为3:写数据库

send_email(user.email, "重置成功") # 行为4:发邮件

log.info(f"reset {user_id}") # 行为5:写日志

被拆解为5个原子行为:

第二步:为每个行为标注需求归属

让AI对每一个原子行为回答:"支撑这个行为的需求是哪一条?"

<action id="A1">db.find(user_id)</action>

<supported_by>REQ-01: 通过用户ID查询用户信息</supported_by>

<action id="A4">send_email(user.email, "重置成功")</action>

<supported_by>REQ-03: 密码重置后发送邮件通知</supported_by>

第三步:标记 " 无主行为 "

如果某个行为找不到任何需求条目支撑,就标记为**"** 无主行为 ",进入待处理清单。

第四步:分类处理,而不是一刀切删除

通过这种分类处理,我们既不会僵化地"删除一切",也不会纵容"一切随意"。

四、反向映射的自动化现状

目前,这套反向映射流程的自动化程度已经可以达到一个实用的水平:

|-----------|-----------|----------------------|
| 环节 | 自动化程度 | 依赖技术 |
| 原子行为拆解 | 高 | AST静态分析 + LLM辅助命名 |
| 行为↔需求语义匹配 | 中高 | Embedding相似度 + 需求向量库 |
| 无主行为判定 | 高 | 集合运算 |
| 处理策略选择 | 中 | 规则引擎 + 白名单配置 |

最大的变量在于需求的输入形式

  • 如果需求是结构化的条目(如Gherkin格式、用户故事+验收条件),映射准确率可达90% 以上
  • 如果需求是非结构化的自然语言描述,映射准确率在**60%-70%**之间,仍需要一定的人工复核。

这也提示我们:提升需求的结构化程度,是提升 AI 辅助编程质量的一把关键钥匙。

五、 " 快、好、省 " 的三角博弈:反向映射是杠杆解

在AI辅助编程中,我们常常面临"快、好、省"的三角博弈。

反向映射的引入,对这三个维度的影响出乎意料:

|--------|--------------------------------------------------|
| 维度 | 变化 |
| | 增加约30%的生成时间,但避免了"写错再删"的返工,总工期反而缩短 |
| | 正确性从"需求覆盖"升级为"需求边界精确",质的提升 |
| | 不生成无用代码、不引入无用依赖,实测Token 消耗降低 15%-25% |

反向映射不是额外成本,而是高杠杆的质量投资

六、我们在 Agent System Prompt 中加的那句话

在我们的AI编程助手的System Prompt中,有这样一条最高优先级的指令:

"Omission over Commission (宁可缺,不可过)。 "
当不确定某个功能是否属于需求范围时,默认不做 ,并在交付时标注*"* 未确认项*"* 交由人类决策。
最终交付必须同时满足:
- 需求覆盖率 = 100% (该做的都做了)
- 额外功能覆盖率 = 0%(不该做的都没做)

这条规则的意义在于:把"保守"从一种态度,上升为一种可验证的工程原则

七、结语:正确性的本质是边界管理

归根结底,软件正确性不是一个"做得越多越好"的问题,而是一个边界管理的问题。

  • 正向验证 管理的是覆盖边界------确保需求全部落入代码的范围;
  • 反向映射 管理的是外延边界------确保代码没有任何部分越出需求的范围。

两者合在一起,才构成一条完整、闭环的正确性保障链路。

在Agentic Coding日益普及的今天,我们不仅要教会AI"如何写代码",更要教会它 " 什么不该写 " 。这或许才是从"能用"到"可信"的关键一跃。

相关推荐
死神6544 小时前
Vibe Coding 实战:我用 Nuxt 3+NestJS 做了一个电商运营 SaaS
saas·nestjs·nuxt.js·电商运营·vibe coding
lincats1 天前
Caveman vs Ponytail:AI 编程圈"懒人哲学"两大门派正面交锋
ai·ai agent·vibe coding·claude code
NutShell Wang1 天前
Vite 8.1 深度拆解:Rolldown 统一打包器如何终结前端构建的「双引擎时代」
前端·rust·开源·vite·开发者工具·vibe coding
NutShell Wang3 天前
零依赖架构实战:451个HTML文件如何用63KB平均体积交付3584个交互模块
前端·人工智能·arcgis·npm·开源·html·vibe coding
lincats3 天前
/handoff,只有几行,却是Matt Pocock调用频率最高的 skill
ai·ai agent·vibe coding·claude code·skills
lincats5 天前
Remotion Skill vs HyperFrames Skill:AI视频圈两大技能包正面交锋,最好的对手,最好的搭档?
typescript·vibe coding·claude code·skills
可涵不会debug6 天前
【LangChain系列】 零基础入门实战:从环境搭建到 LCEL 链式完整 Demo 详解
运维·服务器·langchain·vibe coding
AI-Frontiers7 天前
一文搞定Gerrit/Gitee/GitHub MCP Server,实现开发全流程自动化
vibe coding
lincats7 天前
Claude Code 最强Skills路由器:/ask-matt 完全上手指南(入门篇)
ai·vibe coding·skills