filefunc × Hono — エージェントが一度に読むコードを60行から18行へ

コーディングエージェントが大きなコードベースで的外れな修正を繰り返すなら、ファイルひとつをreadさせたら関係のない関数19個がついてきてコンテキストが汚染されるなら、「1 file 1 concept」のようなコンベンションが本当に効果があるのか疑わしいなら――スター23k超の実戦フレームワークでそれを測定した結果がここにある。

「ファイルが多くなりすぎないか?」

filefuncに対して最もよく受ける質問だ。186個のファイルを626個に分割したら管理できなくなるのではないかという懸念だ。

答えはHonoにある。Cloudflare Workers、Deno、Bun、Node.jsで動く超軽量Webフレームワーク。スター23k+、npm週間ダウンロード100万+。プロダクションで検証された実戦コードだ。このコードをfilefuncでリファクタリングした。テスト4419件、すべて通過。(park-jun-woo/hono フォークで直接検証可能)

数字

指標原本リファクタリング後
ソースファイル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から来た。ロジックは一行も変わっていない。純粋な構造リファクタリングだ。

核心はファイル数ではなく「読む長さ」だ

「違反0、626個のファイル」は実のところプロキシだ。filefuncの真の目的はファイルを細かく分割することではない――エージェントが概念ひとつをreadするとき、その概念だけを、過度に長くなく読めるようにすることだ。だから証明すべき本当の数字は違反数ではなくファイルごとの読む長さだ。測定した。

ファイルごとの行数原本リファクタリング後
中央値60.017.5(−71%)
p90305119(−61%)
最大2,7781,051(−62%)
≤20行のファイル比率26%54%

エージェントが概念ひとつを開くと、以前は平均60行を飲み込んでいたが、今は18行を飲み込む。最悪の場合(p90)も305行から120行に落ちた。関数自体の長さはそのままだ(中央値11→12行)――当然だ。関数を書き直したのではなく再配置したのだから。減ったのは「概念ひとつを読もうとして否応なく一緒に読まされる周辺コード」だ。

なぜこれが重要なのか。長いコンテキストはただではない。LLMは長い入力の中央に埋もれた情報を体系的に見落とす(Liu et al., Lost in the Middle, TACL 2023, arXiv:2307.03172)。コーディング課題ではコンテキストが長くなるほど性能が急落する――あるベンチマークでは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エージェントが HonoRequest 型ひとつを探してこのファイルをreadすると? 19個の不要な型がついてくる。コンテキスト汚染だ。

リファクタリング後は各型が独立したファイルだ。HonoRequest ひとつが必要なら hono_request.ts ひとつだけreadすればいい。原本の types.ts はre-export hubとして残し、既存のimportパスを保存した。

# 原本
import { HonoRequest } from './types'  // 20+の型がついてくる

# リファクタリング後
import { HonoRequest } from './types'  // 同じパス、同じ動作
// 内部的には types.ts → hono_request.ts re-export

外から見れば何も変わっていない。AIエージェントから見ればすべてが変わった。

深さ6から2へ

Honoのルーターアルゴリズムは複雑だ。trie-routerNode.search はnesting depthが6だった。

for → if → if → for → if → if   // depth 6

depth 6は悪いコードか? 違う。トライ探索は元来ネストが深い。しかしAIエージェントがこの関数を理解しようとすると6段階のネストを一度に頭の中に入れなければならない。人間も同様だ。filefuncは内部ロジックをprivateメソッドとモジュールレベルの矢印関数に抽出した。depth 6 → 2。各断片はひとつの制御フローだけを持つ。アルゴリズム全体は同一だ。

# 原本: モノリシックなsearch
Node.search()   // depth 6、100行+

# リファクタリング: 断片に分解
Node.search()           // depth 2、組み合わせのみ担当
  → matchParam()        // depth 1、パラメータマッチング
  → matchWildcard()     // depth 1、ワイルドカード処理
  → mergeHandlers()     // depth 1、ハンドラーのマージ

TypeScriptのF1、そして正直な末尾

filefuncの核心ルールF1は「ファイルひとつに関数ひとつ」だ。Goでは直感的だ。しかしTypeScriptでファイルを分割するとモジュールシステムが壊れる。クラスメソッドを外部ファイルに出すと this バインディングが失われる。そこでfilefuncのTypeScriptパーサー(ts_ast.js)は function 宣言だけをカウントし、const 矢印関数はカウントしない。原則は「ファイルひとつに概念ひとつ」であって「文法的にfunctionがひとつ」ではないからだ。

