免费主机跑代码前 五个AI反模式

AI 代码评审的隐患不在模型太软,而在把聊天里的"同意"当成了发布门禁。
文章指出,AI 模型在代码审查中倾向于点头附和,真正的问题不在模型,而在工作流把"同意"当成了评审。作者归纳出五个反模式:点头式评审、Schema 轮盘赌、密钥渗漏、主机角色漂移和剧场测试,它们都发生在代码上传免费主机之前——发现成本最低却被速度掩盖的阶段。核心主张是:契约文件才是唯一门禁,聊天窗口里的绿色对勾不算。契约是一组进版本控制的文件,包含 schema、拒绝用例和主机角色三要素,配合一个只用标准库的本地检查器,非零退出即禁止上传。作者还强调,失败用例必须由人类编写,免费主机应标注为 scratch 并禁止存储用户数据。

开发者社区最近在讨论一个话题:AI模型在代码审查中太容易点头附和。有人怪模型太软,但真正的锅在工作流——把"同意"当成了评审。
我把这类问题归结为 五个反模式,它们都发生在一个关键时刻:代码还没上传到免费主机之前。发现问题成本最低的阶段,恰恰是被速度掩盖的阶段。
核心主张只有一句话:契约文件才是唯一的门禁,聊天窗口里的绿色对勾不是。
为什么"点头式评审"危险
我不信任一个只会点头的聊天模型。我信任一个失败时会报错的契约文件。
免费草稿很便宜,而廉价的同意很贵。一个临时主机能展示页面,但不等于对你的数据负责。模型用更漂亮的措辞复述你的方案,你会感觉被评审了,实际什么都没被证伪。
问题不在模型本身。原因是你向模型要的是信心,而不是反例。偏向说 yes 的模型会欣然上钩,而你的截止日期会误把礼貌语气当成证据。
替换做法:要求模型给出三个应该让代码失败的输入,在它夸奖你之前先把这些输入存好。
反模式一:点头式评审
症状:模型把你的方案用更干净的话复述一遍。你感觉被评审了,但没有任何东西被证伪。
根因:你向模型要的是信心,不是反例。截止日期让你把礼貌当证据。
替换:要求三个应该让代码失败的输入,在任何赞美之前保存它们。你会接受一个人只说"不错"的评审意见吗?
反模式二:Schema 轮盘赌
症状:字段名在相似运行之间漂移。一次叫 userId,下次变成裸 id。客户端只能解析昨晚最后到达的那个形状。
根因:Prompt 描述的是感觉,不是锁定的 schema。重试带来"进展感",漂移因此被掩盖。
替换:固定必需键,拒绝未知字段。本地检查在必需键缺失时失败。不要在聊天里重写客户端来修复漂移。
什么是响应 Schema:在 API 或数据交换语境里,schema 是一份显式声明——它规定一个响应对象必须包含哪些键、每个键是什么类型、是否允许出现额外字段。它和 prompt 里的自然语言描述是两回事:prompt 说"返回用户信息"只是意图,schema 用 {"required": ["userId", "email"], "additionalProperties": false} 这类约束把意图锁死。没有 schema,模型每次采样就可能给出不同形状,userId 和 id 都被当成合法答案。文章主张的"拒绝未知字段"正是 schema 校验里的严格模式(strict mode):只要出现未声明的键就判失败,而不是静默忽略。这样漂移会在本地第一时间暴露,而不是流到客户端。
反模式三:密钥渗漏
症状:token、cookie 或客户数据行出现在 prompt 里。同一段文本之后可能落进主机日志。你能把密钥从历史记录里拉回来吗?
根因:因为免费,临时路径感觉像私密的。但免费访问仍然可能保留 prompt 或日志。免费服务器是便利,不是保险库。
替换:写 prompt 之前先脱敏。真实凭证放本地 env 文件。检测到疑似密钥模式就拒绝推送。
为什么免费路径也会留下日志:很多人以为临时主机"用完即弃",所以把 token 贴进 prompt 没关系。事实上,任何 HTTP 层都可能被记录——反向代理、CDN、托管平台的调试日志,都可能把请求体原样写盘。一旦 API key、cookie 或客户数据行随 prompt 进入这些日志,你就失去了对它的控制,也无法保证它会被按期清除。文章要求"写 prompt 之前先脱敏",并把真实凭证留在本地 env 文件里,本质是把机密和请求流量在物理上分开。再配合对 scan_paths 做正则扫描,可以在提交前拦住疑似密钥,而不是等它在公开日志里出现。
反模式四:主机角色漂移
症状:免费服务器起初是短期 demo,一周后持有唯一一份状态。没有任何 commit 记录这次悄然角色变化。
根因:在线页面让你感觉真的拥有数据。但免费选项随时可能暂停、重置或消失。如果那个主机今晚忘了你的数据,什么会崩?
替换:把主机角色写下来,标注 scratch 或 durable。当 scratch 会存储用户数据时阻止部署。角色升级必须经过人类 commit。
scratch 与 system-of-record 的区别:scratch 主机是临时草稿环境,可以随时被重置、暂停或删除,里面的数据不作数;system-of-record(记录系统)则是权威数据源,丢失它会引发真实事故。角色漂移的危险在于,一个从 scratch 起步的免费主机,会随着时间悄无声息地变成唯一状态持有者,而没有任何一次 commit 记录这次升级。文章的规则是:把这台主机是 scratch 还是 durable 明确写进契约文件,当 scratch 被要求存储用户数据时直接阻止部署,任何角色升级都必须经过一次人类 commit 留痕。这样"这台机器被允许记住什么"就从默认行为变成了显式决策。
反模式五:剧场测试
症状:测试断言模型刚写的一个 happy string,在生成它们的同一棵树上通过。空输入、超大输入、类型错误的输入在哪?
根因:模型给自己刚讲的故事打分。你要速度,对抗性用例就溜走了。契约从未列出那些用例。
替换:实现之前,你来写失败用例。模型仍可起草处理代码,但不能是测试的唯一作者。
替换工作流
即使聊天感觉已经结束,也按这个顺序干活:
- 写 schema 文件,列出必需键
- 写 cases 文件,包含你发明的拒绝输入
- 写 host 文件,标注角色和扫描路径
- 运行本地检查器,修掉每一条报错
- 这时才让模型起草 handler
- 上传之前再次运行检查器
- 免费服务器只在角色为 scratch 时使用
契约本身是一小组放在 git 里的文件。它列出必需键、拒绝用例、禁止扫描项,也说明主机是 scratch 还是 durable。这是提案而非个人基准,样本未在真实主机上执行过——在你用玩具仓库试之前,把它们标记为未运行。
本地检查器提案
下面这个脚本只用 Python 标准库,不调用任何网络 API。把它放在仓库的 tools/check_contract.py:
#!/usr/bin/env python3
'''Local contract check. Proposal only, not a vendor SDK.'''
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SECRET = re.compile(
r'(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*\S+'
)
def load(name):
path = ROOT / 'contract' / name
return json.loads(path.read_text())
def main():
schema = load('schema.json')
cases = load('cases.json')
host = load('host.json')
errors = []
required = schema.get('required', [])
if not required:
errors.append('schema has no required keys')
for case in cases.get('cases', []):
if 'input' not in case or 'expect' not in case:
errors.append('case missing input or expect')
role = host.get('role')
if role not in {'scratch', 'system-of-record'}:
errors.append('host role is missing or unknown')
if role == 'scratch' and host.get('stores_user_data'):
errors.append('scratch host must not store user data')
for rel in host.get('scan_paths', []):
path = ROOT / rel
if not path.is_file():
errors.append('scan path missing: %s' % rel)
continue
text = path.read_text(errors='ignore')
if SECRET.search(text):
errors.append('secret-like text in %s' % rel)
if errors:
print('\n'.join(errors))
return 1
print('contract ok')
return 0
if __name__ == '__main__':
sys.exit(main())
从仓库根目录运行,非零退出意味着先别上传。零退出只说明这批文件内部自洽。
这份检查器做了什么、没做什么:脚本只读取 contract/ 下的三个 JSON 文件,做纯本地的一致性校验——schema 是否声明了必需键、每个用例是否同时有 input 和 expect、主机角色是否为已知值、scratch 主机是否被标记为存储用户数据、扫描路径是否存在且不含疑似密钥。它不会调用任何模型,也不会证明 handler 真的匹配这些用例,那需要真正的测试运行器。密钥正则也会漏掉编码后的 blob 或拆分字符串,还可能误报玩具示例。它的价值在于把"发布门禁"从聊天窗口移到一个零退出的命令行:非零退出意味着先别上传,零退出只代表这批文件内部自洽。
决策表
| 症状 | 根因 | 替换 |
|---|---|---|
| 聊天只夸方案 | Prompt 在要同意 | 要求三个破坏性输入 |
| 键名在运行间改名 | 没有固定响应 schema | 拒绝缺失或多余键 |
| 密钥文本进入日志 | 临时路径感觉私密 | 扫描文件并脱敏 prompt |
| Demo 主机持有真实状态 | 角色从未被记录 | 标注主机并阻止 scratch 写入 |
| 测试只欢呼一个 happy string | 模型出了自己的考卷 | 你来掌握失败用例 |
找到和你最近一次真实事故匹配的那一行。如果匹配两行,先修密钥那行——泄露的 token 比混乱的 schema 跑得更快。
局限性
检查器不会自己调用模型,也无法证明 handler 匹配 cases。那需要真正的测试运行器。密钥正则遗漏编码 blob 和拆分字符串,也可能误报玩具示例——把 scan_paths 指向源文件,而不是这篇稿子。短句会磨掉真实边缘情况,表格和脚本才承载精确部分。
什么时候该跳过
数据受监管或客户机密时跳过。需要本周审计签字时跳过。没人愿意读 diff 时跳过。如果免费服务器必须是你的唯一数据库——那是主机角色反模式换了件外套。如果契约不会进版本控制——带过期文件的检查器只是另一个点头的评审者。
值得大声问出口的问题:什么输入会让这个函数对你撒谎?如果厂商记录日志,prompt 去哪?这台主机被允许记住哪怕一个用户吗?
下一步
挑一个你已经很懂的小 handler。在打开会话之前写三个拒绝用例。运行检查器,再决定临时主机是否有帮助。契约文件在仓库里失败,比密钥在公开日志里失败便宜得多。
相关推荐

零成本搭建AI超级员工系统:智能体自动工作流全解析
深度拆解B站演示的AI超级员工系统:Agent智能体编排、数字人口播、地图与企业获客、24小时智能客服及法务模块全覆盖。分析零成本开户背后的商业逻辑与合规风险,帮助创业者判断这套AI数字员工系统的真实投产比。

Chatbot与AI Agent的本质区别:从对话到执行的跨越
Chatbot和AI Agent有什么区别?本文基于Learn Tech频道的MCP系列讲解,用订机票、笔记摘要等实例说明:Chatbot只给答案,Agent通过思考-行动-观察循环真正完成任务,并拆解模型、工具、记忆、目标四大部件及MCP的桥梁作用,同时讨论Agent的适用边界与权限风险。

免费GitHub仓库:342小时AI课程,从数学到MCP服务器
一个免费GitHub仓库提供342小时AI课程,包含523节课、20个阶段,覆盖数学基础到Agent构建和MCP服务器。支持目标驱动学习,每节课产出可留存的prompt、skill或agent,已有近10万人开始学习。