TL;DR
ИИ-агент заново собрал приложение на TypeScript, Next.js и React, а качество кода проверялось набором сенсоров: линтерами, правилами зависимостей, AI-ревью модульности, покрытием тестами и мутационным тестированием.
Статический анализ хорошо ловил локальные проблемы и помогал агенту самокорректироваться. Но архитектура, связанность и распределение ответственностей требовали уже не метрик, а осмысленного AI-ревью с контекстом.
Главный сюрприз оказался в тестах: высокое покрытие выглядело убедительно, но мутационное тестирование показало, что часть тестов просто выполняет код, почти не защищая его от регрессий. Статья показывает, где автоматизация действительно повышает доверие к коду, а где создаёт опасную иллюзию качества.
Содержание
Приложение
Обзор всех используемых сенсоров
Базовые обвязки и модели
Статический анализ кода: базовый линтинг
Правила для типичных слабых мест ИИ
Подсказки для самокоррекции
Управление предупреждениями — теперь это реалистичнее?
Наблюдения
Главные выводы
Статический анализ кода: правила зависимостей
Наблюдения
Главные выводы
Статический анализ кода: данные о связанности
Для людей
Для ИИ
Наблюдения
Главные выводы
Статический анализ кода: AI‑ревью модульности
Наблюдения
Главные выводы
Набор тестов как сенсор регрессий
Мутационное тестирование
Наблюдения
Главные выводы
Выводы и открытые вопросы
В кодовых базах обычно есть несколько измерений, которых мы хотим достичь и за которыми следим: функциональная корректность — всё работает как задумано, архитектурная пригодность — достаточно быстро, безопасно и удобно, и сопровождаемость (maintainability).
Здесь я определяю сопровождаемость как возможность со временем менять кодовую базу легко и с низким риском. Это ещё называют «внутренним качеством».
То есть мне важно не только быстро вносить изменения сегодня, но и сохранять такую возможность в будущем. И я не хочу каждый раз переживать, что при очередном изменении — моём или сделанном ИИ — я нечаянно внесу баги или ухудшу архитектурную пригодность.
Первые трещины в сопровождаемости ИИ‑сгенерированной кодовой базы я обычно замечаю по тому, что для маленькой правки приходится менять всё больше файлов. Или когда изменения начинают ломать то, что раньше работало.
Как обычно, мой взгляд в первую очередь связан с написанием кода для приложений: цифровых продуктов и корпоративного ПО. Профили риска и цели использования ИИ‑агентов для программирования зависят от того, какое именно ПО мы строим.
Проблемы внутреннего качества влияют на ИИ‑агентов примерно так же, как и на обычных разработчиков. Агент, работающий в запутанной кодовой базе, может искать существующую реализацию не там, где нужно, создавать несогласованности, потому что не заметил дубликат, или быть вынужденным загружать больше контекста, чем на самом деле требует задача.
В этой статье я описываю свои эксперименты с разными сенсорами, которые помогают нам и ИИ размышлять о сопровождаемости кодовой базы, а также рассказываю, что из этого вынесла.
Приложение
Я работаю над внутренним аналитическим дашбордом для комьюнити‑менеджеров. Он читает данные об активности в чат‑пространствах, вовлечённости и демографии из нескольких API и показывает их в веб‑фронтенде.

Стек — TypeScript, NextJS и React. Бэкенд читает данные из API и объединяет их. Приложение существует уже какое‑то время, но ради этих экспериментов я перестроила его с нуля с помощью ИИ.
Гайдов для ИИ о качестве кода и сопровождаемости почти нет, например markdown‑файлов. Мне хотелось посмотреть, насколько хорошо он справится, если будет опираться только на обратную связь от сенсоров.
Обзор всех используемых сенсоров

