filefunc × Hono — 让Agent一次读完的代码,从60行压缩到18行

如果你的编程Agent总是在大型代码库里改错地方,如果让它read一个文件却带来了19个无关函数污染context,如果你怀疑"1 file 1 concept"这类约定究竟有没有实际效果——那么,这里有一份来自star数23k的实战框架的测量结果。

“文件会不会太多了?”

这是关于filefunc最常被问到的问题。把186个文件拆成626个,管理上会不会失控?

答案在Hono里。Hono是一个可以运行在Cloudflare Workers、Deno、Bun、Node.js上的超轻量Web框架,star数23k+,npm周下载量100万+,是经过生产环境验证的实战代码。我们用filefunc对这套代码进行了重构,4419个测试,全部通过。(可在 park-jun-woo/hono fork中亲自验证)

数字

指标原版重构后
源文件数186626
总行数24,65330,244
filefunc违规数3970
vitest通过44194419
vitest失败44(原有缺陷)
vitest跳过3333

文件数增加了3.4倍,行数增加了23%,违规数从397降到了0,没有一个测试被破坏——准确说是与原版完全相同的4个测试失败(原版本身就有的缺陷)。23%的行数增加来自注解(//ff:func//ff:what)和re-export hub。逻辑一行都没有改变,是纯粹的结构重构。

关键不在于文件数,而在于"读取长度"

“零违规、626个文件"其实只是代理指标。filefunc真正的目标不是把文件拆得越细越好——而是让Agent在read某个概念时,只读到那个概念,且不会过长。所以真正需要证明的数字不是违规数,而是每个文件的读取长度。我们测量了。

每文件行数原版重构后
中位数60.017.5(−71%)
p90305119(−61%)
最大值2,7781,051(−62%)
≤20行文件占比26%54%

Agent打开一个概念,以前平均要消化60行,现在只需消化18行。最坏情况(p90)也从305行降到了120行。函数本身的长度没有变(中位数11→12行)——这很自然,因为我们不是在重写函数,而是在重新排列它们。减少的是"为了读某一个概念,不得不连带读到的周边代码”。

为什么这很重要?长context不是免费的。LLM会系统性地遗漏埋在长输入中间的信息(Liu et al., Lost in the Middle, TACL 2023, arXiv:2307.03172)。在编程任务中,随着context变长,性能会急剧下滑——在某个基准测试中,Claude 3.5 Sonnet的准确率从29%崩溃到了3%(Rando et al., LongCodeBench, 2025, arXiv:2505.07897)。而按概念切割、只给精确所需内容,比整文件喂入能更好地提升代码补全质量(Yusuf et al., 2025, arXiv:2510.06606)。缩短读取长度不是审美偏好,而是对准确率的主动防御。

types.ts 问题

不谈抽象,看具体。Hono原版的src/types.ts里有超过20个接口和类型。

AI Agent想找HonoRequest这一个类型,却不得不read整个文件?那19个无关类型就跟着进来了,context被污染。

重构后,每个类型都是独立文件。只需要HonoRequest,就只read hono_request.ts这一个文件。原来的types.ts作为re-export hub保留,现有的import路径照常可用。

# 原版
import { HonoRequest } from './types'  // 20+个类型一起进来

# 重构后
import { HonoRequest } from './types'  // 同样的路径,同样的行为
// 内部实际上是 types.ts → hono_request.ts re-export

从外部看,什么都没变。从AI Agent的角度看,一切都变了。

嵌套深度从6降到2

Hono的路由算法很复杂。trie-routerNode.search嵌套深度为6。

for → if → if → for → if → if   // 深度6

depth 6是烂代码吗?不是。trie搜索本来就嵌套深。但AI Agent要理解这个函数,就必须一次性把6层嵌套装进脑子里。人也一样。filefunc将内部逻辑提取为私有方法和模块级箭头函数,depth 6→2,每个片段只含一种控制流,整体算法不变。

# 原版:单块式 search
Node.search()   // 深度6,100行+

# 重构后:分解为片段
Node.search()           // 深度2,只负责组合
  → matchParam()        // 深度1,参数匹配
  → matchWildcard()     // 深度1,通配符处理
  → mergeHandlers()     // 深度1,合并处理器

TypeScript的F1规则,以及诚实的尾部

filefunc的核心规则F1是"一个文件一个函数"。在Go里很直观,但在TypeScript里拆文件会破坏模块系统——把类方法移到外部文件,this绑定就消失了。因此filefunc的TypeScript解析器(ts_ast.js)只计数function声明,不计数const箭头函数。原则是"一个文件一个概念",而不是"语法上只有一个function"。

这里需要诚实。这种做法对简单情况(类型、单一helper)分离得很干净,但并没有把所有东西都分离清楚。重新测量重构后的结果:

  • 626个文件中,90%(566个)函数数≤1个——满足"1文件1概念"。(原版是70%。)
  • 60个文件(9.6%)里仍然有2个以上函数共存,而且恰好这些文件都很长——这60个文件的行数中位数为151行。例如src/utils/url.ts里14个函数共用319行。

也就是说,const箭头函数的做法通过了计数器,但只部分实现了目标。如果一个文件里保留了多个箭头函数,Agent打开这个文件时,读到的仍然是多个概念。指标成为目标的那一刻,指标就开始失效(Goodhart)。filefunc也不例外——剩余read-length风险的大部分都集中在这10%的尾部。不把"零违规"这个数字捧上神坛,还要测量哪里还没做到——这才是验证。

“那到底好在哪儿?”

文件变成626个,人可能会不舒服,打开目录一堆文件扑面而来。但AI Agent不会打开目录,它用grep

rg '//ff:func' --glob '*.ts' -l | head -20    # 提取候选文件
rg '//ff:what.*router' --glob '*.ts'            # 只查路由相关函数

186个文件里平均每个有3-4个函数,grep找到文件之后还要read,无关函数跟着进来。626个文件里每个只有1个概念,grep找到的文件=所需概念,中间过程消失了。在Agent的代码导航中,“定位相关位置(localization)“是下游问题解决的瓶颈(Chen et al., LocAgent, 2025, arXiv:2503.09089),而filefunc通过让概念与文件边界对齐,使这个定位过程变得确定性。

函数粒度总是正确答案吗

反面证据也要诚实面对。一项对照实验报告称,在RAG代码自动补全中,“函数级分块比其他策略低3.6~5.6pp,且不是帕累托最优”(Wu et al., 2026, arXiv:2605.04763)。函数级不是万能的。

不过,这是不同层次的讨论。那个实验是检索器把代码切块塞进提示词的自动补全场景。filefunc处理的不是检索器生成chunk,而是Agent直接选择文件进行read的运作单元。分块策略(retrieval chunk)和运作单元(agent打开的文件)是不同层次。即便如此,明确说明这个区别依然重要——“拆得越细越好"不是filefunc的主张。主张是"Agent读取的单元与概念一致,读取长度就会缩短”,上面的数字证明了这一点。

约束即自由

用filefunc重构Hono,验证了一件事。

结构性约束不会限制代码,它解放了导航。

文件增多是成本。但当每个文件只承载一个概念时,Agent就能精确read所需内容,不被无关context污染——读取长度从60行降至18行的测量就是证据。对人来说也一样,函数名即文件名,目录就是目录。

397个违规归零,4419个测试与原版一致通过。而且这个结果任何人都可以在README的重构报告里自己复现。这就是"1 file 1 concept"不只是理论,而是实战的证明。包括那剩余的9.6%的尾部在内。

相关文章

延伸阅读(外部)

  • Effective context engineering for AI agents — Anthropic. 将"context rot"与有限的注意力预算作为核心问题点出的第一手资料——filefunc减少无效context的理由与之同根。
  • Strategies and Tactics for working with Coding Agents — Brian Kihoon Lee. 主张亲手设计基于grep的导航与信息架构——与让Agent"只读目标"的文件结构约定直接呼应。
  • The Vibes Don’t Scale — Paul Stack. 解析vibe coding在规模化后因架构漂移而崩溃的机制——filefunc所要解决的"大型代码库让Agent失效"这一问题意识的来源。
  • Agentic Engineering Patterns — Simon Willison. Context Quarantine/Pruning等context管理模式——将filefunc的agent-operable主张扩展到实践模式语言中。
  • Agent Harness Engineering — Addy Osmani. Agent性能由模型周边基础设施决定——将代码结构约定重新语境化为harness的一个轴心。

参考文献

  • Liu et al. “Lost in the Middle: How Language Models Use Long Contexts” (TACL 2023, arXiv:2307.03172)
  • Rando et al. “LongCodeBench: Evaluating Coding LLMs at 1M Context Windows” (2025, arXiv:2505.07897)
  • Yusuf et al. “Beyond More Context: How Granularity and Order Drive Code Completion Quality” (2025, arXiv:2510.06606)
  • Chen et al. “LocAgent: Graph-Guided LLM Agents for Code Localization” (2025, arXiv:2503.09089)
  • Wu et al. “How Does Chunking Affect Retrieval-Augmented Code Completion? A Controlled Empirical Study” (2026, arXiv:2605.04763)
  • 重构结果验证: park-jun-woo/hono (README的filefunc Refactoring Report) · 约定: filefunc
  • 题图:AI 生成(Google Gemini)