让 AI 修改代码时,真正危险的往往不是它不会写,而是它一次写得太多:顺手整理目录、统一命名、升级依赖,再补上一层“更合理”的抽象。最终 diff 看起来很完整,验证成本却超过了人工重写。
解决办法不是把提示词写得更长,而是给每轮修改设定一个明确的“变更预算”。预算限制本轮可以触碰的文件、行为和验证范围,使 AI 的产出保持在人工能够快速审查、机器能够及时验证的尺度内。
一、变更预算不是行数限制
只要求“修改不超过 50 行”并不可靠。删除一个判断可能只有一行,却会改变整个调用链;批量格式化几百行,反而可能完全不影响行为。
更实用的预算由四部分组成:
- 文件预算:允许新增或修改哪些文件,哪些目录禁止触碰。
- 行为预算:本轮只改变一个可描述的外部行为。
- 依赖预算:是否允许升级版本、引入库或调整构建配置。
- 验证预算:改动完成后,必须在多长时间内得到明确结果。
例如,Android 中修复一次空指针,可以这样定义边界:只修改 ViewModel 和对应测试;不改接口模型,不升级依赖;空数据返回时展示空状态;运行目标单元测试必须通过。
这比“修复页面偶发崩溃”更容易执行,也更容易判断任务是否真的结束。
二、先让 AI 交付最小闭环
一次有效的小步修改应包含三个部分:触发问题的输入、针对问题的改动、能够证明结果的验证。缺少其中任何一项,改动都只是一个尚未闭合的猜测。
可以把任务描述成下面的形式:
1 | 目标:修复网络返回空列表时页面一直显示加载中的问题。 |
这里最重要的不是措辞,而是每项约束都能被检查。完成后可以直接用 git diff --stat 看范围,用测试结果看行为,而不必依赖 AI 自己声称“已经解决”。
三、Android 项目按层切分
Android 工程容易出现跨层扩散:界面问题一路改到 ViewModel、Repository、网络模型和 Gradle 配置。面对这类任务,可以先按故障所在的最窄层设置预算。
如果状态转换错误,就先限定在状态持有者及其测试;如果布局在特定尺寸下溢出,就先限定布局资源和截图验证;如果构建失败,再把预算放到对应模块的构建脚本。只有现有证据证明问题跨层,才扩大下一轮范围。
目标测试也应尽量贴近改动模块:
1 | ./gradlew :app:testDebugUnitTest \ |
目标测试通过后,再根据风险决定是否运行模块测试或完整构建。这样既保持反馈速度,也不会把“跑了一个大命令”误当成精确验证。
四、Linux 工具修改先固定输入输出
Shell 脚本和系统工具的改动看似局部,却经常依赖环境变量、退出码、权限和文件状态。给 AI 修改前,先把输入输出契约写清楚:
1 | tmp_dir=$(mktemp -d) |
这段验证不一定要直接用于生产,但它明确了测试数据、退出状态和预期输出。AI 若想顺手改变输出格式,差异会立即暴露,而不是等到下游脚本异常时才发现。
对于会写入系统目录、操作设备或调用网络的脚本,还应默认禁止真实副作用,优先使用临时目录、模拟输入或 dry-run。验证过程本身也属于预算的一部分。
五、每轮结束检查预算是否超支
AI 完成修改后,不要立刻进入下一项需求。先做一次短检查:
1 | git status --short |
然后回答四个问题:
- 修改是否只发生在允许的文件中?
- 是否引入了未授权的依赖、配置或公共接口变化?
- 验证命令是否真的执行,结果是否与目标直接相关?
- 如果本轮失败,能否只撤销这一步而不影响其他工作?
发现超支时,最稳妥的处理通常不是继续解释,而是缩小任务重新做一轮。一个需要长篇说明才能证明安全的 diff,本身就已经失去了小步修改的价值。
六、什么时候扩大预算
预算不是越小越好。边界过窄也可能迫使代码加入临时补丁,掩盖真正的问题。扩大预算应由证据触发,而不是由“顺便优化”触发。
常见的扩大理由包括:测试证明错误来自下游契约;局部修改会破坏已有公共行为;构建系统无法只验证单个模块。扩大时仍然一次只增加一个维度,例如先增加一个文件,再增加一组测试,而不是同时开放整个仓库。
七、把 AI 从代码生成器变成受控执行者
变更预算的核心,是把一次模糊的“帮我改好”转换成多轮可观察的工程动作。每一轮都有明确边界、可执行验证和独立回滚点,人的审查压力会显著降低,AI 也更少在缺少证据时自行扩展任务。
高效的人机协作并不追求一次生成最多代码,而是追求每一步都能快速回答:改了什么、为什么有效、失败后如何退回。能持续回答这三个问题,小步修改最终反而更快。
- 本文链接: https://blog.hansong.icu/2026/07/14/ai_change_budget_engineering/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。