← 返回文章列表
002 — AI · #逆向工程 #Prompt 工程 #输出质量控制 · 2026-08-19 · 18 MIN READ

业务需求生成:把代码翻译成人话的质量工程

一行合并字段,暴露整件事的难度

让大模型把一个「保存合同」功能翻译成业务需求文档,第一版输出几乎一定会出现这样的表格:

字段类型必填说明
合同信息对象包含合同编码、名称、金额等

一行塞了三个字段,说明里挂着两个模糊词。客户看不懂「对象」是什么,测试没法依据「等」字写用例,新人照着它找不到任何一行代码。这行字在任何意义上都不合格,但它是大模型最自然的产出方式——概括、省 token、模仿互联网上泛滥的粗颗粒文档习惯。

业务需求生成这一步(skill3,business-requirement)要产出的文档,读者是客户、产品、测试和新入职的开发。这个读者定位决定了验收标准:代码词汇零残留,每个字段独立成行,每个说法都能对应到真实代码。围绕这个标准,这一步沉淀出了一套完整的质量工程,也是本系列里最容易直接搬到你自己项目里的部分。

先看它的位置:输入是单个链路图文件(doc/link_diagram/xxx-链路图.md链路图生成的产物),输出是对应的业务需求文档(doc/business/xxx-业务需求.md,命名规则就是原文件名里的 -链路图 替换成 -业务需求)。文件即接口的编排协议在这里生效:上下游靠文件名对齐,谁也不用认识谁。

读者决定语言:业务语言与技术语言的分界

整份文档只有一种合法语言。为此技能里维护着一份对照表,把模型最常犯的翻译错误逐一封死:

场景正确(业务语言)错误(技术语言)
数据校验系统验证输入数据是否完整使用 if (name == null) 判断
按钮操作用户点击保存按钮后触发 handleSave 方法
数据展示表格显示合同列表el-table 绑定 tableData
条件分支如果库存不足,则显示警告if (sl < 0) { … }
数据查询根据科室过滤医生列表执行 SELECT * FROM … WHERE …
状态控制审核通过后,修改按钮自动禁用this.disableBtn = true
批处理系统每天自动同步一次 SAP 数据调用 updateSapVoucherData 方法

注意这些「正确示例」的共同点:主语是用户或系统,动词是点击、验证、显示、过滤,条件写成「如果…则…」。技术语言则是另一套主谓宾——方法名做动词,属性做宾语,框架名当形容词。

