AI 写的功能能跑,为什么还是会返工?它漏掉的往往是这 7 类边界

前两篇解决的是“先读代码”和“怎样判断 Codex 已经读懂项目”。

项目读清楚之后,下一步是不是就可以按照需求直接实现?

还差一件事:把需求边界找出来。

前端需求里最容易被忽略的内容,通常不是页面上有没有按钮,而是条件发生变化以后,页面到底应该怎么表现。

“增加一个用户编辑功能”,正常路径很清楚:

点击编辑 → 打开弹窗 → 修改字段 → 保存成功 → 刷新列表

Codex 完全可以很快把这条路径写出来。

可真正导致返工的问题,往往藏在主路径旁边:

  • 连续点击保存会不会发两次请求?

  • 保存失败后,用户刚填的内容是否保留?

  • 刷新列表时,搜索条件和当前页要不要保留?

  • 记录已经被别人删除时,弹窗怎么处理?

  • 用户权限在页面打开后发生变化怎么办?

  • 关闭弹窗再打开,校验信息是否残留?

  • 姓名为空字符串、全是空格或超长时是否算有效?

这些内容不会因为我们写了“请完善异常处理”就自动得到正确答案。

AI 可以补一个技术上常见的处理方式,但哪个结果符合当前业务,仍然要由人确定。

什么叫需求边界

我现在把需求边界理解为:

当输入、状态、时间、权限、作用范围或运行环境发生变化时,系统行为需要重新作出决定的位置。

正常路径描述“事情顺利时怎么走”,边界描述“条件不再理想时怎么选”。

例如“点击保存后调用接口”是主流程;下面这些都是边界:

  • 表单未通过校验时是否调用。

  • 已经处于提交中时是否允许再次调用。

  • 接口返回业务失败时弹窗是否关闭。

  • 请求还没结束,用户先关闭弹窗时怎样处理。

  • 保存成功,但列表刷新失败时给用户什么结果。

边界之所以容易漏,不是因为开发者不知道它们存在,而是需求通常以功能名组织,代码却以状态变化运行。

产品写“新增编辑功能”,设计稿展示弹窗静态页面,接口文档描述请求和响应。真正把它们连起来的状态转换,往往落到前端开发阶段才暴露。

Codex 如果只按功能名生成实现,也会优先补齐最显眼的正常路径。

第一类:输入和值的边界

前端最常见的误判之一,是把所有“没有值”当成同一种情况。

实际接口和表单里,下面这些值含义可能完全不同:

  • 字段不存在。

  • 值为 null。

  • 值为 undefined。

  • 空字符串。

  • 只有空格的字符串。

  • 数字 0。

  • 布尔值 false。

  • 空数组。

如果 AI 使用一个简单的真假判断:

const displayValue = value || '--'

数字 0 和 false 也会被当成空值。

代码没有语法错误,页面甚至看起来更整齐,但业务含义已经改变。

输入边界需要提前确认:

  • 哪些值算未填写。

  • 是否需要去掉首尾空格。

  • 最小值、最大值和长度限制。

  • 日期范围能否只选一端。

  • 多选为空时发送空数组、null,还是不发送字段。

  • 枚举遇到未知值时怎样展示。

  • 后端缺失字段时是容错还是报错。

我不会把“处理空值”当成完整要求。它至少要落到一个明确映射:

输入 预期行为
null 或 undefined 显示占位符
空字符串 根据字段规则决定占位或保留
0 正常显示 0
false 显示对应业务文案
未知枚举 显示原值、未知状态或告警,需业务决定

AI 擅长写判断,人的责任是先定义什么值得判断。

第二类:操作顺序和状态转换边界

很多前端问题只有连续操作才会出现。

单独测试查询、翻页和重置,它们都可能正常;连起来操作,状态就可能错位:

  1. 设置筛选条件并查询。

  2. 翻到第三页。

  3. 修改筛选条件。

  4. 再次查询。

  5. 点击重置。

这里至少有三个需要决定的边界:

  • 修改条件后查询是否回到第一页。

  • 重置是否立即请求。

  • 重置后的页码和每页数量恢复到什么状态。

同样,新增和编辑共用弹窗时,也不能只检查“分别能打开”:

  1. 打开编辑并触发表单校验。

  2. 不保存,直接关闭。

  3. 再打开新增。

  4. 检查字段、校验信息和临时状态。

前一个操作会不会污染后一个操作,这就是状态转换边界。

我会要求 Codex 按“动作前状态 → 用户动作 → 动作后状态”来描述,而不是只列方法名。

例如:

动作前:pageNum = 3,存在筛选条件
动作:点击重置
动作后:筛选恢复默认值,pageNum = 1,重新请求列表
保持不变:pageSize

