Сенсоры поддерживаемости для агентов кодирования

Как 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:
We want things to be typed to make it easier to avoid errors, especially for key concepts.
But we also want to avoid cluttering our codebase with unnecessary types. Make a judgment
call about this. If you choose to not introduce a type, suppress it with:
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- (give reason why)
		

Управление предупреждениями: теперь реально?

Статический анализ существует давно, но команды нередко применяли его непоследовательно, даже если инструмент был настроен. Одна из причин — накладные расходы на управление. Эффективное использование требует «чистого дома»: иначе метрики превращаются в шум. Особенно сложны предупреждения вроде no-explicit-any: не всегда нужно их исправлять — это ситуативно. А подавлять их по одному всегда казалось муторным.

С агентами кодирования, возможно, появился шанс получить такую чистую базу. В приведённом тексте руководства агенту предложено принять решение и при необходимости подавить предупреждение прямо в коде — что сохраняет подавления управляемыми, видимыми и доступными для ревью.

Для пороговых значений — максимальное число строк, допустимая цикломатическая сложность — агенту сообщалось, что он может немного увеличить порог, если считает рефакторинг излишним или невозможным. Это не подавляет правило навсегда, а лишь поднимает планку, так что правило сработает снова при дальнейшем ухудшении. Ограничения сохраняются без принудительного бинарного выбора «подавить или исправить».

Наблюдения

  • Изучение исключений, которые создавал ИИ (подавленные предупреждения, увеличенные пороги), стало отличной отправной точкой для моего код-ревью.
  • ИИ часто решал увеличить порог цикломатической сложности, но предлагал хорошие рефакторинги, когда я его дополнительно подталкивала. Это единственная категория, где он так делал — и позже я обнаружила, что не включила для неё руководство по самокоррекции. Индикатор того, что кастомные lint-сообщения действительно имеют значение.
  • Иногда правила нужно применять по-разному в разных частях кода. Например, no-console: на бэкенде я хочу, чтобы ИИ использовал компонент логгера; на фронтенде — чтобы не использовал прямое логирование или применял другой компонент. Это ещё один пример силы руководства по самокоррекции и того, где ИИ помогает с семантическими суждениями.
  • Наблюдала случаи компромисса между правилами: max-lines и max-lines-per-function. ИИ делал полезные рефакторинги, разбивая код на меньшие функции и компоненты. Однако в React-фронтенде возникла тревожная тенденция: компоненты с огромным количеством свойств, через которые значения передаются по нарастающей цепочке всё более мелких компонентов.

Главные выводы

В целом я была приятно удивлена тем, насколько многое можно охватить статическим анализом. Мне приходилось напоминать себе, почему он был недостаточно используем раньше, и что изменилось: соотношение затрат и выгод. Затраты снижаются, потому что создавать кастомные скрипты и правила с ИИ стало значительно дешевле. Выгоды тоже выросли: результаты анализа помогают быстро оценить множество гигиенических факторов, которые меньше проявляются при написании кода человеком, — и убрать типичные ошибки ИИ с пути.

Вместе с тем возникает опасение: может ли это создать ложное ощущение безопасности и иллюзию качества? Линтеры имеют ограничения, и использовать их как упрощённый индикатор качества рискованно. Есть много семантических аспектов качества, которые статический анализ не улавливает — остаётся выяснить, сможет ли ИИ адекватно заполнить этот пробел в партнёрстве с инструментами.

Статический анализ: правила зависимостей

Базовый линтинг в основном фокусируется на качестве и сложности внутри файла или функции. Следующим шагом стало изучение сенсоров, дающих обратную связь о проблемах поддерживаемости, пересекающих границы файлов и модулей. Инструменты в этой области исторически используются ещё реже, чем базовый линтинг.

Для изучения потенциала сенсоров модульности я исследовала три области:

  • Правила зависимостей (детерминированный подход)
  • Анализ связанности (детерминированный + инференциальный)
  • Ревью модульности (инференциальный)

Начнём с правил зависимостей. Примерно на середине реализации я вместе с агентом выработала слоистую модульную структуру для приложения и попросила его написать правила dependency-cruiser для её соблюдения.

Одно из правил, например, обеспечивает, чтобы код в папке clients никогда не импортировал ничего из папки services:

			{
  name: "clients-no-services",
  comment:
    "API clients must not depend on the orchestration layer above them. " + LAYERS,
  severity: "error",
  from: { path: "^server/clients/", pathNot: "/__tests__/" },
  to: { path: "^server/services/" },
},
		

