AI пишет код. Зачем тогда методология?

Самое частое возражение к Use Case Pattern: «зачем учить методологию, если Claude и так пишет код». Полный ответ с экспериментом, пятью сценариями где AI без методологии разваливается, разбором что такое «общий контекст» технически, и историческими параллелями.

зачем методология если AI пишет код

Это самое частое возражение к Use Case Pattern, его задают почти все, кому я первый раз показываю методологию. В разных формулировках:

— Зачем мне твой паттерн, мне Claude и так напишет код. — Сейчас можно просто описать что нужно — и сгенерируется. — Методологии — это для эпохи, когда AI не умел. — Что вы все цепляетесь за слойную архитектуру, дайте AI задачу — он справится.

Я с этим согласен. Частично. И именно потому, что согласен частично, я строю Use Case Pattern.

Эта статья — длинный ответ на возражение. Цель: убедить вас не в том, что AI плох (он отличен), а в том, что методология в эпоху AI становится не ненужной, а более ценной. Чем умнее становится AI, тем больше зависит от того, какие правила вы ему дадите.


Где AI без методологии действительно достаточно

Начну с признания. Для целого класса задач AI без всякой методологии справляется на ура:

  • Прототип, MVP, проверка гипотезы. Один разработчик, один сервис, цель — за две недели проверить, нужно ли это рынку. Здесь методология тормозит. Бери Claude, описывай задачу, получай код. Через три месяца либо переделаешь с нуля, либо выбросишь. Любой методологический оверхед — деньги в мусорку.
  • Скрипты, утилиты, ad hoc автоматизация. Один файл на Python, который раз в неделю собирает отчёт. Никто его не будет поддерживать командой. Никто не будет ревьюить. Если работает — работает.
  • Рефакторинг с понятной целью. «Вынеси этот метод в отдельный класс», «перепиши switch на pattern matching» — Claude отлично делает это в любом контексте. Нужна не методология, а тесты и здравый смысл.
  • Изоляция учебных задач. «Покажи как работает Saga на простом примере». Здесь методология даже мешает — упрощённый пример должен быть упрощённым.
  • Личный пет-проект. Один человек, один кодовый стиль (даже если он меняется каждый месяц), нет команды, нет долгосрочной поддержки. Делайте как удобно.

Если ваш контекст — один из этих, закройте эту статью. UCP вам не нужен. Весь дальнейший разговор — про другой контекст.


Где AI без методологии разваливается

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

В backend-кластере, которым я руковожу, команда — 4 фича-команды плюс платформенная, 20+ инженеров. Продукт — государственная IT-система с 5+ годами планируемой жизни. Время — на годы вперёд, не на спринт. И в этом контексте я наблюдал пять сценариев, в которых AI без методологии раз за разом разваливается.

1. Несогласованность между сессиями

Поставьте Claude трижды одну и ту же задачу: «напиши Spring Boot сервис обработки заказов с REST API на создание, оплату, отмену». В трёх отдельных чатах, без всякого общего контекста.

Получите три разных решения. Все три — рабочие. Все три — типизированные, с тестами, с обработкой ошибок. Но:

  • В первом — три отдельных контроллера: OrderController, PaymentController, CancelController.
  • Во втором — один OrderController с тремя методами: POST /orders, POST /orders/{id}/payment, POST /orders/{id}/cancel.
  • В третьем — OrderController с методами и OrderCommandHandler для команд.

Каждое решение по отдельности нормально. Вместе — три разных стиля проектирования API в одном кодбазе. Через месяц у вас 30 сервисов, каждый чуть-чуть по-другому. Через год новый разработчик в команде проводит первую неделю на «понять, как у нас принято».

Решение Claude зависит от того, что чаще встречалось в его training data на момент конкретного запроса. Без явных правил извне он не выберет ваш стиль — он выберет средний из всех Java-разработчиков мира.

2. Несогласованность между разработчиками

Та же задача, но решает её разработчик A, B, C — каждый в своей сессии Claude.

A — работал в Spring до этого, попросит Claude использовать Spring Data JPA. B — пришёл из Kotlin/Ktor, попросит Claude сделать на чистых JDBC + DTO. C — недавно прочитал Vaughn Vernon, попросит Claude сделать «по DDD».

Все трое получат от Claude рабочий код. Все трое будут считать его «правильным» в своём контексте. И после слияния PR-ов в main у вас три сервиса в одном репозитории, написанные в трёх несовместимых стилях, и никто из троих не сделал ничего «неправильно».

