
Если кодинг-агент раз за разом правит не то в большой кодовой базе, если один 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.)
Цифры
| Метрика | Оригинал | После рефакторинга |
|---|---|---|
| Исходных файлов | 186 | 626 |
| Всего строк | 24 653 | 30 244 |
| Нарушений filefunc | 397 | 0 |
| vitest: прошли | 4419 | 4419 |
| vitest: упали | 4 | 4 (исходный дефект) |
| vitest: пропущены | 33 | 33 |
Файлов стало в 3,4 раза больше. Строк — на 23% больше. Нарушений — с 397 до нуля. Ни один тест не сломался — точнее, ровно так же, как в оригинале, упали те же 4 (дефект был изначально). Прирост в 23% строк объясняется аннотациями (//ff:func, //ff:what) и re-export hub. Логика не изменилась ни на строку. Чистый структурный рефакторинг.
Суть — не в количестве файлов, а в длине чтения
«Ноль нарушений, 626 файлов» — это, по сути, прокси. Настоящая цель filefunc — не дробить файлы ради дробления. Цель — чтобы агент, открывая файл ради одной концепции, читал только её, и не слишком длинно. Значит, доказывать нужно не число нарушений, а длину чтения на файл. Мы её измерили.
| Строк на файл | Оригинал | После рефакторинга |
|---|---|---|
| Медиана | 60,0 | 17,5 (−71%) |
| p90 | 305 | 119 (−61%) |
| Максимум | 2 778 | 1 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% хвоста.
Связанные статьи
- Кодовая база, управляемая агентом — «20 функций в файле снижают производительность агента на 30–85%» — тезис, которому посвящена эта статья
- filefunc: один файл — одна концепция — определение самой конвенции; эта статья — её масштабная практическая проверка
- Как создавать agent-operable системы — большой нарратив о том, как сделать унаследованный код читаемым для агента
- Ratchet Pattern — детерминированный шлюз, который не пускает дальше, пока не пройдут все 4419 тестов
Дополнительное чтение (внешнее)
- 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)