filefunc × Hono — код, читаемый агентом за один раз: с 60 строк до 18

Если кодинг-агент раз за разом правит не то в большой кодовой базе, если один read файла тянет за собой 19 посторонних функций и загрязняет context, если вы сомневаетесь, работает ли конвенция вроде «1 file 1 concept» на практике — вот результат измерений на реальном фреймворке с 23k звёзд.

«Файлов не станет слишком много?»

Это самый частый вопрос о filefunc. Мол, разбить 186 файлов на 626 — это уже неуправляемо.

Ответ — в Hono. Сверхлёгкий веб-фреймворк для Cloudflare Workers, Deno, Bun, Node.js. Более 23k звёзд, более 1 млн еженедельных загрузок npm. Код, проверенный в продакшне. Мы отрефакторили его по правилам filefunc. 4419 тестов — все прошли. (Проверить можно в форке park-jun-woo/hono.)

Цифры

МетрикаОригиналПосле рефакторинга
Исходных файлов186626
Всего строк24 65330 244
Нарушений filefunc3970
vitest: прошли44194419
vitest: упали44 (исходный дефект)
vitest: пропущены3333

Файлов стало в 3,4 раза больше. Строк — на 23% больше. Нарушений — с 397 до нуля. Ни один тест не сломался — точнее, ровно так же, как в оригинале, упали те же 4 (дефект был изначально). Прирост в 23% строк объясняется аннотациями (//ff:func, //ff:what) и re-export hub. Логика не изменилась ни на строку. Чистый структурный рефакторинг.

Суть — не в количестве файлов, а в длине чтения

«Ноль нарушений, 626 файлов» — это, по сути, прокси. Настоящая цель filefunc — не дробить файлы ради дробления. Цель — чтобы агент, открывая файл ради одной концепции, читал только её, и не слишком длинно. Значит, доказывать нужно не число нарушений, а длину чтения на файл. Мы её измерили.

Строк на файлОригиналПосле рефакторинга
Медиана60,017,5 (−71%)
p90305119 (−61%)
Максимум2 7781 051 (−62%)
Доля файлов ≤20 строк26%54%

Раньше агент, открывая одну концепцию, в среднем поглощал 60 строк — теперь 18. Даже в худшем случае (p90) — с 305 до 120. Длина самих функций не изменилась (медиана 11→12 строк) — и неудивительно: их не переписывали, а переставляли. Сократилось «окружение», которое агент вынужден читать вместе с нужной концепцией.

Почему это важно. Длинный context — не бесплатный. 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). И подача точно нужного фрагмента вместо целого файла улучшает качество code completion лучше, чем просто увеличение контекста (Yusuf et al., 2025, arXiv:2510.06606). Уменьшение длины чтения — это не эстетика, а защита точности.

Проблема types.ts

Перейдём от абстракции к конкретике. В оригинальном src/types.ts у Hono было более 20 интерфейсов и типов.

Агент, которому нужен только тип HonoRequest, читает этот файл — и получает 19 лишних типов в придачу. Загрязнение context.

После рефакторинга каждый тип живёт в отдельном файле. Нужен HonoRequest — читаем только hono_request.ts. Оригинальный types.ts остался как re-export hub, чтобы сохранить привычные пути импорта.

# Оригинал
import { HonoRequest } from './types'  // тянет 20+ типов

# После рефакторинга
import { HonoRequest } from './types'  // тот же путь, то же поведение
// внутри: types.ts → hono_request.ts re-export

Снаружи — ничего не изменилось. Для ИИ-агента — изменилось всё.

Глубина вложенности: с 6 до 2

Алгоритм маршрутизации Hono непрост. Node.search в trie-router имел глубину вложенности 6.

for → if → if → for → if → if   // глубина 6

Это плохой код? Нет. Обход trie по природе своей глубоко вложен. Но агенту, чтобы понять эту функцию, нужно удержать в голове 6 уровней вложенности сразу. Человеку — тоже. filefunc извлёк внутреннюю логику в приватные методы и стрелочные функции уровня модуля. Глубина 6 → 2. Каждый фрагмент содержит ровно один управляющий поток. Весь алгоритм остался прежним.

# Оригинал: монолитный search
Node.search()   // глубина 6, 100+ строк

# После рефакторинга: разбит на части
Node.search()           // глубина 2, только композиция
  → matchParam()        // глубина 1, сопоставление параметров
  → matchWildcard()     // глубина 1, обработка wildcard
  → mergeHandlers()     // глубина 1, слияние обработчиков

F1 в TypeScript и честный хвост

Ключевое правило filefunc — F1: «один файл — одна функция». В Go это интуитивно. Но в TypeScript дробление файлов ломает модульную систему: вынесите метод класса в другой файл — потеряете this. Поэтому TypeScript-парсер filefunc (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 — не исключение: большая часть оставшегося риска длинного чтения сосредоточена в этих 10% хвоста. Не возводить ноль нарушений в абсолют, а измерять, что ещё не сделано, — это и есть верификация.

«И что с того?»

При 626 файлах человеку может быть неудобно. Открываешь директорию — файлы сыплются. Но ИИ-агент директории не открывает. Он делает grep.

rg '//ff:func' --glob '*.ts' -l | head -20    # извлечь файлы-кандидаты
rg '//ff:what.*router' --glob '*.ts'            # только функции, связанные с роутером

Если в 186 файлах в среднем по 3–4 функции — grep найдёт файл, но read притащит лишние функции. Если в 626 файлах по одной концепции — найденный grep файл = нужная концепция. Промежуточный шаг исчезает. Локализация нужного места — узкое место downstream-решения задач агентом (Chen et al., LocAgent, 2025, arXiv:2503.09089); filefunc совмещает концепцию с границами файла и делает этот поиск детерминированным.

Всегда ли функциональный уровень — правильный ответ?

Посмотрим честно и на контраргументы. Одно контролируемое исследование сообщает, что в RAG-автодополнении кода «function-level chunking отстаёт от других стратегий на 3,6–5,6 п.п. и не является Парето-оптимальным» (Wu et al., 2026, arXiv:2605.04763). Функциональный уровень — не универсальный ответ.

Но это разные уровни разговора. То исследование касается автодополнения, где ретривер нарезает код и вталкивает куски в промпт. filefunc работает с операционной единицей — файлом, который агент сам выбирает и читает. Стратегия chunking (retrieval chunk) и операционная единица (файл, открываемый агентом) — разные слои. Тем не менее важно это оговорить: filefunc не утверждает, что «чем мельче, тем лучше». Утверждение одно: «когда единица чтения агента совпадает с концепцией, длина чтения сокращается» — и приведённые цифры это подтверждают.

Ограничение — это свобода

Рефакторинг Hono по правилам filefunc подтвердил одно.

Структурное ограничение не сужает код. Оно освобождает навигацию.

Рост числа файлов — это цена. Но когда каждый файл содержит ровно одну концепцию, агент читает именно то, что нужно, и не загрязняет context лишним — измерение это подтверждает: длина чтения упала с 60 до 18 строк. То же верно для человека: если имя функции совпадает с именем файла, директория становится оглавлением.

397 нарушений стали нулём, 4419 тестов прошли ровно так же, как в оригинале. И каждый может воспроизвести результат по Refactoring Report в 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 и другие — расширение agent-operable аргументации filefunc в язык практических паттернов.
  • 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 (filefunc Refactoring Report в README) · Конвенция: filefunc
  • Заглавное изображение: сгенерировано ИИ (Google Gemini)