只有把“变什么”和“不变什么”同时写出,状态边界才算清楚。

第三类:异步和时间边界

前端页面不是按代码行一次执行完的,用户操作、网络响应和组件生命周期会交错发生。

常见的时间边界包括:

  • 用户连续点击按钮。

  • 前一次请求比后一次更晚返回。

  • 请求期间切换路由。

  • 弹窗关闭后详情请求才返回。

  • 上传或导出耗时较长。

  • 页面重复进入触发多次初始化。

假设用户先查询“已启用”,马上又查询“已禁用”。第二个请求先返回,页面正确显示已禁用数据;随后第一个请求返回,又把列表覆盖成已启用。

每一次请求都成功了,但最终页面是错的。

这类问题不能只写“增加 Loading”。Loading 解决的是反馈和部分重复操作,不一定解决响应乱序。

异步边界要确认:

  • 哪些操作在请求期间必须禁用。

  • 哪些请求允许并行。

  • 是否需要取消旧请求或忽略过期结果。

  • 组件卸载后是否继续更新状态。

  • 成功、失败、取消三种结果怎样恢复 UI。

  • Loading 属于整个页面、局部区域还是单个按钮。

AI 可以根据现有项目方式选择实现,但必须先知道要保护哪一种业务结果。

第四类:成功一半和失败恢复边界

前端任务经常不是只有“成功”和“失败”两个状态。

例如编辑保存包含两个动作:

  1. 保存表单。

  2. 刷新列表。

可能出现:

  • 保存成功,刷新也成功。

  • 保存失败,列表不刷新。

  • 保存成功,但列表刷新失败。

  • 服务端已成功,前端因超时认为失败。

第三和第四种情况最容易让用户困惑。

如果保存成功后直接关闭弹窗,但刷新失败,用户可能看不到新数据;如果提示“保存失败”,用户再次点击,又可能重复提交。

所以我会把组合流程拆开确认:

阶段 失败时需要决定
表单校验 是否保留输入,焦点放在哪里
保存请求 弹窗是否保留,按钮何时恢复
保存成功后的刷新 是否提示已保存但刷新失败,能否手动重试
后续跳转或关闭 未完成动作是否会丢失

“接口失败时提示错误”远远不够。真正的边界是失败发生在哪一步,以及已经成功的部分能不能撤销。

第五类:权限和数据变化边界

前端看到的数据不是静止的。

页面打开后,记录可能被其他人删除、状态可能被修改,当前用户的权限也可能变化。

常见边界包括:

  • 列表里能看到记录,打开编辑时记录已不存在。

  • 打开弹窗时有权限,提交时权限已被收回。

  • 当前页最后一条记录被删除。

  • 服务端返回的状态不再允许当前操作。

  • 按钮被隐藏,但用户仍可通过旧链接进入页面。

前端权限不能替代后端鉴权,但前端仍要定义展示和失败后的行为。

例如删除当前页最后一条数据后:

  • 保留当前页,显示空列表。

  • 回到上一页。

  • 回到第一页。

三种实现都可能合理,取决于产品体验和项目既有规则。

AI 不能从“增加删除功能”这句话中知道答案。

我会提前确认:

  • 权限影响的是可见、可点、可提交还是可访问。

  • 服务端拒绝后页面状态怎样恢复。

  • 数据已变化时提示什么,是否自动刷新。

  • 删除、禁用、审核等操作后页码如何处理。

  • 前端展示权限与后端真实权限的责任边界。

第六类:生命周期和返回路径边界

页面打开时状态正确,不代表再次进入仍然正确。

前端状态会跨越:

  • 弹窗打开与关闭。

  • 组件挂载与卸载。

  • 路由进入与离开。

  • KeepAlive 激活与停用。

  • 标签页切换。

  • 从详情页返回列表。

需要决定:

  • 搜索条件是否保留。

  • 页码是否保留。

  • 临时表单是否清空。

  • 校验信息是否清理。

  • 未完成请求是否取消。

  • 返回列表是否自动刷新。

这些边界尤其依赖项目现有导航和缓存机制。

如果 Codex 只读取目标组件,不查路由、缓存或父级容器,就很容易把初始化逻辑放在错误的生命周期中。

所以涉及“返回”“再次打开”“切换页面”时,我会把它们当成独立路径写进需求,而不是默认框架会处理好。

第七类:作用范围和兼容边界

最后一类边界不一定发生在页面交互里,而是发生在代码影响范围中。

例如:

  • 修改公共组件默认值。

  • 改变工具函数返回类型。

  • 调整接口类型定义。

  • 修改全局样式。

  • 更新依赖版本。

  • 重构共享状态。

当前页面可能因此变得更好,其他调用方却一起改变。