Это не баг Claude. Это последствие того, что у Claude нет источника правды о вашем стиле, кроме промпта конкретного разработчика.

3. Несогласованность во времени

Вы делаете сервис в январе. Claude генерирует красивый код. Через год — добавляете новую фичу. Снова просите Claude.

За год Claude обновился до новой версии. Изменилось training data. По-другому понимает «лучшие практики». Сгенерирует код, который не похож на ваш январский. На ревью замечают: «у нас обычно не так». Но обычно как? Промпт для Claude в январе никто не сохранял. Договорённости — в головах разработчиков, многие из которых уже ушли.

Вы получаете дрейф — каждый новый кусок кода чуть-чуть в другом стиле, чем то, что было до. Через 3-5 лет код выглядит как археологический раскоп: «вот это писали в 2025, видно по неймингу; а это в 2027, тут уже другая школа».

4. Сложный домен и архитектурные trade-off

Claude не делает архитектурных решений. Он применяет шаблоны.

Спросите его: «у меня заказ, который должен быть оплачен через внешний шлюз, потом отгружен через курьерку, и если отгрузка провалилась — деньги вернуть. Как это сделать?»

Claude напишет один из вариантов:

  • Прямые вызовы (oh well)
  • Synchronous orchestration через transaction
  • Saga с компенсациями
  • Event-driven через Outbox
  • 2PC через Atomikos (если ему так захочется)

Все варианты он напишет грамотно. Но выбор между ними — это архитектурное решение, которое зависит от десятка факторов: SLA, сколько денег готовы потерять при отказе, какие соседние команды есть, можно ли просить заказчика подождать 5 секунд, насколько критичен idempotency, и т.д.

Без вашей методологии Claude выберет «самое популярное в training data» решение для подобной задачи. Это будет грамотно, но необязательно правильно для вашего контекста.

С методологией — у Claude есть инструкция: «у нас в проекте уровень зрелости 3 (DDD), для распределённых процессов используем Saga + Outbox, идемпотентность ключевая, синхронных распределённых транзакций избегаем». Тогда Claude выбирает не среднее, а ваше.

5. Регрессия качества по умолчанию

Claude по умолчанию делает то, что выглядит правильным, но не всегда то, что правильно.

Несколько типичных дефолтов «голого» Claude:

  • При ошибке возвращает null или Optional.empty() без логирования
  • В catch-блоке пишет // log error and continue без явной обработки
  • Идемпотентность не реализует, если её явно не попросить
  • Метрики не добавляет (никто же не упомянул метрики)
  • Тайм-ауты на внешние вызовы — дефолтные (то есть бесконечные)
  • DTO возвращает наружу как есть, даже если внутри domain-объект

Каждый из этих дефолтов технически работает. Но в проде через полгода вы ловите: тихие ошибки которые никто не видел, висящие соединения с упавшим внешним сервисом, дублирующиеся заказы потому что нажали «купить» дважды.

С правилами методологии Claude поведение меняется: «не возвращай null, возвращай типизированную ошибку», «обработка ошибок обязательна, swallow запрещён», «все внешние вызовы с тайм-аутом из конфига», «DTO != domain». Эти правила должны быть проверяемыми — иначе их никто не соблюдает.


Демонстрация: один и тот же бриф, два прохода

Чтобы это было не слова, разберём конкретный пример.

Бриф (одинаковый для обоих проходов): «Сделай Spring Boot сервис, который принимает команду оплатить заказ. Должен вызвать платёжный шлюз, получить ответ, сохранить статус. Если шлюз не отвечает — повторить. Если отказ — оставить заказ в pending.»

Проход 1: Claude без методологии

Дайте этот промпт Claude в чистой сессии. Получите примерно такой код:

@RestController
@RequestMapping("/api/payments")
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @PostMapping("/{orderId}/pay")
    public ResponseEntity<String> pay(@PathVariable String orderId) {
        try {
            paymentService.processPayment(orderId);
            return ResponseEntity.ok("Payment processed");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Payment failed: " + e.getMessage());
        }
    }
}

@Service
public class PaymentService {

    @Autowired
    private PaymentGatewayClient gateway;

    @Autowired
    private OrderRepository orderRepository;