Это обзор сенсоров, которые я настроила на всём пути до продакшена.
Во время сессии программирования
Сенсоры, которые непрерывно работают рядом с агентом и дают быструю обратную связь.
Проверка типов (вычислительный сенсор)
ESLint (вычислительный сенсор)
Semgrep, SAST‑инструмент, предписанный нашей внутренней AppSec‑командой (вычислительный сенсор)
dependency‑cruiser, который запускает структурные правила для проверки внутренних зависимостей между модулями (вычислительный сенсор)
Результаты набора тестов, включая покрытие кода тестами (вычислительный сенсор, хотя сам набор тестов сгенерирован ИИ, а значит, создан интерпретирующим способом)
Инкрементальное мутационное тестирование (вычислительный сенсор)
GitLeaks запускается как часть pre‑commit‑хука. Я тоже считаю его сенсором, потому что он даёт агенту обратную связь, когда тот пытается сделать коммит (вычислительный сенсор)
После интеграции — пайплайн
Те же вычислительные сенсоры снова запускаются в CI. Сенсоры, работающие во время сессии, дают агенту раннюю обратную связь в процессе разработки. А CI‑пайплайн подтверждает результат на чистой инфраструктуре и уже после интеграции.
Периодически
Сенсоры, которые запускаются реже и помогают обнаруживать деградацию со временем, а не ошибки, возникающие прямо в моменте.
Ревью безопасности: промпт, составленный на основе нашего AppSec‑чеклиста для внутренних приложений (интерпретирующий сенсор)
Ревью обработки данных: промпт описывает требования вроде «имена пользователей никогда не должны отправляться во фронтенд» (интерпретирующий сенсор)
Отчёт о свежести зависимостей: сначала запускается скрипт, который собирает возраст и активность библиотечных зависимостей, а затем ИИ создаёт отчёт с рекомендациями о возможных обновлениях, устареваниях и так далее (вычислительный и интерпретирующий сенсор)
Ревью модульности и связности (вычислительный и интерпретирующий сенсор)
С контекстом разобрались, теперь перейдём к первой категории сенсоров.
Базовые обвязки и модели
В ходе работы над приложением я использовала Cursor, Claude Code и OpenCode — именно в таком порядке по частоте. Моделью по умолчанию у меня обычно был Claude Sonnet.
Для некоторых задач планирования и анализа я использовала Claude Opus, а для реализации часто брала модель composer-2 от Cursor.
Статический анализ кода: базовый линтинг
Начну с того, что я поняла, используя ESLint в этом приложении. Инструменты базового линтинга вроде ESLint в основном нацелены на риски для сопровождаемости на уровне отдельных файлов и функций.
Правила для типичных слабых мест ИИ
По моему опыту, самые очевидные режимы отказа ИИ, которые проще всего поймать статическим анализом кода, — это:
максимальное число аргументов у функций;
длина файла;
длина функции;
цикломатическая сложность.
Но эти правила даже не были включены в дефолтный пресет ESLint — мне пришлось сначала настроить максимальные значения вручную. Надеюсь, инструменты статического анализа со временем начнут предлагать более подходящие пресеты для работы с ИИ.
Небольшое исследование показывает, что люди уже публикуют ESLint‑плагины с наборами правил, специально нацеленными на известные режимы отказа агентов: например, плагин от Factory с правилами вроде обязательного наличия тестовых файлов или структурированного логирования.
Подсказки для самокоррекции
Сенсор нужен для того, чтобы давать агенту обратную связь и позволять ему самокорректироваться. В идеале вместе с этой обратной связью мы хотим дать агенту дополнительный контекст для самокоррекции — такой полезный вариант промпт‑инъекций. Для этого я собрала кастомный форматтер для ESLint, который переопределяет некоторые стандартные сообщения. Разумеется, с помощью ИИ.
Вот пример моей подсказки для предупреждения no‑explicit‑any.
Мы хотим, чтобы сущности были типизированы: так проще избегать ошибок, особенно для ключевых понятий.
Но мы также не хотим захламлять кодовую базу ненужными типами. Прими решение по ситуации. Если решишь не вводить тип, подави предупреждение так:
// eslint‑disable‑next‑line @typescript‑eslint/no‑explicit‑any — (укажи причину).
Управление предупреждениями — теперь это реалистичнее?
Статический анализ кода существует уже давно, и всё же команды часто не используют его последовательно, даже если он у них настроен. Одна из причин — управленческие накладные расходы. Чтобы такой анализ был действительно полезен, команде нужно поддерживать «чистый дом», иначе метрики быстро превращаются в шум. Особенно сложны предупреждения вроде примера с no‑explicit‑any: их не всегда нужно исправлять, многое зависит от контекста. А подавлять их по одному всегда казалось утомительным и выглядело как лишний шум в коде.
С агентами для программирования у нас, возможно, появился шанс получить такой чистый базовый уровень. В тексте подсказки выше агенту предлагают принять решение по ситуации и разрешают подавить предупреждение в коде. Благодаря этому подавления остаются управляемыми, видимыми и доступными для ревью.
Для порогов — например, максимального числа строк или максимально допустимой цикломатической сложности — я в сообщении линтера говорила агенту, что он может слегка увеличить пороги, если считает, что в конкретном случае рефакторинг не нужен или невозможен.
Это не отключает порог навсегда, а только повышает его, так что правило снова сработает, если ситуация в будущем станет ещё хуже. Ограничения сохраняются, но мы не загоняем агента в бинарный выбор «подавить или подчиниться».
Наблюдения
Просмотр исключений, которые создал ИИ — подавленных предупреждений и повышенных порогов, — оказался хорошей точкой входа для ревью кода.
ИИ часто решал повысить порог цикломатической сложности, но предлагал хорошие варианты рефакторинга, когда я подталкивала его чуть дальше. Это была единственная категория, где он так поступал. Позже я обнаружила, что именно для неё у меня не было подсказки для самокоррекции, а значит, не было явной инструкции, что повышение порога должно быть крайней мерой. Это хороший признак того, что кастомные сообщения линтера действительно могут сильно влиять на результат.
Иногда я хочу по‑разному применять правила в разных частях кода. Возьмём no‑console, которое ругает ИИ за использование console.log. На бэкенде я хочу, чтобы вместо этого он использовал компонент логирования. На фронтенде я, возможно, вообще не хочу прямого логирования или как минимум хочу использовать другой компонент для логов. Это ещё один пример силы подсказок для самокоррекции и ситуации, где ИИ может помочь с семантической оценкой и управлением предупреждениями анализа.
Я внимательно искала примеры компромиссов между правилами. Пока я увидела только один — он возник из‑за правил 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, который может отдавать эти метрики агенту для программирования.

