Сенсоры поддерживаемости для агентов кодирования
Как ESLint, dependency-cruiser и инференциальные ИИ-ревью помогают держать под контролем код, который пишут агенты. Практический опыт с TypeScript/Next.js.
Перевод статьи Birgitta Böckeler с martinfowler.com. Birgitta — Distinguished Engineer и эксперт по AI-assisted delivery в Thoughtworks, более 20 лет опыта в разработке ПО.
В предыдущей статье об инженерии харнесов Birgitta сформулировала концепцию: система «гидов и сенсоров» увеличивает вероятность качественных выводов агента и позволяет ему самостоятельно исправлять ошибки до того, как они попадут на глаза человеку. Эта статья — практическое продолжение, посвящённое сенсорам поддерживаемости кода.
Ключевые выводы
- Базовый линтинг (ESLint) с правилами под ИИ-ошибки — дешёвый и эффективный сенсор, которого раньше избегали из-за накладных расходов
- Кастомные lint-сообщения с инструкциями по самокоррекции заметно меняют поведение агента
- Правила зависимостей (dependency-cruiser) надёжно держат архитектурные слои — хорошая замена markdown-гайдам
- Метрики связанности (coupling data) мало полезны ИИ сами по себе: нужна семантическая интерпретация
- ИИ-ревью модульности находит реальные архитектурные проблемы, накопленные агентами, — даже без coupling-данных
Обычно мы хотим добиться и контролировать несколько измерений качества кодовой базы: функциональную корректность (работает как задумано), архитектурную пригодность (достаточно быстрая, безопасная, удобная) и поддерживаемость. Поддерживаемость здесь определяется как лёгкость и низкий риск изменения кодовой базы со временем — также известная как «внутреннее качество». То есть важно уметь вносить изменения быстро не только сегодня, но и в будущем. И не хотелось бы беспокоиться о внесении багов или ухудшении показателей при каждом изменении — ни своём, ни агентском.
Проблемы внутреннего качества влияют на ИИ-агентов примерно так же, как и на разработчиков-людей. Агент, работающий в запутанной кодовой базе, может искать не там, создавать несоответствия из-за незамеченного дублирования или быть вынужден загружать больше контекста, чем требует задача.
В этой статье я описываю свои эксперименты с различными сенсорами, которые помогают нам и ИИ оценивать поддерживаемость кодовой базы, и делюсь тем, что из этого вынесла.
Приложение
Я работала над внутренним аналитическим дашбордом для комьюнити-менеджеров: он читает данные об активности в чат-пространствах, вовлечённости и демографические данные из набора API и представляет их в веб-интерфейсе.
Стек: TypeScript, Next.js и React. Бэкенд читает и объединяет данные из API. Приложение существует давно, но для этих экспериментов я пересобрала его с нуля с помощью ИИ. Гайдов для ИИ о качестве кода и поддерживаемости почти нет — хотелось понять, насколько хорошо агент справляется, опираясь только на обратную связь от сенсоров.
Обзор всех используемых сенсоров
Сенсоры можно запускать в разных точках пути к продакшену: во время сессии кодирования, в пайплайне, по расписанию и в продакшене.
Во время сессии кодирования — сенсоры с непрерывной быстрой обратной связью:
- Проверка типов (вычислительный)
- ESLint (вычислительный)
- Semgrep — SAST-инструмент, предписанный внутренней командой AppSec (вычислительный)
dependency-cruiser— структурные правила для внутренних модульных зависимостей (вычислительный)- Результаты тест-сьюта с покрытием (вычислительный, хотя тест-сьют генерируется ИИ — то есть создаётся инференциально)
- Инкрементальное мутационное тестирование (вычислительный)
- GitLeaks в pre-commit хуке — тоже считаю сенсором, потому что даёт обратную связь, когда агент пытается сделать коммит (вычислительный)
После интеграции — пайплайн. Те же вычислительные сенсоры запускаются в CI. Внутрисессионные сенсоры дают агенту раннюю обратную связь, CI подтверждает результат на чистой инфраструктуре и после интеграции.
Периодически — сенсоры с медленным циклом для обнаружения накапливающегося дрейфа:
- Ревью безопасности: промпт из чеклиста AppSec для внутренних приложений (инференциальный)
- Ревью обработки данных: промпт описывает вещи вроде «имена пользователей не должны отправляться на фронтенд» (инференциальный)
- Отчёт о свежести зависимостей: скрипт собирает возраст и активность библиотек, ИИ создаёт отчёт с рекомендациями (вычислительный + инференциальный)
- Ревью модульности и связанности (вычислительный + инференциальный)
Базовые харнесы и модели
На протяжении всей работы я использовала смесь Cursor, Claude Code и OpenCode (в порядке убывания частоты). Основной моделью обычно был Claude Sonnet, для планирования и анализа — Claude Opus, для задач реализации — часто Cursor composer-2.
Статический анализ: базовый линтинг
Начнём с опыта использования ESLint в этом приложении. Базовые линтеры вроде ESLint в основном нацелены на риски поддерживаемости на уровне отдельных файлов и функций.
Правила под типичные ИИ-ошибки
На мой взгляд, самые очевидные мишени для статического анализа — это типичные сбои ИИ:
- Максимальное количество аргументов у функций
- Длина файла
- Длина функции
- Цикломатическая сложность
Однако всё это не было активировано в ESLint-пресете по умолчанию — пришлось настраивать лимиты вручную. Хочется верить, что инструменты статического анализа постепенно предложат лучшие пресеты для работы с ИИ. Небольшое исследование показало, что люди уже начали публиковать ESLint-плагины с наборами правил, специально нацеленными на известные сбои агентов, — например, Factory с правилами о наличии тест-файлов или структурированного логирования.
Руководство для самокоррекции
Сенсор призван давать агенту обратную связь, чтобы тот мог самостоятельно исправиться. В идеале — с дополнительным контекстом для этого исправления. Своего рода хорошая инъекция промпта. Для этого с помощью ИИ я создала кастомный ESLint-форматтер, переопределяющий некоторые стандартные сообщения.
Вот пример руководства для предупреждения no-explicit-any:
Управление предупреждениями: теперь реально?
Статический анализ существует давно, но команды нередко применяли его непоследовательно, даже если инструмент был настроен. Одна из причин — накладные расходы на управление. Эффективное использование требует «чистого дома»: иначе метрики превращаются в шум. Особенно сложны предупреждения вроде no-explicit-any: не всегда нужно их исправлять — это ситуативно. А подавлять их по одному всегда казалось муторным.
С агентами кодирования, возможно, появился шанс получить такую чистую базу. В приведённом тексте руководства агенту предложено принять решение и при необходимости подавить предупреждение прямо в коде — что сохраняет подавления управляемыми, видимыми и доступными для ревью.
Для пороговых значений — максимальное число строк, допустимая цикломатическая сложность — агенту сообщалось, что он может немного увеличить порог, если считает рефакторинг излишним или невозможным. Это не подавляет правило навсегда, а лишь поднимает планку, так что правило сработает снова при дальнейшем ухудшении. Ограничения сохраняются без принудительного бинарного выбора «подавить или исправить».
Наблюдения
- Изучение исключений, которые создавал ИИ (подавленные предупреждения, увеличенные пороги), стало отличной отправной точкой для моего код-ревью.
- ИИ часто решал увеличить порог цикломатической сложности, но предлагал хорошие рефакторинги, когда я его дополнительно подталкивала. Это единственная категория, где он так делал — и позже я обнаружила, что не включила для неё руководство по самокоррекции. Индикатор того, что кастомные lint-сообщения действительно имеют значение.
- Иногда правила нужно применять по-разному в разных частях кода. Например,
no-console: на бэкенде я хочу, чтобы ИИ использовал компонент логгера; на фронтенде — чтобы не использовал прямое логирование или применял другой компонент. Это ещё один пример силы руководства по самокоррекции и того, где ИИ помогает с семантическими суждениями. - Наблюдала случаи компромисса между правилами:
max-linesиmax-lines-per-function. ИИ делал полезные рефакторинги, разбивая код на меньшие функции и компоненты. Однако в React-фронтенде возникла тревожная тенденция: компоненты с огромным количеством свойств, через которые значения передаются по нарастающей цепочке всё более мелких компонентов.
Главные выводы
В целом я была приятно удивлена тем, насколько многое можно охватить статическим анализом. Мне приходилось напоминать себе, почему он был недостаточно используем раньше, и что изменилось: соотношение затрат и выгод. Затраты снижаются, потому что создавать кастомные скрипты и правила с ИИ стало значительно дешевле. Выгоды тоже выросли: результаты анализа помогают быстро оценить множество гигиенических факторов, которые меньше проявляются при написании кода человеком, — и убрать типичные ошибки ИИ с пути.
Вместе с тем возникает опасение: может ли это создать ложное ощущение безопасности и иллюзию качества? Линтеры имеют ограничения, и использовать их как упрощённый индикатор качества рискованно. Есть много семантических аспектов качества, которые статический анализ не улавливает — остаётся выяснить, сможет ли ИИ адекватно заполнить этот пробел в партнёрстве с инструментами.
Статический анализ: правила зависимостей
Базовый линтинг в основном фокусируется на качестве и сложности внутри файла или функции. Следующим шагом стало изучение сенсоров, дающих обратную связь о проблемах поддерживаемости, пересекающих границы файлов и модулей. Инструменты в этой области исторически используются ещё реже, чем базовый линтинг.
Для изучения потенциала сенсоров модульности я исследовала три области:
- Правила зависимостей (детерминированный подход)
- Анализ связанности (детерминированный + инференциальный)
- Ревью модульности (инференциальный)
Начнём с правил зависимостей. Примерно на середине реализации я вместе с агентом выработала слоистую модульную структуру для приложения и попросила его написать правила dependency-cruiser для её соблюдения.
Одно из правил, например, обеспечивает, чтобы код в папке clients никогда не импортировал ничего из папки services:
Как и с сообщениями ESLint, сообщения об ошибках я расширила до руководств по самокоррекции, кратко пересказывающих концепцию слоёв:
Наблюдения
- Без ИИ настроить эти правила быстро было бы нереально: у инструмента высокий порог входа, и ИИ почти полностью поглотил эти затраты.
- Агент нарушал правила несколько раз после их введения, но каждый раз самостоятельно исправлялся на основе обратной связи от
dependency-cruiser— инструмент действительно помог удерживать концепции папок. - Тот же подход применила для структуры React-хуков на фронтенде.
- Пришлось разобраться, как ловить ситуации, когда ИИ создаёт новые папки за пределами заданной структуры — с правилом, требующим, чтобы каждый новый файл находился в предопределённой структуре папок.
Главные выводы
В момент введения этих правил структурирование кода по папкам уже стало слегка хаотичным. Правила помогли агенту навести порядок и в дальнейшем поддерживать слои. Это оказалось хорошей заменой описанию структуры кода в markdown-файлах. Однако инструменты этого типа ограничены тем, что выражается через импорты, имена файлов и структуру папок.
Статический анализ: данные о связанности
Следующим шагом стало извлечение типичных метрик связанности из кодовой базы — количества входящих и исходящих импортов и вызовов на файл.
Я не использовала готовые инструменты: попросила агента написать приложение, создающее такие метрики с помощью TypeScript-компилятора. Добавила два интерфейса: веб-интерфейс с несколькими визуализациями для моего потребления и CLI для предоставления метрик агенту.
Для людей
Большинство визуализаций — это известные концепции вроде матрицы структуры зависимостей (DSM). Они оказались утомительными в интерпретации. Мне кажется, детальные данные о связанности требуют много контекста и опыта для интерпретации и соотнесения с практиками высокого уровня. Поэтому у меня ощущение, что инструменты этого типа всё равно не сильно снизят когнитивную нагрузку человека при проверке кодовых баз, изменённых ИИ.
Для ИИ
Я дала агенту доступ к кастомному CLI (coupling-analyser) и попросила создать отчёт на основе данных с предложениями по улучшению. Важно: агенту не давалось чёткого определения «хорошей» и «плохой» модульности — интерпретация была делегирована модели.
Создай markdown-отчёт о модульности и качестве связанности для целевой TypeScript-кодовой базы, основанный на реальных данных CLI изnpx coupling-analyser, а не на догадках от статического просмотра кода.
Наблюдения
Результаты оказались довольно слабыми (использовался Claude Opus 4.7):
- Как одну из главных проблем ИИ назвал фабрику, инициализирующую все необходимые компоненты — хотя она была намеренно введена как лёгкий фреймворк внедрения зависимостей.
- Другой «проблемой» оказалась общая схема (zod) между фронтендом и бэкендом, объявленная ИИ «God-модулем». Это распространённый паттерн для создания явного контракта между клиентом и сервером — не проблема, когда они эволюционируют вместе.
- Когда легитимные паттерны выглядят как хабы с высокой связанностью, нужен способ исключать их из будущих анализов — иначе это просто шум.
- Одна интересная находка: файл
index.tsв папке domain без разбора экспортировал все файлы из./domainи импортировался множеством мест. Распространённый паттерн для создания явных контрактов слоя, но у него есть плюсы и минусы — стоит разобраться, уместен ли он.
Главные выводы
Примеры выше показывают: то, что хорошо, а что плохо, не имеет чёткого определения — всё зависит от того, что уместно. А уместность связанности зависит от контекста, а не только от графа вызовов и импортов. Данные о связанности, по-видимому, не полезны ИИ сами по себе.
Более практичное применение — приоритизация рисков при код-ревью. Зная радиус влияния изменённых файлов, можно уделять больше внимания, если меняется файл с 10+ зависимыми. Агент ревью мог бы использовать эти данные для расстановки приоритетов.
Статический анализ: ИИ-ревью модульности
Слабые результаты от данных о связанности могли быть вызваны несколькими причинами: нечёткий промпт, сами данные мало полезны ИИ, или только данные о связанности слишком поверхностны. Поэтому последним шагом стало погружение в полностью инференциальный подход — использование «Modularity Skills» Влада Хононова для анализа дизайна кодовой базы и поиска проблем модульности.
Это оказалось очень продуктивным. ИИ нашёл множество интересных указателей для рефакторингов, которые явно снизили бы риски будущих изменений. Второй запуск скиллов с доступом к coupling CLI в основном подтвердил уже найденное, но не дал дополнительных находок — инструмент CLI при этом пропустил много вещей, которые нашёл ИИ. Также стоит отметить: второй запуск (без контекста первого) выявил ещё одну проблему, которую первый не обнаружил. Полезное напоминание: когда это важно, стоит запускать LLM-анализ несколько раз для более полной картины.
Наблюдения
Основные находки (Claude Opus 4.7, как и для анализа связанности):
- Дублирование кода роутов — три бэкенд-эндпойнта имели почти идентичные реализации. При желании изменить общий принцип API (например, добавить request ID или изменить подход к логированию ошибок) потребовалось бы менять несколько файлов. Агенты обычно не начинают рефакторинг без явного указания, копируя паттерн третий-четвёртый раз.
- Несоответствие в вызовах бэкенда — семантическое дублирование. Три страницы приложения вызывают бэкенд с одним набором параметров. Две страницы использовали одинаковый хук и подход, но при создании третьей ИИ отклонился и переосуществил аналогичное поведение по-своему. Это ведёт к несоответствиям в обработке ошибок и необходимости менять несколько файлов при изменении API.
- Неэффективная передача ключевых аргументов — параметры chat space ID и диапазон дат передаются через 40+ файлов. Ревью подтвердило проблему: «Issue: Request parameters repeated at every level». ИИ уже частично решил её, но никогда не довёл до конца, создав непоследовательный беспорядок.
- Ответственности не на своём месте — код аутентификации оказался внутри фабрики, отвечающей только за сборку модулей. Неожиданное место создаёт риск пропустить это при добавлении новых роутов.
- Лучшая интерпретация «хабов» с высоким импортом — в отличие от анализа данных связанности, ИИ при полном чтении кода замечал, что хабы с высокими показателями оправданы в контексте приложения. Это связано либо с качеством промптинга, либо с тем, что анализ читал сам код.
Главные выводы
- Парсеры зависимостей вроде
dependency-cruiserэффективны как живые сенсоры для базовых структур папок и направлений зависимостей, но имеют ограничения. - ИИ-ревью модульности хорошо работает как «сборщик мусора» с мощными промптами. Включение данных о связанности, похоже, не давало большой разницы.
- ИИ-ревью, запущенное после построения большей части кодовой базы без подобных проверок, выявило серьёзные и вполне обоснованные находки, которые увеличили бы риски в будущем. Без экспертизы в области связанности и этих дополнительных ИИ-ревью агент определённо накапливал непреднамеренный технический долг.
В целом, дизайн кодовой базы и модульность — это проблема, где вычислительные сенсоры в одиночку мало чем помогут. Нужен ИИ для семантической интерпретации и учёта компромиссов.
Часто задаваемые вопросы
Что такое «сенсоры» в контексте ИИ-агентов?
Сенсоры — инструменты, дающие агенту автоматическую обратную связь о состоянии кодовой базы: статические анализаторы, тест-сьюты, инструменты покрытия. Они работают непрерывно и позволяют агенту самостоятельно исправлять ошибки до того, как код попадёт на человеческую проверку. Böckeler делит их на вычислительные (детерминированные) и инференциальные (ИИ-ревью).
Почему ESLint важнее при работе с ИИ, чем при написании кода людьми?
Агенты склонны к типичным ошибкам: слишком длинные функции и файлы, чрезмерная сложность, большое число аргументов. ESLint с настроенными лимитами фиксирует эти ошибки сразу, а кастомные сообщения объясняют агенту, как именно исправить проблему. Раньше настройка ESLint требовала больших затрат — теперь ИИ поглощает эти расходы.
Насколько эффективен dependency-cruiser для удержания агентов в архитектурных рамках?
Достаточно эффективен: агент нарушал правила несколько раз после их введения, но каждый раз самостоятельно исправлялся на основе обратной связи от dependency-cruiser. Правила оказались хорошей альтернативой описанию структуры в markdown-файлах — более надёжной, потому что нарушение вызывает ошибку, а не просто рекомендацию.
Зачем запускать ИИ-ревью модульности несколько раз?
Каждый запуск может выявить разные проблемы: LLM не детерминированы. В экспериментах Böckeler второй запуск нашёл проблему, которую первый пропустил. Когда результаты важны — стоит запускать ИИ-анализ несколько раз для более полной картины.
Можно ли полностью автоматизировать контроль качества ИИ-кода?
Нет. Вычислительные сенсоры улавливают синтаксические и структурные проблемы, но не понимают семантику. Дизайн кодовой базы и модульность требуют ИИ для семантической интерпретации и учёта компромиссов. Автоматика — необходимое условие, но недостаточное: нужны и человеческое ревью, и периодические инференциальные ревью.
Выводы
Böckeler продолжает эксперименты: следующая часть статьи будет посвящена роли регрессионного тестирования как сенсора, а также опыту с покрытием и мутационным тестированием ИИ-сгенерированных тест-сьютов.
Главный вывод этих экспериментов: вычислительные сенсоры снижают порог входа в статический анализ — ИИ берёт на себя настройку инструментов с высоким порогом входа (dependency-cruiser, кастомные форматтеры). Но ни один детерминированный инструмент в одиночку не даст полной картины поддерживаемости. Необходима комбинация: линтинг — правила зависимостей — периодическое ИИ-ревью модульности.
Оригинал статьи опубликован 19–20 мая 2026 года на martinfowler.com. Следующая часть выйдет в том же месте.