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

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

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

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

Все три должны работать одновременно в зрелой команде. Линтер ловит NPE, исполняемое правило ловит «событие зарегистрировано не в корне агрегата», человек смотрит на «правильно ли вообще выбрана граница агрегата».

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

ПараметрSonarQube/ESLintТимлид-ревьюИсполняемые правила агента
Что проверяетстиль, security-баги, complexityархитектура, доменархитектура, домен
Глубина пониманиясинтаксис + ASTсемантика + контекстсемантика + контекст (через набор правил)
Кто исполняетстатический анализаторчеловекLLM-агент
Кодифицированоплагины + конфигв головеmarkdown в репо
Ссылка в обзореда (java:S1234)нетда (AGG-4)
Масштабируетсяданет (узкое место)да
Задержка обзорасекунды (CI)дни (тимлид reads)минуты (claude run)
False positivesсредне (~10–20%)низкосредне (~10–15%)
Понимает доменнетдада
Версионируетсярелиз плагинаустные правила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:S3655Optional.get() без isPresent() — может бросить
  • java:S1192 — string literal "PAID" дублируется (если есть в других местах)

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

  • AGG-4 — событие зарегистрировано не в корне агрегата
  • ENT-3 — публичные сеттеры вместо бизнес-метода
  • EVT-4events.publishEvent() без транзакционных гарантий

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

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

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

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

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

  • Команда < 5 инженеров.
  • Простой CRUD-проект, не доменно-сложный.
  • Нет архитектурных стандартов в голове техлида.

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

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

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

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

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

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

Что общего у всех трёх

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

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

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

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

Что исполняемые правила делают, чего не делают линтеры

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

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

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

Что линтеры делают, чего не делают исполняемые правила

И обратное:

  1. Скорость. Линтер за 3 секунды просмотрит 100K строк. Исполняемое правило — десятки секунд на PR.
  2. Детерминированность. Линтер на одном и том же коде даст один и тот же результат. LLM — с вариациями.
  3. Низкоуровневые баги. NPE, deadlock, off-by-one. Это работа AST-анализатора.
  4. Security-сканирование. SQL injection, XSS, hardcoded secrets — паттерны более чёткие.

Дальше

  • Исполняемый стандарт — основная статья про подход