作用范围边界要回答:

  • 本次只修当前页面,还是统一所有同类页面。

  • 公共 API 能否改变。

  • 旧调用方式是否需要兼容。

  • 默认行为改变后谁会受到影响。

  • 本次是否允许顺手清理历史代码。

我最警惕的需求词是“统一处理一下”。

它听起来像小优化,实际上可能把局部需求升级成全局行为变更。没有调用方清单、兼容策略和回归范围时,不应该让 Codex 自行扩展。

我如何在需求里主动找边界

等待 AI 写完再找边界,成本已经偏高。

我会在任务开始前做四轮检查。

第一轮:圈出需求里的动作

找出查询、重置、保存、删除、关闭、返回、切换、导出等动词。

每个动作都问:

  • 触发前是什么状态。

  • 成功后改变什么。

  • 失败后保留什么。

  • 连续执行会怎样。

第二轮:圈出需求里的状态

找出筛选条件、页码、Loading、表单、权限、选中项、缓存等状态。

每个状态都问:

  • 唯一来源在哪里。

  • 谁能修改。

  • 何时初始化。

  • 何时清理。

  • 跨页面是否保留。

第三轮:圈出外部依赖

找出接口、公共组件、全局状态、路由、权限和工具函数。

每个依赖都问:

  • 失败时当前功能怎样退化。

  • 行为变化会影响哪些调用方。

  • 是否存在项目统一封装。

  • 哪些结论需要从代码中确认。

第四轮:按用户路径串起来

不要只检查单个按钮,要组合操作:

  • 查询 → 翻页 → 重置。

  • 编辑 → 校验失败 → 关闭 → 新增。

  • 提交 → 失败 → 修改 → 再提交。

  • 删除当前页最后一条 → 列表刷新。

  • 发起请求 → 切换页面 → 请求返回。

只要路径中出现状态切换,就值得问一次边界。

一份可以交给 Codex 的边界发现清单

## 当前任务边界检查
​
在修改代码前,先根据项目现状和需求检查以下内容。
​
### 输入和值
​
- null、undefined、空字符串、0、false 分别怎样处理?
- 长度、范围、空格、未知枚举怎样处理?
​
### 操作顺序
​
- 查询、翻页、重置连续执行时,哪些状态改变?
- 新增、编辑、关闭再打开时,哪些状态清理?
​
### 异步和时间
​
- 是否可能重复提交、请求乱序或卸载后回写?
- Loading、禁用、取消和恢复分别由谁负责?
​
### 失败恢复
​
- 失败发生在哪一步?
- 用户输入、旧数据、弹窗和按钮状态分别保留什么?
​
### 权限和数据变化
​
- 权限变化、记录不存在、状态已改变时怎样处理?
- 删除后页码和列表怎样恢复?
​
### 生命周期
​
- 再次进入、返回、缓存激活时哪些状态保留或刷新?
​
### 作用范围
​
- 公共组件、类型、工具或样式有哪些调用方?
- 哪些行为必须向后兼容?
​
### 输出要求
​
- 将结论分为:已确认、需要从代码验证、需要业务决定。
- 没有依据的边界不要自行补成确定规则。
- 关键边界未确认时,先报告,不要开始扩大修改。

边界不是越多越好

把所有理论上的异常都列进任务,同样会让需求失去重点。

我会根据三个维度排序:

  1. 发生概率:真实用户是否容易触发。

  2. 影响程度:触发后会不会丢数据、误操作或破坏其他页面。

  3. 修复成本:现在确认和交付后返工,成本差多少。

高概率、高影响或高返工成本的边界,必须在实现前确定。

低概率、低影响且当前没有事实依据的情况,可以记录成未覆盖项,不需要为了显得完整而虚构处理方案。

工程判断不在于列出最多的边界,而在于先兜住最危险的边界。

人要负责决定边界,AI 可以负责把边界变成检查

Codex 很适合帮我做三件事:

  • 从代码中找出状态、调用方和现有异常处理。

  • 根据已确认边界补充实现和测试。

  • 把用户路径转成检查步骤并执行可运行的验证。

但它不能替我决定业务上唯一正确的结果。

“失败后是否保留旧数据”“删除后回到上一页还是第一页”“权限不足时隐藏还是禁用”,这些都可能有多个合理答案。

人的工作不是把每行代码写出来,而是在关键分岔点给出可承担的决定。

下一篇我会继续把边界落成验收标准。重点不是再写一句“功能正常”,而是用行为、数据、范围、自动检查和页面证据,提前定义 Codex 到底要拿什么证明任务完成。

本系列持续更新。Day 5 的第二篇会把“哪些地方容易漏”转成一份可直接使用的前端验收模板。

参考资料

posted @ 2026-08-02 19:58  孙梦少爷  阅读(3)  评论(0)    收藏  举报