Для людей
Большинство этих визуализаций основаны на хорошо известных концепциях, например на матрице структуры зависимостей (Dependency Structure Matrix, DSM). Мне было утомительно их интерпретировать. И хотя они были созданы с помощью вайб‑кодинга и почти наверняка могли бы быть лучше, думаю, дело было скорее в природе самих данных. Это очень детализированные данные: чтобы их интерпретировать и связать с более высокоуровневыми хорошими практиками, нужно много контекста и опыта.
Поэтому у меня сложилось ощущение, что такие инструменты всё ещё не особенно помогают снизить когнитивную нагрузку человека при ревью кодовых баз, изменённых ИИ.
Для ИИ
Я дала агенту доступ к этому кастомному CLI (coupling‑analyser) и попросила подготовить отчёт на основе данных, включая предложения по улучшению критичных проблем.
Вот фрагмент того промпта. Я привожу его в основном затем, чтобы показать: на самом деле я почти не объясняла агенту, как выглядит хорошая или плохая модульность. Я в основном делегировала модели интерпретацию того, что считать хорошим и плохим:
Фрагмент промпта
Подготовь markdown‑отчёт о качестве модульности и связанности целевой кодовой базы на TypeScript. Отчёт должен опираться на фактический вывод CLI из npx coupling‑analyser, а не на догадки после простого статического просмотра кода.
Собери доказательства:
Запусти CLI.
Выполни CLI и сохрани stdout. Используй подкоманды report — комбинируй их так, как полезно для вопроса: …
Напиши markdown‑отчёт
Используй понятные заголовки. По возможности указывай конкретные ID модулей / пути и числа, процитированные или пересказанные из вывода CLI.
Предлагаемые разделы:
Контекст — что анализировалось/
Краткое резюме — 2–5 пунктов: общее состояние модульности, топ-1–3 системные проблемы.
Выводы инструмента — суммируй проблемные участки, основные риски, заметные циклические зависимости или взаимные зависимости, а также поведенческие особенности, которые показывает CLI.
Интерпретация через призму модульности — свяжи метрики с дизайном ПО: связанность внутри модуля и разброс изменений, стабильность и направление зависимостей, интуиция входящей/исходящей связанности, влияние циклов.
Глубокий разбор каждой высокой и критической проблемы.
Что это — модуль/модули, роль в системе, соседние зависимости (из CLI + минимальный просмотр кода, если нужен). Текущие ответственности … Почему это вредит … Варианты дизайна — 2 или больше, где это разумно … Почему новый дизайн лучше — меньше циклов, понятнее направление зависимостей, меньше поверхности, удобнее тестовые швы, лучше соответствие вероятным векторам изменений. Риск будущих изменений — как каждый вариант снижает риск регрессий и удешевляет безопасную эволюцию: конкретные сценарии вроде «добавить X», «заменить Y», «поставлять Z независимо».
Промпт на англ
Produce a markdown report on modularity and coupling quality for the target TypeScript codebase, grounded in actual CLI output from npx coupling‑analyser, not guesswork from static browsing alone.
Gather evidence (run the CLI)
Execute the CLI and capture stdout. Use the report subcommands‑combine as useful for the question: …
Write the markdown report
Use clear headings. Prefer concrete module IDs / paths and numbers quoted or paraphrased from CLI output.
Suggested sections:
Context — What was analyzed
Executive summary — 2–5 bullets: overall modularity posture, top 1–3 systemic issues.
Findings from the tool — Summarize hotspots, top risks, notable cycles or mutual dependencies, and behavioural highlights as reported by the CLI.
Interpretation (modularity lens) — Tie metrics to software design: cohesion vs. spread of change, stability vs. dependency direction, fan‑in/fan‑out intuition, cycle impact.
Deep dives for each high and critical issue
What it is — Module(s), role in the system, dependency neighbours (from CLI + minimal code peek if needed).
Responsibilities today …
Why it hurts …
Design options (2+ where reasonable) …
Why the new design is better — Fewer cycles, clearer dependency direction, smaller surfaces, test seams, align with likely change vectors.
Future change risk — How each option reduces regression risk and makes safe evolution cheaper (concrete scenarios: “adding X”, “swapping Y”, “shipping Z independently”)
Такой LLM‑анализ действительно указал мне на те же проблемные участки связанности, которые я нашла бы, просматривая визуальные диаграммы, — просто в более удобоваримом формате.
А просьба к LLM опираться в анализе на результаты детерминированного инструмента дала мне больше уверенности и, вероятно, потребовала меньше времени и токенов, чем если бы агент сам сканировал кодовую базу в поисках проблем связанности.
Наблюдения
То, что LLM нашла на основе этих данных, оказалось довольно бледным. Я использовала Claude Opus 4.7.
Она назвала одной из главных проблем фабрику, которая инициализирует все необходимые компоненты. Но я специально ввела эту фабрику как компонент, работающий примерно как лёгковесный фреймворк внедрения зависимостей.
Другая проблема, которую LLM увидела, — общая схема zod между фронтендом и бэкендом. Модель объявила её «божественным модулем». Но это довольно распространённый паттерн для создания явного контракта между бэкендом и фронтендом. И он не так проблемен, когда бэкенд и фронтенд в любом случае развиваются вместе или даже живут в одном репозитории, как у меня.
Когда легитимные паттерны выглядят как хабы с высокой связанностью, нужен способ подавлять их в будущих анализах. Иначе они будут создавать ещё больше шума.
Единственная хоть сколько‑то интересная находка была такой: файл index.ts в папке domain без разбора экспортировал все файлы из./domain, и его импортировали во многих местах. Это тоже распространённый паттерн для создания явного контракта слоя, но у него есть свои плюсы и минусы. Как минимум стоит изучить, подходит ли он именно этой кодовой базе.
Главные выводы
Примеры выше показывают: даже сильнее, чем в случае с базовым линтингом, здесь нет чёткого определения хорошего и плохого. Всё упирается в уместность. А какая связанность уместна, зависит от большого количества контекста, а не только от сырого графа вызовов и импортов в кодовой базе. Поэтому по итогам этого небольшого эксперимента у меня не сложилось впечатления, что такие данные о связанности сами по себе полезны для ИИ.
Более практичное применение этих данных я вижу в приоритизации рисков при ревью кода. Когда я ревьюю изменение, сделанное ИИ, полезно знать радиус влияния изменённых файлов: например, чтобы уделить больше внимания случаю, когда меняется файл с 10+ вызывающими местами. Или ИИ‑агент для ревью мог бы использовать эти данные, чтобы расставить приоритеты и понять, куда тратить токены.
Статический анализ кода: AI‑ревью модульности
У бледных результатов эксперимента с данными о связанности могло быть несколько причин:
мой промпт о том, что именно анализировать, был недостаточно конкретным;
данные о связанности бесполезны для ИИ;
одних данных о связанности слишком мало: они слишком поверхностны и не дают контекста всего кода.
Поэтому в конце я полностью ушла в интерпретирующий подход и использовала «Modularity Skills» Влада Хононова, чтобы проанализировать дизайн кодовой базы и найти проблемы модульности. Это оказалось очень плодотворно. Я получила много интересных указаний на рефакторинги, которые явно снизили бы риск будущих изменений. Затем я запустила эти skills второй раз и дала им доступ к моему CLI для анализа связанности. ИИ в основном нашёл в данных подтверждение своим выводам, но не обнаружил ничего дополнительно.
Напротив, он указал на множество вещей, которых CLI не видел. Ещё стоит отметить, что второй запуск анализа — без контекста первого — поднял ещё одну проблему, которую первый запуск не нашёл. Полезное напоминание: когда вопрос важен, LLM‑анализ часто стоит запускать несколько раз, чтобы получить более полную картину.
Наблюдения
Вот несколько заметных моментов из результатов. Использовалась модель Claude Opus 4.7, та же, что и для анализа связанности.
Дублирование кода маршрутов. У всех трёх моих бэкенд‑эндпоинтов был свой файл маршрута, и реализации этих маршрутов были почти одинаковыми. Поэтому всякий раз, когда я захотела бы изменить общие принципы backend API — например, ввести request ID или поменять подход к обработке ошибок либо логированию, — мне пришлось бы править несколько файлов. Третий эндпоинт я добавила совсем недавно, так что, думаю, вполне нормально, что эта логика ещё не была вынесена в абстракцию. Но по моему опыту, ИИ‑агенты обычно не начинают рефакторинг сами, без явного толчка, даже когда повторяют кусок кода в третий или четвёртый раз. Они вполне довольны copy‑paste.
Несогласованность в вызовах бэкенда. Или, иначе говоря, ещё одна форма семантического дублирования. В приложении есть три страницы, которым нужно вызывать бэкенд с одним и тем же набором параметров: выбранное чат‑пространство и диапазон дат для анализа. Две из этих страниц использовали один и тот же хук и общий подход, но когда ИИ добавил третью страницу, он отклонился от этого решения и заново реализовал похожее поведение по‑своему. Это может, например, привести к несогласованности в обработке ошибок или снова потребовать менять несколько файлов, когда изменятся принципы backend API.
Неэффективная работа с ключевыми аргументами. Как я уже сказала, все страницы в приложении передают на бэкенд ID чат‑пространства и диапазон дат. Я уже замечала, что когда изменила способ, которым пользователь задаёт диапазон дат, ИИ пришлось поменять очень много файлов — больше 40. То есть я уже понимала, что здесь что‑то не так, и анализ это подтвердил: «Проблема: параметры запроса повторяются на каждом уровне». Рекомендация состояла в том, чтобы ввести объект, оборачивающий все эти параметры. В каком‑то смысле ИИ уже это сделал, но так и не довёл использование этого объекта до конца. В результате получилась несогласованная каша.
Ответственности не на своём месте. Ревью нашло немного кода аутентификации внутри фабрики, которая должна была отвечать только за связывание модулей. В ней была реализована подмена данных на mock‑данные, если пользователь не аутентифицирован. Такое неожиданное место создаёт риск: при добавлении новых роутов его легко не заметить.
Более точная интерпретация допустимых «хабов» с большим числом импортов. Помните так называемые «божественные классы», найденные моим предыдущим анализом связанности? Modularity Skills тоже их заметили, но в обоих случаях аккуратно указали, что в контексте этого приложения у них есть назначение. Думаю, причина либо в хорошем промптинге внутри этих skills, либо в том, что этот анализ действительно читал код, тогда как предыдущий я попросила опираться только на данные о связанности.
Главные выводы
Инструменты анализа зависимостей вроде dependency‑cruiser могут работать как сенсоры с обратной связью в реальном времени: они помогают контролировать базовую структуру папок и направления зависимостей, но на этом их возможности во многом заканчиваются.
AI‑ревью модульности — отличный пример «уборки накопившихся проблем». С сильными промптами оно сработало довольно хорошо. Опора на реальные данные о связанности, похоже, не сильно повлияла на результат. Было бы здорово найти способ применять такой подход к файлам, изменённым в коммите, чтобы встроить его в пайплайн раньше, но я это пока не исследовала.
Я запустила ревью модульности уже после того, как большая часть кодовой базы была построена без такого ревью с моей стороны. И оно нашло несколько довольно тревожных и вполне валидных проблем, которые в будущем повысили бы риски. Это показывает, что без человеческого ревью и экспертизы в связанности, а также без дополнительных AI‑ревью, агент определённо накапливал непреднамеренный технический долг.
В целом дизайн кодовой базы и модульность выглядят как область, где одни вычислительные сенсоры нам мало помогут. Нужен ИИ, чтобы добавить семантическую интерпретацию и учитывать компромиссы.
Набор тестов как сенсор регрессий
У тестов много назначений: они помогают нам продумывать проектирование и направлять его, а также документируют желаемое поведение приложения — это и есть окончательная спецификация! — и помогают обнаруживать регрессии, то есть показывают, когда мы изменением ломаем уже существующую функциональность. Эффективные регрессионные тесты играют большую роль в сопровождаемости кодовой базы: с ними менять код гораздо безопаснее. Поэтому в контексте сенсоров сопровождаемости этот раздел посвящён роли набора тестов как сенсора регрессий.
Когда уже существующий тест падает, нам приходится задать себе вопрос: «Я случайно что‑то сломала, и значит, нужно менять реализацию? Или я намеренно меняю поведение, а значит, тесты должны измениться и отразить новую спецификацию?» Падающий тест даёт ИИ возможность задать себе тот же вопрос. Конечно, он не всегда примет правильное решение. Но хороший набор тестов снижает вероятность того, что ИИ сломает нужное уже существующее поведение.
В моём приложении для аналитики чатов агент постепенно написал все тесты сам, почти без моего контроля: я ограничивалась ручным тестированием и следила за покрытием кода тестами. Мне хотелось получить полностью ИИ‑сгенерированный набор тестов, чтобы затем задним числом проанализировать его эффективность для защиты от регрессий.
У подхода, в котором ИИ генерирует тесты без ревью, есть два основных риска:
покрытие кода тестами — недостаточный показатель эффективности тестов;
тесты могут проверять ошибочное поведение. Это гораздо более сложная проблема, чем проверка эффективности тестов, и тема для отдельного разговора. Эта статья сфокусирована только на эффективности тестов: если считать, что код реализует желаемое поведение, есть ли у нас тесты, которые ловят поломки.
Что есть в нашем наборе инструментов?
Покрытие кода тестами ($): отслеживает, какие части кода выполняются тестами, и даёт представление о том, какие части кода видимы и невидимы для тестов.
Тестирование на основе свойств ($): может находить недостающие логические тест‑кейсы, генерируя множество комбинаций входных данных из заданных свойств, вместо того чтобы вручную придумывать отдельные примеры.
Фаззинг ($$): может находить недостающие тест‑кейсы для устойчивости к входным данным, подсовывая системе неожиданные или некорректные входы.
Мутационное тестирование ($$): может находить недостающие проверки, внося небольшие мутации в код и проверяя, поймает ли их набор тестов.
В своём приложении я использовала покрытие кода тестами и мутационное тестирование, потому что тестирование на основе свойств и фаззинг хуже подходили для моего случая.
Мутационное тестирование
Вот небольшой пример из моей кодовой базы, который показывает, как мутационное тестирование помогает находить пробелы в проверках. Агент создал для меня эту диаграмму во время анализа результатов мутационного тестирования.

