ФАЗА 22. AI-АГЕНТЫ Инструменты качества

Обязательно

Правила агента, SonarQube, ESLint и ревью

SAST · Executable rules · Human judgment

Инструменты закрывают разные классы ошибок и усиливают друг друга вместо конкуренции.

1. Линтер быстро ловит известные шаблоны

1 / 5

ESLint, Checkstyle и golangci-lint проверяют синтаксис, типичные ошибки и соглашения детерминированно.

  • Правило имеет понятный идентификатор.
  • Результат повторяем.
  • Доменный смысл ограничен шаблоном.
Source codeLinter rulessyntax + known patternsFast findingRepeatable

2. SonarQube управляет качеством во времени

2 / 5

Платформа собирает issues, coverage, дубли и историю. Она показывает технический долг и ворота качества, но не понимает продуктовый сценарий.

  • Метрики сравниваются по периодам.
  • Quality gate задаёт порог.
  • Шум требует настройки правил.
Issuesbugs + smellsvulnerabilitiesTrendcoverage + dupeshistoryQuality gatethresholdsmerge decision

3. Исполняемые правила проверяют архитектуру

3 / 5

ArchUnit, dependency-cruiser и тесты границ подтверждают разрешённые связи и структуру. Это машинная проверка решения команды.

  • Запрещённая зависимость роняет тест.
  • Граница описана кодом.
  • Изменение правила проходит ревью.
API layerDomainDatabaseforbidden shortcut fails

4. AI ищет смысловые несоответствия

4 / 5

Агент сопоставляет спецификацию, сценарии и diff. Он может предложить крайний случай, но его находка требует воспроизведения.

  • Контекст ограничен и точен.
  • Утверждение не равно доказательству.
  • Тест подтверждает найденный риск.
Spec + diffcontextAI findingpossible behavior gapReproduceprove before fix

5. Человек решает компромисс

5 / 5

Цена изменения, читаемость, сроки и риск не сводятся к правилу. Тимлид объясняет выбор и отвечает за него.

  • Машина закрывает повторяемое.
  • AI расширяет поиск сценариев.
  • Человек принимает архитектурный tradeoff.
Automationknown rulesrepeatable gatesAInew scenariossemantic reviewHumantradeoffsresponsibility

Исполняемые правила агента vs SonarQube vs ESLint vs ревью тимлидом

Самый частый вопрос про исполняемый стандарт: «это же просто SonarQube?». Нет. Но и не вместо. Эти инструменты живут на разных уровнях абстракции и дополняют друг друга, а не конкурируют.

Эта статья — таблица сравнения по 12 параметрам и разбор «когда что использовать».

линтер NPE и off-by-one стиль инъекции и секреты исполняемые правила границы агрегатов доменные имена соглашения проекта человек верна ли граница компромиссы намерение

Три слоя проверки ловят разное: линтер работает с деревом кода, исполняемые правила читают прозу правил вместе с кодом, человек решает то, что не записано.

Резюме за 30 секунд

  • SonarQube / ESLint / Detekt — для багов уровня кода и стиля. Регексы, AST-анализ, безопасность.
  • Code review тимлидом — для архитектуры и домена. Не масштабируется на команду 20+.
  • Исполняемые правила агента с набором правил — для архитектуры и домена. Масштабируются. Дополняют линтеры, не заменяют.

Полная таблица сравнения

ПараметрSonarQube/ESLintТимлид-ревьюИсполняемые правила агента
Что проверяетстиль, security-баги, complexityархитектура, доменархитектура, домен
Глубина пониманиясинтаксис + ASTсемантика + контекстсемантика + контекст (через набор правил)
Кто исполняетстатический анализаторчеловекLLM-агент
Кодифицированоплагины + конфигв головеmarkdown в репо
Ссылка в обзореда (java:S1234)нетда (R-AGG-4)
Масштабируетсяданет (узкое место)да
Задержка обзорасекунды (CI)дни (тимлид reads)минуты (claude run)
False positivesсредненизкосредне
Понимает доменнетдада
Версионируетсярелиз плагинаустные правилаgit diff на наборе правил
Открытостьopen-source движок, часть проверок — только в платных редакцияхв головерепо команды
Lifecycle правилрелизы плагиновнеформальноgit, semantic diff

Где они НЕ конкурируют

Возьмём один и тот же PR — handler оплаты заказа:

public void handle(PayOrder cmd) {
    Order order = orders.findById(cmd.orderId()).get();
    order.setStatus(OrderStatus.PAID);
    order.setPaidAt(Instant.now());
    orders.save(order);
    events.publishEvent(new OrderPaid(order.id(), order.total()));
}
func (h *PayOrderHandler) Handle(ctx context.Context, cmd PayOrderCommand) error {
    order, err := h.orders.FindByID(ctx, cmd.OrderID)
    if err != nil {
        return err
    }
    order.Status = OrderStatusPaid
    order.PaidAt = time.Now()
    if err := h.orders.Save(ctx, order); err != nil {
        return err
    }
    return h.events.Publish(ctx, OrderPaidEvent{ID: order.ID, Total: order.Total})
}
async handle(cmd: PayOrderCommand): Promise<void> {
    const order = await this.orders.findById(cmd.orderId);
    order.status = OrderStatus.PAID;
    order.paidAt = new Date();
    await this.orders.save(order);
    await this.events.publish(new OrderPaidEvent(order.id, order.total));
}
from datetime import datetime, timezone

