[控场AI]
· 10 分钟阅读· 5,397 字

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

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

AI 代码评审的隐患不在模型太软,而在把聊天里的"同意"当成了发布门禁。

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

rss source: Five Anti-Patterns Before a Free Host Sees Code

开发者社区最近在讨论一个话题: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,在生成它们的同一棵树上通过。空输入、超大输入、类型错误的输入在哪?

根因:模型给自己刚讲的故事打分。你要速度,对抗性用例就溜走了。契约从未列出那些用例。

替换:实现之前,你来写失败用例。模型仍可起草处理代码,但不能是测试的唯一作者。

替换工作流

即使聊天感觉已经结束,也按这个顺序干活:

  1. 写 schema 文件,列出必需键
  2. 写 cases 文件,包含你发明的拒绝输入
  3. 写 host 文件,标注角色和扫描路径
  4. 运行本地检查器,修掉每一条报错
  5. 这时才让模型起草 handler
  6. 上传之前再次运行检查器
  7. 免费服务器只在角色为 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编程反模式#免费主机部署#代码审查#契约检查#Secret渗漏#Schema漂移#AI代码评审
分享:

相关推荐