Файл mappers.ts показывал 100% покрытия инструкций и 75% покрытия ветвлений, но оказалось, что для него нет модульных тестов. А Stryker — инструмент мутационного тестирования, который я использовала, — сообщил о 13 выживших мутантах: то есть после 13 мутаций кода от Stryker набор тестов всё ещё оставался зелёным. Покрытие в этом случае было высоким, потому что в кодовой базе есть большой приёмочный тест, который в итоге вызывал эти функции.
Покрытие говорит нам, что строка была выполнена, но не говорит, что её эффект был проверен. Если бы эта небольшая вспомогательная функция dvpToSchema из mappers изменилась в будущем, она потенциально могла бы сломать отображение графика данных в UI.
Наблюдения
ИИ очень помог в анализе проблемных участков мутаций и составлении приоритизированного плана: где именно повышать качество тестов.
Stryker записывает результаты в огромный JSON‑файл. Чтобы упростить анализ и случайно не забить контекстное окно, я сгенерировала кастомный скрипт, который помогает агенту эффективно запрашивать результаты Stryker. Это лишь один из множества примеров, где ИИ помог мне помочь ИИ.
«Запрашивает JSON‑отчёт Stryker по мутационному тестированию из командной строки.»
Использование:
python query_stryker.py <report.json>; <command> [options]
Команды:
summary - Общие итоги по статусам, показатели мутационного тестирования, пороги.
files - Разбивка по файлам, по умолчанию отсортирована по возрастанию показателя мутационного тестирования.
hotspots - Строки с наибольшим числом выживших мутантов / мутантов без покрытия.
tests - Эффективность тестов: слабые, неиспользуемые или тесты, убивающие больше всего мутантов.
Примеры
1. Общее состояние — показатель мутационного тестирования, разбивка по статусам, прохождение/непрохождение порога
python./query_stryker.py reports/mutation/mutation.json summary
2. Сначала худшие файлы, с подсказкой к действию: усилить проверки или добавить тесты
python./query_stryker.py reports/mutation/mutation.json files ‑top 10 ‑v
3. То же самое, но только для файлов, изменённых вами в git: репозиторий определяется автоматически
python./query_stryker.py reports/mutation/mutation.json files ‑changed ‑v
4. Приблизиться к одному файлу: каждая строка, actionable‑счётчики, примеры мутаторов
python./query_stryker.py reports/mutation/mutation.json hotspots ‑file server/services/ai‑summaries.ts ‑top 30
Главные выводы
Сейчас, похоже, есть тренд в сторону приёмочных тестов в стиле end‑to‑end. Как я уже упоминала в начале, ИИ стал очень хорошо генерировать тесты, поэтому для разработчиков стало вполне нормальным просто позволять ИИ писать много тестов — почти без ревью. Ревью модульных тестов, в частности, может быть очень утомительным.
Я не говорю, что совсем не смотреть на них — это хорошо. Но признаю реальность: ожидать, что человеческое ревью всех тестов устойчиво масштабируется, нереалистично. И так же нереалистично ожидать, что люди действительно будут это делать. Поэтому, пока мы ищем подходящую форму пирамиды тестирования / рожка мороженого / маффина для будущего программирования с ИИ, набирают популярность техники вроде approved scenarios. Как показано выше, приёмочные тесты увеличивают покрытие, но часто не особенно богаты проверками.
Это создаёт ложное чувство уверенности в эффективности тестов, а мутационное тестирование помогает отслеживать этот разрыв.
У мутационного тестирования, конечно, есть практическое ограничение: оно довольно ресурсоёмкое. В моей настройке я не запускала его непрерывно, как некоторые другие сенсоры, а вручную инициировала инкрементальные прогоны.
Выводы и открытые вопросы
Вычислительные сенсоры больше всего впечатлили меня на уровне файлов и функций. Межфайловые аспекты вроде модульности и связанности — совсем другая история: сами по себе сырые данные оказались очень шумными и не слишком полезными без семантической интерпретации LLM, то есть без интерпретирующего сенсора. Но меня сильно впечатлили результаты и рекомендации, которые можно получить с хорошим промптом, а ещё потенциал представлять эту информацию по‑разному, под разные уровни опыта.
Чего я не увидела в своих экспериментах, но подозреваю, что со временем это может стать заметной проблемой, — конфликтов между сенсорами. Правила max‑lines и max‑lines‑per‑function уже показали признаки напряжения: рефакторинги в сторону всё более мелких функций переносили сложность в цепочки пропсов компонентов. Вероятно, где‑то рядом скрываются и другие компромиссы. Будет интересно посмотреть, станет ли это проблемой со временем и как именно.
В этом приложении я вообще не возилась с гайдами, потому что хотела чище увидеть эффект самих сенсоров. Мне интересно, как будет развиваться баланс между гайдами и сенсорами. Когда мы достаточно уверены в наборе сенсоров, от каких гайдов можно отказаться? Делают ли сенсоры использование более слабых моделей реалистичнее? Как поддерживать согласованность между гайдами и сенсорами, и найдём ли мы способы как‑то упаковывать их вместе, чтобы их было проще сопровождать?
В области регрессионного тестирования для меня по‑настоящему стало очевидно, насколько важным становится мутационное тестирование, когда мы решаем оставить большую часть тестирования ИИ. И ещё раз хочу подчеркнуть: о корректности самих тестов нужно говорить отдельно.
Некоторые из этих сенсоров действительно повышают моё доверие к качеству результатов, но это не волшебное решение, которое полностью выводит человека из процесса. Зато я точно почувствовала, что с вычислительными и интерпретирующими сенсорами как партнёрами мой опыт ревью стал лучше, а уровень доверия — выше.
Статьи по теме:

ИИ уже умеет писать код и тесты, но вопрос качества никуда не исчезает. На бесплатных открытых преподаватели-практики покажут, как применять ИИ в тестировании, искать скрытые ошибки и понимать, где автоматизации всё ещё нужен человеческий контроль. Заодно можно познакомиться с форматом обучения и задать вопросы.
18 июня, 20:00. «Тесты, которые чинят себя сами: практика ИИ в UI-тестировании». Записаться
16 июля, 20:00. «Профессия тестировщика в эпоху ИИ — угроза потери работы или суперсила?». Записаться
23 июля, 20:00. «Фаззинг и реверс: как понять, что делает программа, найти в ней ошибки». Записаться
Полный список бесплатных уроков смотрите в дайджесте.






