Как и с сообщениями ESLint, сообщения об ошибках я расширила до руководств по самокоррекции, кратко пересказывающих концепцию слоёв:

			ERROR  clients-no-services
  API clients must not depend on the orchestration layer above them.
  [Layers: routes -> services -> clients + domain; Services orchestrate: fetch data via clients, compute via domain -- no I/O, no SDKs, no knowledge of data fetching.]
		

Наблюдения

  • Без ИИ настроить эти правила быстро было бы нереально: у инструмента высокий порог входа, и ИИ почти полностью поглотил эти затраты.
  • Агент нарушал правила несколько раз после их введения, но каждый раз самостоятельно исправлялся на основе обратной связи от 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 эффективны как живые сенсоры для базовых структур папок и направлений зависимостей, но имеют ограничения.
  • ИИ-ревью модульности хорошо работает как «сборщик мусора» с мощными промптами. Включение данных о связанности, похоже, не давало большой разницы.
  • ИИ-ревью, запущенное после построения большей части кодовой базы без подобных проверок, выявило серьёзные и вполне обоснованные находки, которые увеличили бы риски в будущем. Без экспертизы в области связанности и этих дополнительных ИИ-ревью агент определённо накапливал непреднамеренный технический долг.
В целом, дизайн кодовой базы и модульность — это проблема, где вычислительные сенсоры в одиночку мало чем помогут. Нужен ИИ для семантической интерпретации и учёта компромиссов.
Birgitta BöckelerDistinguished Engineer, Thoughtworks
Часто задаваемые вопросы
1
Что такое «сенсоры» в контексте ИИ-агентов?

Сенсоры — инструменты, дающие агенту автоматическую обратную связь о состоянии кодовой базы: статические анализаторы, тест-сьюты, инструменты покрытия. Они работают непрерывно и позволяют агенту самостоятельно исправлять ошибки до того, как код попадёт на человеческую проверку. Böckeler делит их на вычислительные (детерминированные) и инференциальные (ИИ-ревью).

2
Почему ESLint важнее при работе с ИИ, чем при написании кода людьми?

Агенты склонны к типичным ошибкам: слишком длинные функции и файлы, чрезмерная сложность, большое число аргументов. ESLint с настроенными лимитами фиксирует эти ошибки сразу, а кастомные сообщения объясняют агенту, как именно исправить проблему. Раньше настройка ESLint требовала больших затрат — теперь ИИ поглощает эти расходы.

3
Насколько эффективен dependency-cruiser для удержания агентов в архитектурных рамках?

Достаточно эффективен: агент нарушал правила несколько раз после их введения, но каждый раз самостоятельно исправлялся на основе обратной связи от dependency-cruiser. Правила оказались хорошей альтернативой описанию структуры в markdown-файлах — более надёжной, потому что нарушение вызывает ошибку, а не просто рекомендацию.

4
Зачем запускать ИИ-ревью модульности несколько раз?

Каждый запуск может выявить разные проблемы: LLM не детерминированы. В экспериментах Böckeler второй запуск нашёл проблему, которую первый пропустил. Когда результаты важны — стоит запускать ИИ-анализ несколько раз для более полной картины.

5
Можно ли полностью автоматизировать контроль качества ИИ-кода?

Нет. Вычислительные сенсоры улавливают синтаксические и структурные проблемы, но не понимают семантику. Дизайн кодовой базы и модульность требуют ИИ для семантической интерпретации и учёта компромиссов. Автоматика — необходимое условие, но недостаточное: нужны и человеческое ревью, и периодические инференциальные ревью.

Выводы

Böckeler продолжает эксперименты: следующая часть статьи будет посвящена роли регрессионного тестирования как сенсора, а также опыту с покрытием и мутационным тестированием ИИ-сгенерированных тест-сьютов.

Главный вывод этих экспериментов: вычислительные сенсоры снижают порог входа в статический анализ — ИИ берёт на себя настройку инструментов с высоким порогом входа (dependency-cruiser, кастомные форматтеры). Но ни один детерминированный инструмент в одиночку не даст полной картины поддерживаемости. Необходима комбинация: линтинг — правила зависимостей — периодическое ИИ-ревью модульности.

Оригинал статьи опубликован 19–20 мая 2026 года на martinfowler.com. Следующая часть выйдет в том же месте.