    public void processPayment(String orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));

        for (int i = 0; i < 3; i++) {
            try {
                PaymentResult result = gateway.charge(order.getAmount());
                if (result.isSuccess()) {
                    order.setStatus("PAID");
                } else {
                    order.setStatus("PENDING");
                }
                orderRepository.save(order);
                return;
            } catch (Exception e) {
                if (i == 2) throw new RuntimeException("Gateway unavailable", e);
                try { Thread.sleep(1000); } catch (InterruptedException ie) {}
            }
        }
    }
}

Это рабочий код. Тесты к нему напишутся, в проде он будет крутиться. Но у него куча проблем для production-системы:

  • String orderId вместо типизированного OrderId
  • RuntimeException без иерархии — все ошибки одинаковые
  • String status = "PAID" — magic string, не enum
  • Нет идемпотентности: повторный POST приведёт к повторному списанию
  • Бизнес-логика в Service, не в домене
  • Retry-логика руками вместо Resilience4j
  • Платёжный шлюз без circuit breaker и тайм-аута
  • DTO для ответа — голая String

Каждое из замечаний — пункт code-review. На команде из 20 человек по таким PR-ам уходит больше времени, чем на сам код.

Проход 2: Claude с методологией

Теперь то же самое, но Claude знает: «у нас Use Case Pattern уровень 3, REST API style guide, наш Java code style, все внешние вызовы через resilience4j, идемпотентность по Idempotency-Key обязательна, доменная логика в агрегате».

Это знание загружается через Claude Code skills (ucp-pattern-design, ucp-api-design, ucp-java-style-review). Не нужно повторять в каждом промпте — скиллы применяются автоматически.

Тот же бриф даст примерно:

// adapter/in/rest/OrderController.java
@RestController
@RequestMapping("/v1/orders")
@RequiredArgsConstructor
@InboundAdapter("REST controller for order commands")
class OrderController {

    private final UseCaseDispatcher dispatcher;

    @PostMapping("/{orderId}/pay")
    PayOrderResponse pay(@PathVariable OrderId orderId,
                         @RequestHeader("Idempotency-Key") IdempotencyKey idempotencyKey,
                         @AuthenticationPrincipal Buyer buyer) {
        return dispatcher.dispatch(new PayOrderCommand(orderId, buyer.id(), idempotencyKey));
    }
}

// app/usecase/PayOrderCommand.java
record PayOrderCommand(OrderId orderId, BuyerId buyerId, IdempotencyKey idempotencyKey)
    implements UseCaseCommand<PayOrderResponse> {}

record PayOrderResponse(OrderId orderId, OrderStatus status, PaymentReference reference) {}

// app/usecase/PayOrderHandler.java
@InboundPort
@RequiredArgsConstructor
class PayOrderHandler implements UseCaseHandler<PayOrderCommand, PayOrderResponse> {

    private final OrderRepository orders;
    private final PaymentGateway gateway;
    private final IdempotencyStore idempotency;
    private final DomainEventPublisher events;

    @Override
    @Transactional
    public PayOrderResponse handle(PayOrderCommand cmd) {
        return idempotency.executeOnce(cmd.idempotencyKey(), () -> {
            Order order = orders.findById(cmd.orderId())
                .orElseThrow(() -> new OrderNotFoundException(cmd.orderId()));

            PaymentReference ref = gateway.charge(order.totalAmount(), cmd.idempotencyKey());
            order.markPaid(ref);   // domain logic — invariants checked inside
            orders.save(order);
            events.publish(order.pullEvents());

            return new PayOrderResponse(order.id(), order.status(), ref);
        });
    }
}

// adapter/out/payment/PaymentGatewayAdapter.java
@OutboundAdapter("HTTP client to payment gateway")
@RequiredArgsConstructor
class PaymentGatewayAdapter implements PaymentGateway {

    private final PaymentGatewayHttpClient client;

    @Override
    @Retry(name = "paymentGateway")
    @CircuitBreaker(name = "paymentGateway", fallbackMethod = "fallback")
    @TimeLimiter(name = "paymentGateway")
    public PaymentReference charge(Money amount, IdempotencyKey idempotencyKey) {
        var response = client.charge(new ChargeRequest(amount, idempotencyKey));
        return new PaymentReference(response.transactionId());
    }

    private PaymentReference fallback(Money amount, IdempotencyKey key, Throwable t) {
        throw new PaymentGatewayUnavailableException(key, t);
    }
}

