第8课

实用技巧 — 知道这些就能指挥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行的文件有经验的话"这部分不要动"的直觉会起作用。

智能体没有这种直觉。

SWEAI智能体
探索方式用眼睛浏览目录树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拆分文件的标准不是随意的。任何程序都可以用三种操作的组合来构建:

  1. 顺序 — 从上往下执行
  2. 分支 — 根据条件选择岔路
  3. 循环 — 同一操作重复多次

这在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-3ERROR
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:funcfunc文件的feature、type、control等元数据
//ff:what1行说明 — 这个函数做什么
//ff:why为什么这样做 — 用户的决策
//ff:checkedLLM验证签名(自动生成)

这些注解充当搜索索引。不需要向量嵌入或RAG这样的重型基础设施,一次grep就能得到精确的文件列表。


代码本+注解带来的差异

有filefunc+代码本,智能体从300个文件中精确找到需要的5个。其余295个根本不打开。

没有的话?智能体逐个打开文件猜"这个相关吗?“打开了20个函数,大部分不相关。探索时间比实际工作时间还长。

有代码本的话?智能体看代码本立刻构建搜索查询。一个文件一个概念,所以打开的文件全是有效上下文。read 30个如果全部有效那30个就不是问题。read 1个却跟着来30个分量的才是问题。


tsma — 遗留代码的回归防线

代码已经做到智能体能读了(filefunc)。现在需要让智能体能知道能不能安全地修改。修改没有测试的函数,谁都不知道会坏什么。

假设你接手了10万行的遗留代码。没有测试。想重构但碰了不知道会坏什么。要写测试需要理解代码,要理解代码需要文档,但没有文档。

没人碰。越来越烂。

世界500强IT预算的60-80%被锁在这个僵局中。

如果LLM能代写测试呢?三个问题:

  1. 不知道从哪开始。 527个函数时从1号按顺序来?最重要的先?没标准。
  2. 无法验证测试质量。 LLM写的测试通过了。真的验证了行为还是空壳?
  3. 没反馈就停在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%分支覆盖率)24646.7%
DONE(尽力而为)28153.3%
TODO(未处理)00%

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%被锁在遗留维护中

  1. Stanford “Lost in the Middle”(2024):相关信息埋在上下文中间时30%+下降。Amazon “Context Length Alone Hurts LLM Performance”(2025):不相关token即使是空格也导致13.9-85%下降。 ↩︎