AI 生成的代码怎么 review
5 个必查项,每个都有我用真金白银买来的教训
Review 心态
Review AI 代码和 review 人的代码不一样。人写的代码,你可以假设作者理解自己写的东西;AI 写的代码,你不能假设它理解任何东西。它只是在统计意义上生成了"看起来对"的代码。
所以 review AI 代码的核心原则:不要读代码"看起来对不对",要证明它"在各种情况下都对不对"。
必查项一:边界条件
AI 最容易漏的就是边界条件。happy path 几乎不会错,但空输入、超长输入、并发冲突、网络中断——这些 AI 不会主动考虑。
我的漏检案例:FinBuddy 的 K 线数据查询,AI 写的代码在股票代码不存在时返回空列表,但上层调用方假设返回值一定非空,直接取了 result[0]——IndexError。这个 bug 在正常使用时不会触发,只有查询一只退市的股票才会崩。
# AI 写的版本
kline = await db.fetch_daily_kline(stock_code)
return kline[0].close # stock_code 不存在时 IndexError
# 修正
kline = await db.fetch_daily_kline(stock_code)
if not kline:
raise StockNotFoundError(stock_code)
return kline[0].close
Review 时问自己:如果输入为空 / 超出预期范围 / 并发访问,这段代码会怎样?
必查项二:错误处理
AI 生成的代码经常有"看起来有错误处理,实际上没处理"的情况。最典型的:catch 了异常但什么都没做,或者 catch 了太宽泛的异常类型。
漏检案例:FinBuddy_Web 的 LLM 代理服务,AI 写了 try/except Exception 包住整个请求处理,异常时返回空响应。看起来有错误处理,但实际上所有异常都被吞了——包括 InsufficientBalanceError(余额不足应该返回 402)和 RateLimitError(限流应该返回 429)。客户端收到空响应,完全不知道发生了什么。
# AI 写的版本
try:
result = await process_request(req)
return result
except Exception:
return Response(status=204) # 所有异常都吞了
# 修正:区分异常类型
try:
result = await process_request(req)
return result
except InsufficientBalanceError:
return JSONResponse(status_code=402, content={"error": "余额不足"})
except RateLimitError:
return JSONResponse(status_code=429, content={"error": "请求过于频繁"})
except LLMServiceError as e:
logger.error(f"LLM 服务异常: {e}")
return JSONResponse(status_code=503, content={"error": "服务暂时不可用"})
Review 时问自己:每个 except 分支是否处理了正确的异常类型?异常信息是否传递给了调用方?
必查项三:性能
AI 不关心性能。它生成的代码在功能上可能是对的,但在性能上经常有问题:N+1 查询、不必要的对象创建、同步操作阻塞异步循环。
漏检案例:FinBuddy 批量获取股票数据,AI 写的版本在循环里逐条查数据库:
# AI 写的版本:N+1 查询
for stock_code in watchlist:
kline = await db.fetch_kline(stock_code) # 每次一个查询
results.append(kline)
# 修正:批量查询
results = await db.fetch_kline_batch(watchlist) # 一次查询
50 只股票,AI 版本发 50 次数据库查询,耗时 2.3 秒;批量查询版本 1 次搞定,耗时 0.05 秒。差了 46 倍。
Review 时问自己:有没有循环内的数据库查询?有没有不必要的对象创建?同步操作是否阻塞了异步循环?
必查项四:安全
AI 生成的代码经常忽略安全问题:SQL 注入、敏感信息泄露、权限检查缺失。不是 AI 不知道这些概念,而是它不会主动在生成的代码中加入防护。
漏检案例:FinBuddy_Web 的用户信息查询接口,AI 写的版本直接用用户传入的 user_id 查数据库,没有验证当前登录用户是否有权查看该 user_id 的信息。任何登录用户可以查看任意用户的数据。
# AI 写的版本:缺少权限检查
@router.get("/users/{user_id}")
async def get_user(user_id: int):
return await db.get_user(user_id) # 任何登录用户都能查
# 修正:验证权限
@router.get("/users/{user_id}")
async def get_user(user_id: int, current_user: User = Depends(get_current_user)):
if current_user.id != user_id and not current_user.is_admin:
raise PermissionDeniedError()
return await db.get_user(user_id)
Review 时问自己:有没有用户输入直接拼进查询?有没有权限检查?有没有敏感信息暴露在响应中?
必查项五:架构一致性
AI 不知道你的项目架构约束。它可能生成一段功能正确但违反项目架构的代码——绕过中间件、不按分层调用、引入不存在的依赖。
漏检案例:让 AI 给 FinBuddy 加个新功能——行业分析。AI 直接在路由层写数据库查询,绕过了 DatabaseMiddleware。功能上没问题,但违反了项目的分层架构(Routes → Services → Engines → Middleware → DB)。后来重构时这块代码成了孤岛,差点漏改。
这和 什么时候该让 AI 写 里说的"不提供上下文"是一个问题的两面——你不告诉 AI 架构约束,它就不会遵守。
Review 时问自己:这段代码符合项目的分层架构吗?有没有绕过中间件或服务层直接操作底层?
工具辅助
纯靠人眼 review 容易漏,尤其是大段代码。我配合三个工具:
- 类型检查(mypy / pyright)— 捕获类型不匹配、可选值未检查。AI 经常把 Optional[str] 当 str 用,类型检查能抓出来。
- Lint(ruff / flake8)— 捕获未使用的 import、变量名冲突、过长函数。AI 生成的代码经常有多余的 import。
- 测试— 写测试覆盖边界条件。AI 生成的测试只覆盖 happy path,你补的测试要覆盖空输入、异常路径、并发场景。
工具不能替代人脑,但能帮你抓住人眼容易漏的低级错误。就像 认知偏差 里说的,人的注意力有限,工具是外挂的注意力。
Review 清单一句话版
5 个必查项
边界条件(空输入/超范围/并发)→ 错误处理(异常类型/信息传递)→ 性能(N+1/冗余对象/同步阻塞)→ 安全(注入/权限/信息泄露)→ 架构一致性(分层/中间件/依赖方向)。按这个顺序查,漏检率从 30% 降到 5% 以下。