
实用技巧 — 知道这些就能指挥AI
让智能体修改代码时最大的问题是一个文件里塞了20个函数。你只需要1个函数就打开了文件,结果19个不需要的函数跟着一起来了。这会让智能体性能下降30-85%。
解决方法是三句话。Go、TypeScript、Python全部支持。
对智能体说:“找到最长的文件,按函数分离。文件名跟函数名一致。所有现有测试必须通过。”
对智能体说:“运行filefunc validate,把违规数降到0。所有现有测试必须通过。”
对智能体说:“重复tsma next给所有函数添加测试。出现未覆盖分支就添加覆盖那些分支的测试。直到All functions complete。”
不读代码这三句话也够了。判断由工具来做,执行由智能体来做。你只需要做决策。
“文件不会太多吗?"——文件会增加3-14倍。但智能体不打开目录。它搜索。不管500个还是1000个文件,一条搜索命令就完事。
“没有测试的遗留代码呢?"——重复tsma next就行。智能体中途崩溃进度也会保存。新智能体运行tsma next就接着来。在527个函数上验证通过。
快速体验
用Claude Code打开第1课应用:
“这个项目最长的文件是什么?里面有几个函数?”
大多数情况下多个函数挤在一个文件里。现在指挥:
“把那个文件的每个函数分离到单独的文件。文件名跟函数名一致。”
分离完后这样指挥:
“找到待办完成处理函数并解释。”
分离前智能体需要读整个长文件。分离后只打开complete_todo.go就行。亲眼看到智能体的搜索成本降低了。这就是"一个文件一个概念"的效果。
为什么要这样指挥
引言:不要把机器人放进人类的办公室
到第7课为止我们学了防止漂移(Hurl)、分离决策和实现(yongol)、用棘轮强制进度(Ratchet Pattern)、反向利用谄媚偏差(IFEval)的方法。
即使这些全部应用了,还剩一件事。代码本身的结构。
让智能体"修改这个函数"时智能体做什么?找文件、打开文件、读内容、修改。这个过程中智能体的探索单位是文件。
那如果一个文件里有20个函数呢?
你需要1个函数打开了文件,19个不需要的函数跟着来了。这就是上下文污染。
只需要CrossError类型,打开来读
→ 19个不需要的函数跟着来了
→ 上下文污染
不要把机器人放进人类的办公室。要建造机器人能工作的工厂。代码也一样。
SWE读起来好的代码 ≠ 智能体操作起来好的代码
SWE(软件工程师)读代码时通过滚动文件来把握上下文。2000行的文件有经验的话"这部分不要动"的直觉会起作用。
智能体没有这种直觉。
| SWE | AI智能体 | |
|---|---|---|
| 探索方式 | 用眼睛浏览目录树 | 用grep搜索 |
| 打开文件 | 在IDE中滚动 | read file — 全部加载 |
| 上下文判断 | 直觉+经验 | 只知道上下文中有的 |
| 不需要的代码 | 忽略 | 消耗上下文预算 |
| 决定性差异 | 看2000行也只看需要的部分 | 2000行全部处理 |
研究证实了这一点。
研究表明混入不相关信息时AI性能下降30-85%。1
上下文越短越好。 这不是直觉而是实验结果。那就结构性地拆分代码,只放需要的。问题是没有工具。
filefunc填补了这个空白。支持Go、TypeScript、Python项目。第1课用什么语言做的应用都能用。
filefunc — 一个文件一个概念
filefunc的核心原则只有一个。
一个文件一个概念。文件名 = 概念名。
不管是func、type、interface还是const组都一样。所有规则都从这一个原则派生。
# 没有filefunc
read utils.go → 20个func,19个不需要。上下文污染。
# filefunc
read check_one_file_one_func.go → 1个func。正好是需要的。
在Hono框架(star 23k+)上实证了。186个文件拆成626个。4419个测试一个都没坏。文件增加了3.4倍但逻辑一行都没变。纯粹的结构重构。
拾起需要的5-10个不如不打开不需要的290个更重要。
程序只有三种
filefunc拆分文件的标准不是随意的。任何程序都可以用三种操作的组合来构建:
- 顺序 — 从上往下执行
- 分支 — 根据条件选择岔路
- 循环 — 同一操作重复多次
这在1966年得到了数学证明(Bohm-Jacopini定理)。名字不用知道。重要的是filefunc强制每个函数只有这三种中的一种流程。
这段代码不需要会读。重要的是//ff:what后面一行写了这个函数做什么:
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| control | 含义 | 嵌套限制 |
|---|---|---|
sequence | 顺序执行 | 2层 |
selection | 分支(switch) | 2层 |
iteration | 循环(loop) | 2-3层 |
为什么限制嵌套?代码里条件套循环套条件深度到3,人也难读智能体也困惑。限制到深度2每个函数就变简单,智能体修改时副作用减少。
Hono的Node.search原来深度6。用filefunc重构后深度2。每个片段只有一种控制流。整体算法不变。
22条验证规则 — 你不需要背
filefunc有22条规则。看起来很多但你不需要背。运行filefunc validate工具会找出所有违规并告诉你,智能体来修。你说"让违规数归零"就完事了。
下面的表是为了展示"检查些什么"的全貌。浏览一下跳过就行。
文件结构规则:
| 规则 | 违规时 |
|---|---|
| 一个文件一个func(文件名=函数名) | ERROR |
| 一个文件一个type(文件名=类型名) | ERROR |
| 方法:1文件1方法 | ERROR |
_test.go允许多个func | 例外 |
代码质量规则:
| 规则 | 违规时 |
|---|---|
| 嵌套深度:顺序=2,分支=2,循环=2-3 | ERROR |
| func最大1000行 | ERROR |
| func建议:顺序/循环100行,分支300行 | WARNING |
运行filefunc validate违规会全部列出。把这个列表给智能体它就用while ERROR > 0: fix循环收敛。
代码本和注解 — 智能体的地图
拆完文件不算完。智能体需要能快速判断"该打开哪个文件”。代码本和注解解决这个问题。
代码本(codebook.yaml):
required:
feature: [validate, annotate, chain, parse, codebook, report, cli]
type: [command, rule, parser, walker, model, formatter, loader, util]
optional:
pattern: [error-collection, file-visitor, rule-registry]
level: [error, warning, info]
代码本是项目的词汇表。AI智能体的地图。这个文件也不需要你自己做——对智能体说"帮我创建这个项目的codebook.yaml"就行。有了代码本智能体就能不探索直接找到精确的文件。
注解:
同样不需要懂代码本身。关键是一行注解总结了函数的角色:
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
//ff:why Primary citizen is AI agent. 1 file 1 concept prevents context pollution.
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| 注解 | 内容 |
|---|---|
//ff:func | func文件的feature、type、control等元数据 |
//ff:what | 1行说明 — 这个函数做什么 |
//ff:why | 为什么这样做 — 用户的决策 |
//ff:checked | LLM验证签名(自动生成) |
这些注解充当搜索索引。不需要向量嵌入或RAG这样的重型基础设施,一次grep就能得到精确的文件列表。
代码本+注解带来的差异
有filefunc+代码本,智能体从300个文件中精确找到需要的5个。其余295个根本不打开。
没有的话?智能体逐个打开文件猜"这个相关吗?“打开了20个函数,大部分不相关。探索时间比实际工作时间还长。
有代码本的话?智能体看代码本立刻构建搜索查询。一个文件一个概念,所以打开的文件全是有效上下文。read 30个如果全部有效那30个就不是问题。read 1个却跟着来30个分量的才是问题。
tsma — 遗留代码的回归防线
代码已经做到智能体能读了(filefunc)。现在需要让智能体能知道能不能安全地修改。修改没有测试的函数,谁都不知道会坏什么。
假设你接手了10万行的遗留代码。没有测试。想重构但碰了不知道会坏什么。要写测试需要理解代码,要理解代码需要文档,但没有文档。
没人碰。越来越烂。
世界500强IT预算的60-80%被锁在这个僵局中。
如果LLM能代写测试呢?三个问题:
- 不知道从哪开始。 527个函数时从1号按顺序来?最重要的先?没标准。
- 无法验证测试质量。 LLM写的测试通过了。真的验证了行为还是空壳?
- 没反馈就停在60-70%。 必须告诉哪些分支缺了才能补齐。
tsma全部解决。
tsma next — 一条命令
智能体需要知道的命令只有一个。
$ tsma next
这一条命令驱动整个循环:
$ tsma next # 显示下一个没有测试的函数
→ 编写测试
$ tsma next # 检测新测试,运行,测量覆盖率
→ 100%?PASS,下一个函数
→ <100%?用行号告知未覆盖分支
$ tsma next # 重新测量修改后的测试
→ 改善与否,标记DONE并进入下一个
重复直到"All functions complete!“出现。
反馈戏剧性地改变了LLM的测试
tsma的核心价值不在索引也不在覆盖率测量。是用行号精确告知未覆盖分支。
没有反馈时:
"给ListContracts函数写测试"
→ LLM只测试happy path
→ 覆盖率60-70%
有反馈时:
"给ListContracts函数写测试"
→ 覆盖率65%(11/17)
→ UNCOVERED:
line 41 — if params.Status != nil
line 44 — if params.BuildingId != nil
line 70 — if err != nil (CountSummary)
→ LLM添加精确覆盖那些分支的测试
→ 覆盖率100%
同一个LLM。区别只有反馈有无。三行行号把60%和100%分开了。
第6课学的Ratchet Pattern在这里实现了。tsma给反馈,LLM修改,tsma再测量。这就是Symbolic Feedback Loop。
在527个函数上验证了
在实际项目(527个函数)上应用tsma的结果:
| 结果 | 数量 | 比例 |
|---|---|---|
| PASS(100%分支覆盖率) | 246 | 46.7% |
| DONE(尽力而为) | 281 | 53.3% |
| TODO(未处理) | 0 | 0% |
246个函数达到了100%分支覆盖率。其余281个没到100%但在可能的范围内写了测试。
为什么有些函数到不了100%?有些函数的结构使得测试很困难。原来的代码不是考虑了可测试性才写的。这反映的是代码的可测试性,不是tsma的局限。
智能体会死但进度被保存
智能体必然会崩溃。token限制、网络错误、会话断开。527个函数不可能在一个会话中全部处理。
tsma把进度状态持久化在.tsma/session.json中。
$ tsma status
527 functions
PASS: 246 (46.7%)
DONE: 281 (53.3%)
TODO: 0 (0.0%)
智能体在第200个函数死了?新智能体运行tsma next就从201号开始。session.json就是检查点。多个智能体轮流工作也不冲突。函数级别原子操作。
会话是缓存,源文件是真理来源。删了测试文件即使session.json记录PASS,该函数也会回到TODO。会话不会与现实脱节。
filefunc + tsma + whyso:三个工具的结合
filefunc和tsma是独立的,但结合起来有协同效应。再加上whyso就完成了智能体友好代码库的三根轴。
filefunc → tsma连接:
filefunc强制一个文件一个函数,所以tsma的函数索引等同于文件索引。函数级测试 = 文件级测试。追踪成本为零。
filefunc → whyso连接:
func = file,所以函数级变更历史精确映射到文件级。
whyso history check_ssac_openapi.go # CheckSSaCOpenAPI函数的变更历史
一个文件里有多个函数的话要翻diff才知道哪个函数变了。filefunc的话文件变更 = 函数变更。追踪成本为零。
whyso的耦合统计也因为filefunc变得精确:
whyso coupling check_ssac_openapi.go
同一请求中一起修改的函数:
check_response_fields.go 8次
check_err_status.go 5次
types.go 4次
没有显式关系但在耦合统计中反复出现就是隐藏依赖的信号。
Agent Operable Codebase的4个条件
整理到目前学到的,智能体能稳定工作的代码库条件有四个:
| 条件 | 工具 | 效果 |
|---|---|---|
| 1. 一个文件一个概念 | filefunc | 阻断上下文污染 |
| 2. 所有函数有测试 | tsma | 修改后检测回归 |
| 3. 符号引用连接 | operationId(yongol) | 跨层追踪 |
| 4. 消除隐式耦合 | whyso coupling | 检测隐藏依赖 |
满足这四个条件的代码库中,智能体可以:
- 一次grep找到精确文件
- 一次read只读需要的
- 修改后用测试检测回归
- 用operationId掌握一个功能的全部范围
第8课的核心信息:不要把火车做得更快。铺铁轨。
不是更大的模型、更聪明的智能体——智能体能工作的结构才是第一位的。
实操(filefunc + tsma)
准备物: 项目(Go、TypeScript、Python任一——第1课的应用就行)、filefunc、tsma(让智能体安装)。filefunc和tsma都支持Go、TypeScript、Python。
目标: 把现有项目转换为agent-operable codebase。
步骤1 — 应用filefunc validate
# 在当前项目中运行filefunc validate
filefunc validate
# 确认违规列表
# 指示智能体修复违规
"修复filefunc validate结果中的所有ERROR。
现有测试不能坏。"
步骤2 — 分离违规函数
- 一个文件有多个函数 → 每个函数分离到独立文件
- 文件名与函数名一致
- 对智能体说"分离时不要破坏现有代码"就行
步骤3 — 用tsma测量覆盖率
# 初始化tsma并检查状态
tsma status
# 指示智能体写测试
"重复tsma next添加测试。
出现未覆盖分支就添加覆盖那些分支的测试。
直到All functions complete。"
步骤4 — 确认结果
- filefunc validate: 0 errors
- tsma status: TODO 0
- 现有测试:全部通过
确认事项:
- 违规归零智能体跑了几轮?
- filefunc应用前后文件数和平均LOC怎么变了?
- tsma不给反馈和给反馈时覆盖率差多少?
相关文章
Reins Engineering 全部课程
| 课程 | 标题 |
|---|---|
| 第1课 | 如何指挥AI |
| 第2课 | 如何不信任AI |
| 第3课 | 不会崩溃的应用 |
| 第4课 | 将决策移出代码 |
| 第5课 | 有缰绳的AI |
| 第6课 | 通过就锁定 |
| 第7课 | 翻转谄媚 |
| 第8课 | 智能体的工厂 |
| 第9课 | 代码之外的自动化 |
| 第10课 | 数据的法则 |
参考资料来源
- Stanford,“Lost in the Middle: How Language Models Use Long Contexts”(2024)— 相关信息埋在上下文中间时30%+性能下降
- Amazon,“Context Length Alone Hurts LLM Performance”(2025)— 不相关token即使是空格也导致13.9-85%性能下降
- Bohm-Jacopini定理(1966)— 所有程序都可用顺序、分支、循环三种控制结构的组合表达
- Hono框架实证 — 186个文件→626个文件分离,4419个测试全部通过
- tsma 527个函数实证 — PASS 246个(46.7%)、DONE 281个(53.3%)、TODO 0个
- 世界500强IT预算统计 — 60-80%被锁在遗留维护中
Stanford “Lost in the Middle”(2024):相关信息埋在上下文中间时30%+下降。Amazon “Context Length Alone Hurts LLM Performance”(2025):不相关token即使是空格也导致13.9-85%下降。 ↩︎