以前做一个新功能,最耗时间的是敲代码。
现在有了AI,生成接口、补全类型、编写测试,甚至搭建项目骨架都只需要几分钟。可真正用AI完成一个项目后,我发现了一个很现实的问题:
代码生成得越快,返工不一定越少。
问题往往不是AI不会写,而是需求、边界和数据结构还没确定,代码就已经生成了一大堆。
一、AI能快速写代码,但不知道你真正要什么
假设要开发一个“根据用户ID查询个人信息”的接口,AI很快就能生成:
app.get('/api/users/:id', async (req, res) => {
const user = await userService.findById(req.params.id)
if (!user) {
return res.status(404).json({
message: 'User not found'
})
}
return res.json(user)
})
单独看,这段代码没有明显问题。但放进真实项目后,还要考虑:
- 用户ID是什么格式;
- 未登录用户能否调用;
- 普通用户能否查看他人资料;
- 手机号、邮箱是否需要脱敏;
- 注销用户应该返回什么;
- 数据库查询失败如何处理;
- 接口是否需要限流。
这些问题没有确定之前,代码写得再快也只是一个“能运行的草稿”。
所以我现在使用AI时,不会一上来就让它生成代码,而是先问:
暂时不要写代码。请先列出这个需求中需要确认的业务规则、异常情况和潜在风险。
这一步看起来慢,实际上能减少大量返工。
二、不要一句话生成整个功能
刚开始使用AI编程时,我经常输入:
使用React、Node.js和MySQL,帮我实现一个完整的用户管理系统。
AI会迅速生成很多文件,看起来效率很高。但真正运行时,往往会发现:
- 前后端字段名称不一致;
- 数据库结构与业务逻辑冲突;
- 不同接口使用了不同的异常格式;
- 依赖版本与当前项目不兼容;
- 修改一个字段,需要调整多个文件。
更稳妥的做法,是把一个功能拆成几个可以单独验证的步骤:
- 确定数据结构;
- 设计接口输入与输出;
- 编写数据库访问层;
- 实现业务逻辑;
- 添加权限与异常处理;
- 补充测试;
- 最后接入页面。
每完成一步就运行一次,确认没有问题再继续。
AI适合加快每个小步骤,但不适合替你跳过设计过程。
三、先确定接口,再生成前后端代码
多人协作时,最常见的问题并不是代码写错,而是双方理解的数据格式不同。
例如,后端返回:
{
"token": "xxx",
"user": {
"id": 1,
"name": "Tom"
}
}
前端却按照下面的结构读取:
{
"data": {
"accessToken": "xxx",
"userInfo": {
"id": 1,
"username": "Tom"
}
}
}
两边的代码单独看都能运行,连接起来却全是问题。
因此,在生成实现代码前,可以先固定接口类型:
interface LoginRequest {
email: string
password: string
}
interface LoginResponse {
accessToken: string
expiresIn: number
user: {
id: string
nickname: string
avatar: string | null
}
}
然后再告诉AI:
前端、后端和测试代码必须遵守这份类型定义,不要自行修改字段名称和嵌套结构。
当输入和输出明确以后,AI生成的代码会稳定很多,后续联调也更省时间。
四、代码能运行,不代表功能可靠
AI生成的代码经常能够覆盖正常流程,却容易忽略异常情况。
例如:
const response = await fetch('/api/user')
const data = await response.json()
setUser(data)
正常请求时没有问题,但它没有考虑:
- 网络中断;
- 请求超时;
- 登录状态失效;
- 服务端返回500;
- 返回内容不是JSON;
- 组件卸载后请求才结束。
可以改成:
async function loadUser(signal: AbortSignal) {
const response = await fetch('/api/user', { signal })
if (response.status === 401) {
throw new Error('LOGIN_EXPIRED')
}
if (!response.ok) {
throw new Error(`REQUEST_FAILED_${response.status}`)
}
return response.json()
}
让AI审查代码时,也不要只问“有没有问题”,这种提问通常只能得到一些宽泛建议。
更有效的方式是:
请从权限控制、异常处理、并发请求、数据安全、资源释放和可测试性六个方面检查这段代码,并说明每个问题会在什么情况下发生。
检查范围越明确,AI越容易发现真正有价值的问题。
五、AI生成的代码,至少要经过三层验证
1. 静态检查
先处理最基础的问题:
- 类型是否正确;
- 变量是否可能为空;
- 依赖是否真实存在;
- API用法是否适用于当前版本;
- 是否存在未处理的Promise;
- 是否符合项目规范。
TypeScript、ESLint和格式化工具可以发现不少低级错误。能交给工具检查的内容,没有必要完全依靠人工阅读。
2. 运行验证
不要因为代码“看起来合理”就直接接受。
至少应该测试:
- 正常输入;
- 空值;
- 错误格式;
- 重复提交;
- 超长内容;
- 请求失败;
- 并发操作;
- 权限不足。
AI更容易生成理想条件下的实现,而真实用户不会永远按照理想流程操作。
3. 业务验证
代码通过测试,也不代表业务一定正确。
例如:
- 优惠券接口可以正常调用,但允许同一用户重复领取;
- 订单取消成功了,却没有恢复库存;
- 邮箱修改成功了,却没有进行身份验证;
- 删除用户成功了,却破坏了历史订单关联。
技术测试确认程序是否按照代码运行,业务验证确认代码做的是不是正确的事情。两者缺一不可。
六、让AI参与审查,不要替你做决定
AI很适合充当第二名代码审查者。
完成一个功能后,可以把需求、接口定义和核心代码一起提供给它,让它检查:
- 是否遗漏异常分支;
- 是否存在重复逻辑;
- 函数职责是否过多;
- 是否存在安全风险;
- 测试是否覆盖关键路径;
- 实现是否偏离原始需求。
但AI给出的修改建议不能全部照搬。
有些建议理论上更加优雅,却会让简单项目变得复杂;有些抽象能够减少几行重复代码,却提高了理解成本;还有些方案适合大型系统,并不适合当前项目。
面对一条修改建议,可以先判断:
- 它解决的是实际问题,还是假设中的问题?
- 修改后是否更容易理解和维护?
- 增加的复杂度是否符合项目规模?
AI可以帮你发现问题,但最终取舍仍然需要开发者完成。
七、比提示词更重要的,是项目上下文
很多人会收藏各种“万能提示词”,但真正影响生成质量的,往往是AI是否了解项目背景。
例如:
- 为什么用户数据采用软删除;
- 为什么订单接口必须保证幂等;
- 为什么缓存时间设置为五分钟;
- 为什么这个组件没有继续拆分;
- 为什么订单编号不使用数据库自增ID。
这些决策如果没有记录,不仅AI不知道,开发者过一段时间也可能忘记。
可以在项目中保留一份简单的决策记录:
## 用户删除策略
采用软删除,不直接移除数据库记录。
原因:
1. 订单与操作日志需要保留用户关联;
2. 误删除后需要支持恢复;
3. 管理后台需要查询历史用户。
注意:
所有普通用户查询默认排除 deleted_at 不为空的数据。
后续让AI修改功能时,把这些记录一并提供,比反复调整提示词更有效。
结语
AI确实让写代码变快了,但开发效率的瓶颈也随之发生了变化。
过去,我们花大量时间查询语法、查找API和编写重复代码;现在,更重要的是明确需求、设计接口、识别边界,并判断生成结果是否可靠。
真正高效的AI编程,不是一次生成几千行代码,而是让每一段生成结果都能被理解、验证和维护。
AI可以快速给出答案,但决定应该问什么、哪些答案可以使用,依然是开发者最重要的能力。