Сравните с первым проходом. Без выкладок:

  • Типизированные value objects (OrderId, IdempotencyKey, Money, PaymentReference) вместо String
  • Иерархия исключений (OrderNotFoundException, PaymentGatewayUnavailableException)
  • Идемпотентность — обязательна и встроена через executeOnce
  • Бизнес-логика — в order.markPaid(ref), инварианты проверяются в домене
  • Resilience4j — через аннотации (@Retry, @CircuitBreaker, @TimeLimiter)
  • Платёжный шлюз — за интерфейсом PaymentGateway (порт), реализация — адаптер
  • Hexagonal-аннотации (@InboundAdapter, @OutboundAdapter, @InboundPort)
  • DTO ответа — типизированный record
  • Паттерн UseCase + Handler + Dispatcher соблюдён

И главное: это не результат гениального промпта. Это результат того, что Claude знает правила вашего проекта. Любой разработчик в команде получит на этот бриф то же самое решение, потому что скиллы применяются ко всем сессиям одинаково.

Размер промпта при этом не больше. «Реализуй use case оплаты заказа» — и Claude применяет всё описанное выше автоматически.


Что такое «общий контекст» технически

Возражение «зачем методология, AI и так пишет код» исходит из неявного допущения, что AI работает в одиночку. Один промпт, один ответ.

В реальности AI всегда работает в каком-то контексте. Вопрос только в том, какой это контекст:

  • Без методологии: контекст = training data (среднее всех Java-разработчиков мира) + текст промпта (то, что разработчик помнит написать).
  • С методологией: контекст = ваши явные правила (скиллы), ваша история решений (Memory Bank), ваш шаблон спеки (16 разделов с типизированными полями).

Это три слоя:

Слой 1: Скиллы (правила игры)

Claude Code skill — это не подсказка. Это правила игры, которые Claude применяет автоматически каждый раз, когда видит подходящий контекст.

Пример: ucp-api-design содержит примерно следующие правила (упрощённо):

  • URL всегда в kebab-case, ресурсы во множественном числе
  • Action-эндпоинты через POST /{resource}/{id}/{action}, не GET
  • Версионирование в URL: /v1/
  • Идемпотентность через заголовок Idempotency-Key
  • Ошибки по RFC 9457 Problem Details
  • Алиасы для текущего пользователя: /users/me, не /users/current

Это всё — проверяемые правила. Скилл может найти нарушение и подсветить. AI пишет API → Claude применяет эти правила → выходит API в стиле проекта, а не среднем.

Один скилл = один аспект кодовой базы. У нас сейчас 6 скиллов (REST API, Java code style, тесты, DDD-тактика, Use Case Pattern, спецификации) — каждый закрывает свой пласт. Все вместе — исполняемый стандарт команды.

Слой 2: Memory Bank (что мы решили однажды)

Для каждого сервиса в репозитории есть memory-bank/ — папка с тремя-пятью markdown-файлами, в которых описано:

  • Какой Bounded Context этот сервис реализует
  • Какие у него агрегаты, какие соседние сервисы
  • Какие архитектурные решения уже приняты («мы используем Outbox через PostgreSQL LISTEN/NOTIFY», «idempotency хранится в Redis с TTL 24h»)
  • Какие открытые вопросы и почему они открыты

Это не документация для людей (хотя люди тоже могут читать). Это контекст для AI — Claude в каждой сессии читает Memory Bank первым делом и понимает, в каком сервисе он находится.

Без Memory Bank Claude в каждой новой сессии — как новый разработчик в первый день: знает Java, не знает ваш проект. С Memory Bank — как старожил, знакомый с историей решений.

Слой 3: Шаблон спеки (структура, по которой можно проверить)

Самый недооценённый слой. Один из читателей сформулировал это лучше меня:

«У вашей методологии не самое очевидное место — это не AI-агенты, а сам шаблон из 16 разделов с уровнями детализации. Большинство наших спек проваливают тест на машинную проверяемость просто потому, что они написаны прозой. Чтобы линтер — хоть AI, хоть обычный — что-то поймал, спека должна быть структурой полей, а не сочинением.»

Точно. AI-агенты — это второй порядок производной. Они работают только когда есть структура, по которой их можно настроить. Если спека — сочинение прозой, никакой агент не вытащит из неё контракт интеграций или матрицу переходов состояний — ему попросту нечего парсить.

Use Case спецификация на 16 разделов с тремя уровнями детализации (Tier A/B/C) — это первый порядок производной. Сама структура. Скиллы и Memory Bank — настройка над этой структурой. Без структуры они бесполезны, со структурой — мощные.