def handle(self, cmd: PayOrderCommand) -> None:
    order = self.orders.find_by_id(cmd.order_id)
    order.status = OrderStatus.PAID
    order.paid_at = datetime.now(tz=timezone.utc)
    self.orders.save(order)
    self.events.publish(OrderPaidEvent(id=order.id, total=order.total))

SonarQube скажет:

  • java:S3655 — Optional.get() без isPresent() — может бросить

И на этом всё. Что заказ переводят в оплаченный сеттерами, а событие уходит мимо транзакции, он не заметит: это не его уровень.

Исполняемое правило скажет:

  • R-AGG-X4 — событие зарегистрировано не в корне агрегата
  • R-ENT-X3 — состояние меняют публичным сеттером, а не бизнес-методом корня
  • R-EVT-X3 — событие публикуется прямо из хендлера, мимо корня и транзакционных гарантий

Тимлид скажет:

  • «Этот use case вообще должен быть Order.pay() или application service PaymentSaga? У нас же платёж асинхронный и приходит через webhook.»

Три уровня. Три инструмента. Все три нужны.

Когда что брать

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

Только линтер

  • Команда небольшая, несколько человек.
  • Простой CRUD-проект, не доменно-сложный.
  • Архитектурных договорённостей ещё нет — кодифицировать пока нечего.

Линтер + тимлид-ревью

  • Команда до десятка человек.
  • Есть техлид с устоявшейся архитектурой.
  • Архитектура держится на людях, не формализована.

Линтер + исполняемые правила + тимлид

  • Команда больше десятка человек.
  • Несколько сервисов, риск drift между ними.
  • Архитектурные паттерны достаточно устоялись, чтобы их кодифицировать.

Линтер + тимлид (без исполняемых правил)

  • Ранний прототип, всё меняется еженедельно.
  • Команда не доверяет AI-обзорам.
  • Архитектура ещё не устоялась.
Дополнительно: при первом чтении можно пропустить

Глубже: что общего у всех трёхрасширенное

Все три — это инструменты обзора, а не замены ответственности. Линтер не пишет код за вас. Тимлид не подписывает PR за разработчика. Исполняемое правило не принимает архитектурные решения. Все они — усилители, не подменители.

Качество ревью на любом из трёх уровней зависит от качества исходных стандартов:

  • ESLint без хорошей конфигурации — шум.
  • Тимлид без устоявшихся принципов — субъективные капризы.
  • Исполняемое правило без хорошего набора правил — обобщённый обзор уровня «улучши читаемость».

Качественный набор правил — то, что отличает зрелую команду от слепого копирования чужих практик.

Глубже: что исполняемые правила делают, чего не делают линтерырасширенное

Есть один класс правил, который традиционные линтеры принципиально не достанут:

  1. Правила про доменный слой. «Имя value object — существительное в единственном числе». Линтер не знает что такое value object и какое имя «доменное», а какое нет.
  2. Правила про границы. «Один use case изменяет один агрегат, остальные через события». Линтер не знает где проходят границы агрегатов в вашей доменной модели.
  3. Правила про намерение. «Имя класса соответствует доменному термину из ubiquitous language». Линтер не знает ubiquitous language.
  4. Правила про контекст. «В одних проектах маппинг идёт через отдельный mapper-класс, в других — через сгенерированный». Линтер не знает соглашения конкретного проекта и не выбирает правила в зависимости от них.

LLM умеет всё это потому что читает прозу правил вместе с прозой кода. Для LLM «доменный термин» и «не доменный» — это вопрос контекста, а не AST-узла.

Глубже: что линтеры делают, чего не делают исполняемые правиларасширенное

И обратное:

  1. Скорость. Линтер по изменённым файлам отрабатывает за секунды, полный прогон по сотне тысяч строк — минуты. Исполняемое правило — десятки секунд на PR.
  2. Детерминированность. Линтер на одном и том же коде даст один и тот же результат. LLM — с вариациями.
  3. Низкоуровневые баги. NPE, deadlock, off-by-one. Это работа AST-анализатора.
  4. Security-сканирование. SQL-инъекции, XSS, зашитые в код секреты — паттерны более чёткие. Одна оговорка: инъекции ищут прослеживанием пути данных от входа до запроса, и в SonarQube это есть только в платных редакциях — бесплатная Community такого не умеет.

Коротко

  • Правила про доменный слой, границы, намерение и контекст линтер не достанет: он не знает, что такое value object и где проходит граница агрегата.
  • Линтер выигрывает скоростью и детерминированностью; поиск инъекций по пути данных в SonarQube есть только в платных редакциях.
  • Все три усилители, а не подменители: качество ревью на любом уровне упирается в качество исходных стандартов.

Что почитать дальше