ここで正直でなければならない。このアプローチは簡単な場合(型、単一ヘルパー)はきれいに分離できたが、すべてを分離できたわけではない。 リファクタリング後を再測定すると:

  • 626個中90%(566個)が関数≤1個――「1ファイル1概念」を満たす。(原本は70%だった。)
  • しかし60個のファイル(9.6%)がいまだに関数2個以上を同居させている。そしてよりによってそれらが長い――この60個の行数中央値は151行。例えば src/utils/url.ts には14個の関数が319行の中に入っている。

つまり const 矢印の手法はカウンターは通過させるが目的は部分的にしか達成していない。一つのファイルに矢印関数が複数残ると、エージェントがそのファイルを開くときに依然として複数の概念を読む。メトリクスが目標になった瞬間メトリクスは壊れる(Goodhart)。filefuncも例外ではない――残りのread-length上のリスクの大部分がこの10%の末尾に集中している。違反0という数字を正解に格上げせず、何がまだできていないかまで測定すること――それが検証だ。

「それで何が良くなるの?」

ファイルが626個になると人間は不便かもしれない。ディレクトリを開くとファイルが溢れ出る。しかしAIエージェントはディレクトリを開かない。grep する。

rg '//ff:func' --glob '*.ts' -l | head -20    # 候補ファイルの抽出
rg '//ff:what.*router' --glob '*.ts'            # ルーター関連の関数のみ

186個のファイルに関数が平均3〜4個ずつ入っていると、grepがファイルを見つけてもreadすると不要な関数がついてくる。626個のファイルに概念が1個ずつ入っていると、grepが見つけたファイル=必要な概念。中間過程が消える。エージェントのコード探索で「関連箇所を見つけること(localization)」が下流の問題解決のボトルネックになっている(Chen et al., LocAgent, 2025, arXiv:2503.09089)が、filefuncは概念をファイル境界と一致させることでこの探索を決定論的にする。

関数単位が常に正解か

反証も正直に見よう。ある制御実験ではRAGコード自動補完において「関数単位チャンキングが他の戦略より3.6〜5.6pp低くパレート最適ではない」と報告している(Wu et al., 2026, arXiv:2605.04763)。関数単位が万能ではないということだ。

ただし、これは層が異なる話だ。 その実験は検索器がコードを切り取ってプロンプトに詰め込む自動補完の文脈だ。filefuncは検索器がチャンクを作るのではなく、エージェントがファイルを直接選んでreadする運用単位を扱う。チャンキング戦略(retrieval chunk)と運用単位(file an agent opens)は異なるレイヤーだ。それでもこの区別を明示することは重要だ――「細かく分ければ無条件に良い」という誇張はfilefuncの主張ではない。主張は「エージェントが読む単位が概念と一致すれば読む長さが短くなる」であり、上の数字がそれを示している。

制約は自由だ

Honoをfilefuncでリファクタリングして確認したことはひとつだ。

構造的制約はコードを制限しない。探索を解放する。

ファイルが増えることはコストだ。しかし各ファイルがひとつの概念だけを持つとき、エージェントは正確に必要なものだけをreadし、不要なコンテキストに汚染されない――読む長さが60行から18行に減ったという測定がその証拠だ。人間も同様だ。関数名がファイル名であれば、ディレクトリがそのまま目次になる。

397個の違反が0になり、4419個のテストが原本と同じく通過したこと。そしてその結果をREADMEのリファクタリングレポートで誰でも再実行できること。これが「1 file 1 concept」が理論ではなく実戦であるという証拠だ。残り9.6%の末尾まで含めて。

関連記事

関連読書(外部)

  • Effective context engineering for AI agents — Anthropic. 「context rot」と有限なアテンション予算を核心問題として指摘する一次情報源――filefuncが不要なコンテキストを削減する理由の共通基盤。
  • Strategies and Tactics for working with Coding Agents — Brian Kihoon Lee. grep基盤の探索と情報アーキテクチャを自ら設計せよという主張――エージェントが「ターゲットだけ読む」ようにするファイル構造コンベンションと直結。
  • The Vibes Don’t Scale — Paul Stack. バイブコーディングが規模においてアーキテクチャドリフトで崩壊するメカニズム――filefuncが解こうとする「大きなコードベースがエージェントを壊す」という問題意識。
  • Agentic Engineering Patterns — Simon Willison. Context Quarantine/Pruningなどコンテキスト管理パターン――filefuncのagent-operable主張を実務パターン言語として拡張。
  • Agent Harness Engineering — Addy Osmani. エージェント性能はモデルより周辺インフラで決まる――コード構造コンベンションをハーネスのひとつの軸として再文脈化。

参考文献

  • 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)