Это и есть глубокий ответ на «зачем методология, если AI пишет код». Не для AI, а чтобы AI вообще мог что-то проверить.


Парадокс инверсии: чем умнее AI, тем нужнее методология

Большинство возражений к методологии в эпоху AI исходят из интуиции «AI стал умнее → разработчику не нужно знать архитектуру → методология тем более не нужна».

Эта интуиция неверна. Правильнее так:

AI применяет любые правила, какие ему дать. Без правил — берёт средние из training data. С правилами — следует вашим. Чем точнее AI, тем точнее он следует тому, что вы ему дадите. И тем важнее, чтобы то, что вы ему даёте, было хорошо продумано.

Аналогия: мощный станок ЧПУ режет что угодно по любой программе. Если программа плохая, станок точно вырежет плохо. Если программы вообще нет — станок не работает.

AI-генератор кода — мощный станок. Методология — программа для него. Чем мощнее станок, тем больше зависит от программы, не наоборот.

Без методологии вы получаете: AI рисует «среднюю Java-программу». Через 5 лет проект — зоопарк из «средних программ» разных поколений Claude.

С методологией вы получаете: AI рисует вашу программу — последовательно, согласованно, с учётом ваших архитектурных решений. Через 5 лет проект — единый организм, в котором новый разработчик за день понимает код любого сервиса.


Историческая параллель

Этот разговор уже был. Несколько раз.

Конец 1990-х: «Зачем нам Spring, когда есть Servlet API?»

Аргумент тогдашних скептиков: «Вы можете написать любой сервис на голом Servlet API, всё под рукой. Зачем добавлять фреймворк со своими IoC и аннотациями?»

Ответ оказался простым: совместимость. Spring задал общий контекст для всех Java-приложений того времени. Команда, которая писала на Spring, могла легко переключаться между проектами. Команда без фреймворка — каждый раз изобретала колесо.

Сегодня никто не спорит, что Spring был правильным шагом.

1980-е: «Зачем POSIX, если можно ядро написать заново?»

Каждый Unix-подобный продавец считал свою систему уникальной. AIX, HP-UX, Solaris, IRIX — десяток несовместимых систем. POSIX задал стандарт. Ругали его за бюрократию, за компромиссы, за «убийство гениальных идей».

Сегодня существуют ровно два направления Unix: Linux и BSD. Оба POSIX-совместимые. Без POSIX был бы зоопарк, а Linux никогда бы не стал доминирующим — переносимости приложений не было бы.

2010-е: «Зачем линтер, когда программист умный?»

ESLint, Checkstyle, Sonar. Те же возражения: «программист и так знает как писать», «линтер замедляет ревью». Ответ — масштаб. На команде из трёх человек все правила в головах. На команде из 50 — без линтера правила есть только на бумаге, и они нарушаются ежедневно.

2020-е: «Зачем методология, когда AI пишет код?»

Тот же класс возражений. Тот же класс ответа.

Не «зачем», а «затем что»: совместимость, масштаб, согласованность во времени, общий контекст. Методология не отменяет AI — она задаёт ему рамки, в которых он перестаёт быть «средним из всех Java-разработчиков мира» и становится вашим разработчиком.


Заключение

AI пишет код. Это правда.

AI без методологии хорош для одиночного MVP, прототипа, скрипта. Это правда.

Для команды, продукта и времени — AI без методологии превращается в зоопарк через год.

С методологией AI становится в десять раз ценнее, потому что выходит за рамки «среднего из training data» и работает в вашем контексте.

Use Case Pattern — это попытка такой методологии, открытой и адаптируемой. Четыре столпа:

  1. Pattern — слойная архитектура, в которой use case это первичная единица
  2. Спецификация — 16 разделов с тремя уровнями детализации (Tier A/B/C), машинно-проверяемая структура а не сочинение
  3. Уровни зрелости 1-4 — от MVP до Hexagonal, выбор под задачу
  4. AI-агенты как часть методологии — Claude Code skills, которые применяют правила автоматически

Если вы соглашаетесь с тезисом «AI без методологии хорош для MVP, для команды — нет» — добро пожаловать. Вот что предлагаю прочитать дальше:

Если же вы остаётесь при мнении «AI и так всё сделает» — закройте эту статью, она не для вас. Не агитирую переубеждаться. Возможно, у вас именно тот контекст, в котором это правильное решение.

А если интересно как это всё работает в реальной команде на 20+ инженеров — подписывайтесь на LinkedIn, там я выкладываю наблюдения из практики.