禁令清单比对照表更长,分六类:禁止代码片段、禁止方法名、禁止控件属性(v-modelrow.xxx)、禁止框架术语(Controller、ServiceImpl、Mapper、DTO)、禁止数据库词汇(SQL、SELECT、MyBatis、#{})、禁止编程概念(循环、递归、try-catch、Promise、多线程)。这份清单的长度本身就是经验值——每一条都对应模型真实犯过的错。

数据类型也有强制翻译表:String 写成「字符」,Integer 写成「整数」,Date 写成「日期时间」,BigDecimal 写成「小数」,Boolean 写成「布尔值」。别小看这一行映射,它是评审时最容易暴露「没翻译干净」的信号——文档里只要出现一个英文类型名,就说明有一段代码没被消化。

零合并原则:质量红线的第一条

零合并原则的表述只有一句话:每个字段独占一行,严禁使用「等、包括、多个、相关、信息」这类模糊词汇。对照开头的错误示例,正确写法是:

字段类型必填说明
合同编码字符合同的唯一标识编码,支持模糊搜索
合同名称字符合同名称,用于显示和检索
开始日期日期时间过滤条件,查询该日期之后的记录
结束日期日期时间过滤条件,查询该日期之前的记录
状态标志整数0=全部 / 1=正常 / 2=停用,控制显示状态

两份表格的信息量天差地别:拆开之后,每个字段有了明确的类型、必填性和业务含义,测试可以逐行生成用例,开发可以逐行核对代码。

零合并的检查不止看「一行里是否有顿号」,它有四条细则:

  • 每一行只能包含一个字段,禁止「字段1、字段2、字段3」式写法
  • 说明列不得出现「等、包括、多个、相关、信息」等模糊词
  • 字段名不得叫「xx 信息」「xx 数据」「xx 参数」这类笼统命名
  • 类型为「对象、集合、数组」的字段一律视为未拆完,必须继续拆到原子字段

最后一条最容易被忽视。模型喜欢把一组参数打包成「查询条件(对象)」,看起来整整齐齐,实际上是把拆解工作转嫁给了读者。规则明确:见到聚合类型就退回重拆。

为什么模型这么爱合并?除了省 token 的天性,还因为训练语料里大量需求文档本身就是这种写法——「包含基本信息、扩展信息等」。所以对抗零合并原则不能只靠一句「不要合并」,技能里把反面案例原文写进了给子代理的任务描述:不得输出「合同信息 | 对象 | 否 | 包含编码、名称、金额等」,必须拆分为独立行。给模型看过确切的坏例子,比十条抽象规则都管用。

输入参数:十类来源,一步都不能漏

输入参数表是业务需求文档里最难写对的部分,因为参数散落在代码的各个角落。技能里把它穷举成十类来源清单,要求逐类排查:

序号来源类别典型形态
1方法/函数签名参数前端事件参数、@RequestParam@RequestBody、函数形参
2传入对象的公共属性domain、实体类、DTO、字典项的字段
3用户输入控件的值输入框、下拉框、日期选择器、复选框的绑定值
4表格数据当前选中行、勾选行集合、表格整体绑定数据
5全局与配置全局变量、静态变量、配置文件、数据字典、环境变量
6数据访问层查询参数MyBatis 的 #{}/${}、Hibernate 查询条件、SQL WHERE 参数
7接口调用参数API 入参、query/data、requests 调用参数、SAP 接口入参
8文件读写参数导入/导出文件字段、OCR 与图像识别输入
9事件参数row、selection、event 等,转换为业务概念
10控件附加数据与外部入参row.idrow.status 等行绑定字段、定时任务入参

这份清单的价值在于它是「技术栈中立」的:不管是 Vue 的 v-model、ExtJS 的 store 还是 Python 的函数入参,都能落进这十类之一。漏掉哪一类,文档里就少一截真相。

提取之后是五步强制校验清单,逐步执行、不可跳过:

  1. 源码追踪:从方法签名开始逐层追参数传递,记录每个变量的来源(用户输入、数据库、全局配置、计算结果、外部系统),不得跳过中间层
  2. 参数验证:字段名与源码一致;类型用中文;必填性要有代码依据(前端必填校验、后端 @NotNullif (xxx == null) 任居其一则标「是」);说明必须独立完整,禁止写「同合同编码」这类引用式说明
  3. 零合并强制检查:逐行扫表格,见合并就拆
  4. 交叉验证:调用处实参与定义处形参一一对应;if-else 分支里用到的变量全部入表;业务规则里引用的字段都能在参数表里找到;对照链路图代码片段核对输入控件值
  5. 完整性自检:统计参数数量与代码逻辑比对是否合理;随机抽三个字段回链路图找到出处;检查是否残留「对象、集合、数组」类型;发现问题重新分析

第五步的「随机抽查三个字段」是个很务实的动作——它不追求全量复核,但足以逼着生成端保持每一条都有出处的心态。

输出字段:三级优先级与两条铁律

输出字段的麻烦在于「同一个功能有多个说法」:SQL 的 SELECT 别名、表格列定义、实体类属性,三处各有一份字段清单,以谁为准?

技能给出的答案分两问。第一问,字段清单从哪来?按三级优先级取:

mermaid
flowchart TD
    A["功能点的输出字段提取"] --> B{"功能类型"}
    B -->|"查询 / 打印 / 列表展示"| C["一级:数据访问层<br/>SELECT 列别名"]
    B -->|"接口返回 / 脚本输出"| F["一级:返回对象属性"]
    C --> D["二级:展示层列定义<br/>el-table label / ExtJS header / colModel label"]
    F --> G["二级:查询 SELECT 列"]
    D --> E["三级:实体类属性兜底"]
    G --> H["三级:DTO / 实体属性兜底"]
    C -->|"别名清单与展示层绑定字段<br/>逐一对上(完整性验证)"| I["写入输出文档"]
    D -->|"列标题与列顺序<br/>(唯一的命名基准)"| I

图 1:输出字段的三级优先级。SELECT 别名决定「有哪些字段」,展示层定义决定「叫什么名字、按什么顺序」。

这个分工暗含一个洞察:数据访问层最诚实,展示层最权威。SQL 是最终吐出数据的地方,字段最全、别名最接近业务本名;但用户实际看到的是界面列,列标题和列序是他们心中的「文档事实」。

于是有了第二问,也是两条铁律:

  • 列名严格一致:输出文档中的列标题必须与展示层定义完全一致,严禁修改、翻译、重命名
  • 列顺序严格一致:必须按展示层列定义的顺序输出,严禁调整、重排、优化,哪怕业务逻辑上某列「应该」在前

配套还有一条禁令:SQL SELECT 别名只能用来验证字段完整性,严禁反向用别名改写列名。为什么这么较真?因为模型有「好心办坏事」的倾向——看到 SQL 里别名是「合同编号」,界面列写的是「合同编码」,它会很自然地把两者「统一」掉。可这份文档要跟界面一一对照给测试用,列名改一个字,用例就对不上号了。

一个真实的提取过程,从 MyBatis 的 XML 开始:

xml
<select id="getList" resultType="map" parameterType="map">
    select
        t.contract_no   as "合同编码",
        t.contract_name as "合同名称",
        t.amount        as "金额",
        t.status        as "状态"
    from pbk.PBK_CONTRACT t
    where 1=1
    <if test="name != null and name != ''">
        and t.contract_name like concat('%', #{name}, '%')
    </if>
</select>

对照页面上的表格列定义,输出结果表长这样:

列标题类型宽度对齐方式编辑属性业务说明
合同编码字符型120px左对齐只读合同唯一编码
合同名称字符型200px左对齐只读合同名称
金额小数型120px右对齐只读合同金额,保留 2 位小数
状态整数型80px居中只读状态标志

顺带能看到输入参数的交叉验证在这里生效:上面 XML 里 #{name} 这个 WHERE 参数,必须出现在同功能点的输入参数表里(「合同名称,用于模糊检索」),两张表互相咬合。

输出部分还有几条完整性要求:隐藏列(主键、状态标志、关联 ID 这类只服务业务逻辑的列)必须列出并说明用途;文末必须给出列统计「共 X 列,其中可见列 Y 列、隐藏列 Z 列」;排序规则、汇总规则(合计行、平均值、计数)如有必须写明。列数不一致时要回头检查是不是有计算列或动态列——数不上账,就不许交卷。

两阶段执行:先把输入做对,再谈输出

一个链路图文件里往往有多个事件函数:保存、查询、删除、行点击……技能的做法是给每个事件函数派一个子代理并行处理,每个子代理都遵守严格的两阶段纪律:

mermaid
flowchart TD
    subgraph S["子代理(每个事件函数一个)"]
        S1["阶段一:提取输入参数<br/>执行五步强制校验清单"] -->|"自检通过前<br/>禁止进入下一阶段"| S2["阶段二:提取输出字段<br/>三级优先级 + 列名列序铁律"]
        S2 --> S3["输出单个功能点完整内容<br/>(从 1.1 功能点名称开始,不含模块标题)"]
    end
    subgraph M["主代理"]
        A["读取单个链路图文件"] --> B["按事件函数拆分任务"]
        B --> C["分批启动子代理,每批最多 5 个"]
        C --> D["阶段一校验:输入参数"]
        D -->|"发现合并字段 / 模糊词 / 技术词"| E["退回该子代理重写"]
        E --> D
        D -->|"全部通过"| F["阶段二校验:编号、格式统一、输出完整性、业务规则质量"]
        F --> G["组装文档:功能目录 + 各功能点"]
        G --> H["最终自检 + 打印 输出文件:xxx-业务需求.md"]
    end
    C -."任务分配(含反面案例)".-> S1
    S3 -."结果返回".-> D

图 2:两阶段执行全景。子代理内部先输入后输出,主代理层面再做一轮同样的两阶段校验,不合格就退回重写。

两阶段的本质是把一个容易顾此失彼的任务切成两个各自可控的子任务。输入参数提取是「地毯式搜索」,要求全覆盖、零合并;输出字段提取是「精确制导」,要求优先级正确、列名列序不越权。混在一起做,模型经常输入提一半就开始写输出,回头再补参数,补的过程就是出错的过程。强制「阶段一自检不通过不得进入阶段二」,等于在流程里插了一道闸门。

主代理的校验同样分两阶段,而且校验粒度细到词:逐行扫输入参数表找合并字段,扫说明列找「等、包括、多个、相关、信息」,扫全文找「参数1、字段A、DTO、对象」这类技术残留,发现即退回。全部输入参数过关后,才进入编号(1.1、1.2 按按钮或 Tab 顺序)、格式统一和输出完整性校验。

给子代理的任务描述里还有两个值得借鉴的细节。一是把反面案例原文带上(前文那行「合同信息 | 对象 | 否 | 包含编码、名称、金额等」),让每个子代理开工前先看过坏例子长什么样。二是明确输出范围「不含功能模块标题,从 #### 1.1 功能点名称 开始」——子代理各自为战时最容易各自加一层自己的标题结构,主代理组装时就会格式打架。

并行本身也有纪律:事件函数不超过 5 个一次性全开;超过 5 个分批跑,每批最多 5 个,整批完成再开下一批。等待期间每 30 秒输出一次进度心跳([进度] 正在等待子代理完成... 已等待 X 秒,剩余 Y 个任务),保持进程活跃、防止超时误判。这套并行机制的通用设计在子代理并行一篇里展开。

功能点取舍:不是所有事件都值得一个章节

链路图里记录的每个事件函数并不都配得上「功能点」的待遇。技能用三张清单划清边界:

处置类别示例
必须提取为独立功能点按钮操作类增加、删除、修改、查询、导出、导入、打印、审核、撤销审核、提交、保存、取消、刷新、启动任务
必须提取为独立功能点业务操作类数据提交、状态变更、业务流程推进、批处理步骤
必须提取为独立功能点数据展示类复杂统计、报表生成、数据聚合
必须提取为独立功能点表格行切换查询明细主表选行触发下方明细刷新(row-click、selection-change 等事件)
不提取初始化类页面加载(mounted、created)、构造函数
不提取关闭类页面关闭、返回、释放资源
不提取辅助类纯 UI 美化、样式设置、简单属性赋值
不提取系统类日志记录、通用异常捕获
合并进业务规则表格切换类列排序、分页切换,在相关功能的业务规则里描述切换逻辑
合并进业务规则控件点选类checkbox、radio、select、tab 切换,描述联动效果与数据过滤
合并进业务规则状态联动类disable/visible 变化、级联下拉更新,描述触发条件与效果
合并进业务规则单元格值变化值编辑事件,描述联动、校验与后续处理
合并进业务规则复选框变化选中/取消选中后的数据过滤规则

这张表读起来平淡,实际是整份文档「颗粒度」的定价表。提取太多,文档被 mounted 和 handleClose 灌水,真正的主干功能被淹没;提取太少,测试对着一个「查询」功能点要覆盖七八种联动场景,颗粒度又不够写用例。「合并进业务规则」是中间态:事件本身有业务含义(选中某行后明细过滤),但独立成章节嫌碎,就把它写进所属功能的业务规则段落。

业务规则段落本身有必含清单:核心业务逻辑、关键分支(if-else 全部转写为「如果…则…否则…」)、表格切换逻辑、控件联动、状态联动规则、数据校验规则、权限控制规则(如有)。这段是整份文档里最考验「翻译功力」的地方——它要求模型读完一整个方法体后,用因果句式复述出来,一个技术词都不许带。

产物长什么样

所有功能点过关后,主代理按固定模板组装文档。骨架是「功能目录 + 功能点」两级:

text
## 功能模块名

### 功能名称
(功能说明,写给客户/产品/测试/新开发看)

**功能目录:**
| 功能点编号 | 功能点名称 |
|-----------|-----------|
| 1.1 | 台账列表查询 |
| 1.2 | 台账列表导出 |
| 1.3 | 明细行切换查询 |

#### 1.1 台账列表查询
(功能点说明)

##### 输入参数
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ... | ... | ... | ... |

##### 输出结果
界面展示字段(展示层列):
| 列标题 | 类型 | 宽度 | 对齐方式 | 编辑属性 | 业务说明 |
|--------|------|------|----------|----------|----------|
| ... | ... | ... | ... | ... | ... |

隐藏列(用于业务逻辑):...
共 X 列,其中:可见列 Y 列,隐藏列 Z 列
默认排序:按 [合同编码] [升序] 排列

##### 业务规则
(纯业务语言的自然语言描述:核心逻辑 + 关键分支 + 联动规则 + 校验规则)

查询类、普通返回类、导出打印类功能各有对应的输出表模板,字段结构略有差异,但三段式骨架(输入参数、输出结果、业务规则)雷打不动。

文档落盘到 doc/business/ 目录,文件名由上游链路图替换后缀而来。最后一步是打印完成标志 输出文件:xxx-业务需求.md——中文冒号、完整文件名、不带路径,调度器捕获这一行后触发下一步的模块归类。编排协议在这一步的末尾严丝合缝地接上。

回看整篇,这一步的质量工程其实就做了三件事:把「写好文档」拆解成可逐条检查的规则(红线、清单、优先级),把规则塞进模型的工作流而不是许愿(两阶段闸门、退回重写),再把最难的部分并行分包(子代理 + 主代理校验)。这三招没有一招依赖特定模型能力,换个领域照用。它们作为通用质控体系的完整形态,收拢在LLM 输出质量控制的三层防线一篇。

几十份业务需求散文档产出后,下一步是把它们组织成资产库:文档资产组织三部曲:归类、合并与索引

Comments