摘要:本文总结了 AI 编码工具在缺乏明确工程约束时容易出现的五种"自作聪明"倾向:1) 中文优先原则执行不足;2) 过度兼容历史或假想输入格式;3) 增加无意义的备用兜底逻辑;4) 不必要的可配置化;5) 调试参数混入正式接口。核心观点是:AI 的默认优化方向(更通用、更稳妥、更有弹性)往往与真实业务项目需求(契约清晰、边界明确、行为可预测)不一致,因此必须通过明确的工程约束来界定边界,才能生成真正可用的代码。

这段时间,像 Codex 这样的 AI 编码工具,配合能力足够强的大模型,已经可以生成相当不错的代码了。
只要把一个程序系统定义清楚,比如它是给谁用的、要解决什么问题、输入输出是什么、功能边界在哪里、有哪些规则和约束,很多实现工作确实可以通过自然语言直接交给 AI 完成。
而且,AI 生成出来的代码,往往第一眼看上去还挺“漂亮”:结构完整、命名规范、排版工整,文档风格也显得很职业。
但最近做了一个规模不算大、却也不算简单的项目后,我明显感受到一个问题:如果提示词里的工程约束不够明确,模型很容易在实现时表现出一种“自作聪明”的倾向。
这种“聪明”通常体现为:它会主动把代码写得更“通用”、更“兼容”、更“健壮”、更“灵活”。表面看似稳妥,甚至有点像“经验丰富的工程师提前帮你考虑周全了”;但落到真实项目里,这些额外设计往往并不是你真正需要的,反而会带来更多复杂度,模糊边界,甚至埋下新的 Bug。
所以问题不一定在于 AI 不会写代码,而更可能在于:我们给它的约束还不够清楚。
这篇文章就记录一下这次实践中遇到的几个典型问题,以及我目前总结的一些应对方式。未必完全正确,先记下来,供以后回顾参考。
1. 中文优先原则执行不足
一个很常见但容易被忽视的问题是:即使我们全程用中文描述需求,模型仍然会默认往英文风格上靠。
比如:
- 日志信息写成英文
- 错误提示写成英文
- 页面提示语、报告内容、诊断信息也写成英文
- 项目里中英混杂,用户可见文本风格不统一
甚至在有次修改功能时,发现把与当次修改无关的中文提示文字也都一并改成了英文(无语)。
这背后的原因不难理解。大模型在生成代码时,很容易套用它训练中最常见的“工程模板”,而这些模板大多是英文环境下形成的。所以如果你不特别强调,它就会默认英文更“标准”。
但在很多实际项目里,尤其是内部系统、面向中文用户的工具、运维脚本、报告类程序,用户可见文本是否统一,直接影响可读性和使用体验。这不是“无关紧要的细节”,而是工程要求的一部分。
约束建议
如果项目要求中文优先,最好明确写清楚类似这样的规则:
除命令行参数、JSON key、状态枚举、文件名等机器契约外,日志、错误提示、报告内容、页面提示等用户可见文本,必须中文优先。
这样做的好处是把“哪些必须保持机器可读,哪些应该面向人类阅读”明确区分开,避免模型自行发挥。
2. 过度兼容历史或假想输入格式
第二类问题,是 AI 特别喜欢主动兼容旧格式,或者兼容一些根本没被要求支持的输入形式。
举个典型例子:某个字段已经明确从“字符串”改成了“数组”,但模型生成的实现里,仍然会保留两套解析逻辑,既支持字符串,也支持数组。它通常会觉得这样更保险,避免旧调用方式失效。
问题在于,如果你的需求本来就是“现在开始只支持新格式”,那么这类兼容代码其实是在擅自改变系统边界。
它带来的问题包括:
- 代码分支变多,逻辑变复杂
- 当前输入规则变得不清晰
- 调用方误以为旧格式仍然是正式支持的
- 后续排查问题时,输入契约变得模糊
- 看似“兼容”,实则给未来留下清理成本
更麻烦的是,模型兼容的还不一定真的是“历史格式”,有时只是它自己猜测“也许有人会这么传”。
约束建议
如果项目的目标就是收紧契约,可以明确告诉模型:
除非明确要求迁移兼容,否则只支持当前约定格式;遇到旧格式或未定义格式,直接报错,或等待人工确认。
这类约束的核心,是让模型不要擅自替你做“兼容性决策”。
3. 增加了没意义的备用兜底逻辑
这是我这次感受最明显的一类“自作聪明”。
模型很喜欢写各种“备用方案”和“兜底逻辑”,例如:
- 固定路径找不到,就再从系统
PATH里找 - 指定配置不存在,就尝试自动推导
- 某个工具不存在,就跳过这一步或自动降级
- 服务地址缺失,就从其他字段里猜一个
- 主流程失败后,再静默切换到另一个分支
从编码习惯上看,这些写法似乎显得很“健壮”。一个经验丰富的工程师,确实也会考虑异常场景和失败恢复。
但问题是,并不是所有系统都需要这种层层兜底的设计。
很多时候,一个系统的规则本来就很明确:
- 路径必须存在
- 配置必须完整
- 工具必须安装
- 输入必须合法
- 环境必须满足前置条件
如果这些条件不满足,最合理的处理方式其实不是“继续猜”“继续试”“继续降级”,而是立即报出明确错误。因为这样才能让问题暴露在正确的位置,而不是被“看起来很贴心”的兜底逻辑掩盖掉。
有些兜底分支甚至可能永远不会真正被走到,却依然增加了维护成本和理解成本。
约束建议
可以提前把原则说死:
不满足规则或约定时,直接抛出明确错误;不要自动猜测、降级、补全或兜底,除非需求中明确要求。
这类约束对很多业务系统尤其重要。因为在这类系统里,明确失败往往比模糊成功更有价值。
4. 不必要的可配置化
AI 还有一个很典型的倾向:喜欢把固定规则抽象成参数、配置项,或者“留个扩展口”。
比如:
- 固定路径被设计成可传入参数
- 固定名称被做成配置项
- 固定流程顺序被改成可调整
- 本来只有一种合法策略,却被扩成“支持多种模式”
从“代码设计”的表面上看,这似乎很灵活,也很有扩展性。但问题是:很多业务规则本来就是固定的,不应该被暗示成“可以调整”。
一旦你把它做成参数,调用方就会自然认为:
- 这个值可以改
- 这个顺序可以调
- 这个规则不是硬约束
- 后续场景可能应该支持更多变体
于是,一个原本简单清晰的系统,开始在接口层面释放出错误信号。
对于真实项目来说,很多所谓“可扩展性”其实是伪需求。没场景、没边界、没验证依据的可配置化,只会让实现和使用都变复杂。
约束建议
可以明确给出类似要求:
如果没有明确业务场景,不要把固定规则设计成可配置项;固定值、固定路径、固定流程按需求直接实现,如需抽象,先等待人工确认。
这能有效压制模型“先替你设计未来”的冲动。
5. 调试参数混入正式接口
最后一种也很常见:模型会把调试需求直接做进正式功能里。
比如为了方便测试,它会在正式接口中加入一些可选入参:
- 跳过某一步
- 强制走某个分支
- 注入测试数据
- 指定某个调试路径
- 控制某个只在验证阶段使用的行为
短期看,这确实方便本地验证;但从正式接口设计来看,这种做法很容易污染契约。
因为这些参数一旦进入正式代码:
- 就要长期维护
- 会影响接口语义
- 容易被误用到生产场景
- 后续清理时又会牵涉兼容问题
很多调试能力,应该存在于测试代码、脚本、夹具、专门的验证入口中,而不是混在面向正式调用方的接口里。
约束建议
这一点最好提前说清楚:
明确区分生产代码与测试代码,禁止在正式接口中暴露调试参数;调试能力应通过测试入口、开发脚本或专用验证工具实现。
这条约束看起来细,但对长期维护很重要。
为什么 AI 会这样“自作聪明”?
回头看,这些问题背后其实有一个共同点:模型在缺少明确边界时,会默认朝“更通用、更稳妥、更有弹性”的方向补全。
因为从统计意义上讲,这样写出来的代码更像“标准答案”:
- 更像通用库
- 更像公共组件
- 更像要服务很多未知场景的框架代码
- 更像“经验丰富的人会多考虑一点”的实现
但真实业务项目往往不是这样。
真实项目更需要的是:
- 契约清晰
- 边界明确
- 行为可预测
- 失败可定位
- 规则不含糊
- 不替未来做没有依据的设计
换句话说,AI 的默认优化方向,不一定等于你的项目优化方向。
这时候,提示词里的工程约束就不是“补充说明”,而是决定结果质量的核心输入。
我的一个阶段性结论
到目前为止,我越来越倾向于把 AI 写代码这件事理解成:
不是“把需求告诉它”就够了,而是要把工程边界也明确告诉它。
尤其是下面这些内容,最好尽早说清楚:
- 用户可见文本用什么语言
- 哪些字段是机器契约,不能乱改
- 是否允许兼容旧格式
- 是否允许兜底和降级
- 是否允许做可配置化抽象
- 是否允许把调试能力带进正式接口
- 遇到不确定场景时,是直接报错,还是停下来等人工确认
这些约束写得越明确,生成结果就越接近真实项目需要;反过来,如果边界模糊,模型就很可能按照它理解的“通用最佳实践”自行发挥。
而很多时候,问题恰恰就出在这种“发挥”上。
小结
AI 写代码的能力已经足够强,很多场景下甚至可以显著提高实现效率。但它的价值,并不只是“帮你把代码写出来”,更在于它是否能按你真正需要的方式把代码写出来。
这件事的关键,不只是需求描述是否完整,更是工程约束是否明确。经过这次的实践,最主要的一个体会就是:
约束越清楚,AI 越不容易“自作聪明”;边界越明确,生成结果越接近真实可用的工程实现。
至少现阶段,我觉得这比单纯追求“提示词写得更长”更重要。毕竟大模型现在确实已经很聪明了,需要的其实只是更明确的界定。


842

被折叠的 条评论
为什么被折叠?



